diff --git a/.env.test b/.env.test
new file mode 100644
index 0000000..2f1e270
--- /dev/null
+++ b/.env.test
@@ -0,0 +1,4 @@
+# define your env variables for the test env here
+KERNEL_CLASS='App\Kernel'
+APP_SECRET='$ecretf0rt3st'
+FIXTURES_SIZE=none
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..ef430f7
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,34 @@
+name: Execute CI tests
+
+on:
+ push:
+ branches:
+ - main
+ pull_request: ~
+
+jobs:
+ test-smoke:
+ name: Run smoke tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install the project for test environment
+ run: make test-install
+
+ - name: Run smoke tests
+ run: make test-smoke
+
+ test-functional:
+ name: Run functional tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install the project for test environment
+ run: make test-install
+
+ - name: Run functional tests
+ run: make test-functional
diff --git a/.gitignore b/.gitignore
index 199a138..dd548c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,7 @@ compose.override.yaml
###> lexik/jwt-authentication-bundle ###
/config/jwt/*.pem
###< lexik/jwt-authentication-bundle ###
+
+###> phpunit/phpunit ###
+/.phpunit.cache/
+###< phpunit/phpunit ###
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 3788194..7a2d762 100755
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -3,6 +3,9 @@
$finder = (new PhpCsFixer\Finder())
->in(__DIR__)
->exclude('var')
+ ->notPath([
+ 'tests/bootstrap.php',
+ ])
;
return (new PhpCsFixer\Config())
diff --git a/Makefile b/Makefile
index a2523f1..ea64128 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,10 @@ RUN_PHP_ALONE=$(DOCKER_COMPOSE) run --no-deps --rm $(DOCKER_PHP_CONTAINER)
COMPOSER=$(EXEC_PHP) composer
CONSOLE=$(EXEC_PHP) bin/console
+test-%: RUN_PHP=$(DOCKER_COMPOSE) run --rm --env APP_ENV=test --env XDEBUG_MODE=off $(DOCKER_PHP_CONTAINER)
+test-%: CONSOLE=$(RUN_PHP) bin/console
+test-%: COMPOSER=$(RUN_PHP) composer
+
##
###--------------#
### Docker #
@@ -54,8 +58,9 @@ start: up ## Alias for up
start-all: up-all ## Alias for up-all
install: pull compose.override.yaml up vendor db-schema-force generate-keypair db-fixtures ## Install the project
+test-install: pull vendor generate-keypair db-create db-schema-force db-fixtures ## Install the project for test environment.
-.PHONY: up stop down pull sh build bash start start-all install
+.PHONY: up stop down pull sh build bash start start-all install test-install
##
###----------------#
@@ -105,6 +110,27 @@ db-execute-down: ## Execute the latest migration versions down manually.
.PHONY: db-create db-drop db-migrate db-validate db-schema db-schema-force db-diff db-update
.PHONY: db-fixtures fixtures db-execute-up db-execute-down
+##
+###-------------#
+### Tests #
+###-------------#
+##
+
+test-smoke: ## Run smoke tests
+ $(RUN_PHP) bin/phpunit --no-extensions --testsuite smoke
+
+test-debug: ## Run tests with debug group/tags
+ $(RUN_PHP) bin/phpunit --group debug
+
+test-functional: ## Run functional tests
+ $(RUN_PHP) bin/phpunit --testsuite functional
+
+tests: test-smoke test-functional ## Execute all tests
+ts: test-smoke ## Alias for test-smoke
+tf: test-functional ## Alias for test-functional
+
+.PHONY: test-smoke test-debug tests test-functional ts tf
+
##
###-----------------#
### Q&A tools #
diff --git a/bin/phpunit b/bin/phpunit
new file mode 100755
index 0000000..692bacc
--- /dev/null
+++ b/bin/phpunit
@@ -0,0 +1,23 @@
+#!/usr/bin/env php
+= 80000) {
+ require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
+ } else {
+ define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
+ require PHPUNIT_COMPOSER_INSTALL;
+ PHPUnit\TextUI\Command::main();
+ }
+} else {
+ if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
+ echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
+ exit(1);
+ }
+
+ require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
+}
diff --git a/compose.yaml b/compose.yaml
index 7116a90..d83286d 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -33,6 +33,8 @@ services:
depends_on:
postgres:
condition: service_healthy
+ maildev:
+ condition: service_started
volumes:
- "./:/var/www:rw"
environment:
diff --git a/composer.json b/composer.json
index 863a253..91869d4 100644
--- a/composer.json
+++ b/composer.json
@@ -42,6 +42,7 @@
"symfony/framework-bundle": "7.2.*",
"symfony/intl": "7.2.*",
"symfony/mailer": "7.2.*",
+ "symfony/monolog-bundle": "^3.10",
"symfony/property-access": "7.2.*",
"symfony/property-info": "7.2.*",
"symfony/runtime": "7.2.*",
@@ -52,12 +53,17 @@
"symfony/yaml": "7.2.*"
},
"require-dev": {
+ "dama/doctrine-test-bundle": "^8.3",
"doctrine/doctrine-fixtures-bundle": "^4.1",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^2.1.17",
"phpstan/phpstan-doctrine": "^2.0.3",
"phpstan/phpstan-strict-rules": "^2.0.4",
"phpstan/phpstan-symfony": "^2.0.6",
+ "phpunit/phpunit": "^12.2",
+ "symfony/browser-kit": "7.2.*",
+ "symfony/css-selector": "7.2.*",
+ "symfony/http-client": "7.2.*",
"symfony/maker-bundle": "^1.63",
"symfony/stopwatch": "7.2.*",
"symfony/web-profiler-bundle": "7.2.*",
diff --git a/composer.lock b/composer.lock
index 97bb350..187ef81 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": "11173d976300c025c5db1074d87862a5",
+ "content-hash": "b126cc34d457fa83747ad8571976f54c",
"packages": [
{
"name": "api-platform/doctrine-common",
@@ -2826,6 +2826,109 @@
],
"time": "2025-05-24T22:26:38+00:00"
},
+ {
+ "name": "monolog/monolog",
+ "version": "3.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/monolog.git",
+ "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6",
+ "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/log": "^2.0 || ^3.0"
+ },
+ "provide": {
+ "psr/log-implementation": "3.0.0"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^3.0",
+ "doctrine/couchdb": "~1.0@dev",
+ "elasticsearch/elasticsearch": "^7 || ^8",
+ "ext-json": "*",
+ "graylog2/gelf-php": "^1.4.2 || ^2.0",
+ "guzzlehttp/guzzle": "^7.4.5",
+ "guzzlehttp/psr7": "^2.2",
+ "mongodb/mongodb": "^1.8",
+ "php-amqplib/php-amqplib": "~2.4 || ^3",
+ "php-console/php-console": "^3.1.8",
+ "phpstan/phpstan": "^2",
+ "phpstan/phpstan-deprecation-rules": "^2",
+ "phpstan/phpstan-strict-rules": "^2",
+ "phpunit/phpunit": "^10.5.17 || ^11.0.7",
+ "predis/predis": "^1.1 || ^2",
+ "rollbar/rollbar": "^4.0",
+ "ruflin/elastica": "^7 || ^8",
+ "symfony/mailer": "^5.4 || ^6",
+ "symfony/mime": "^5.4 || ^6"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+ "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+ "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+ "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
+ "ext-mbstring": "Allow to work properly with unicode symbols",
+ "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+ "ext-openssl": "Required to send log messages using SSL",
+ "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
+ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+ "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+ "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+ "rollbar/rollbar": "Allow sending log messages to Rollbar",
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Monolog\\": "src/Monolog"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "https://seld.be"
+ }
+ ],
+ "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+ "homepage": "https://github.com/Seldaek/monolog",
+ "keywords": [
+ "log",
+ "logging",
+ "psr-3"
+ ],
+ "support": {
+ "issues": "https://github.com/Seldaek/monolog/issues",
+ "source": "https://github.com/Seldaek/monolog/tree/3.9.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Seldaek",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-03-24T10:02:05+00:00"
+ },
{
"name": "nelmio/cors-bundle",
"version": "2.5.0",
@@ -5316,6 +5419,165 @@
],
"time": "2025-04-27T13:34:41+00:00"
},
+ {
+ "name": "symfony/monolog-bridge",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/monolog-bridge.git",
+ "reference": "bbae784f0456c5a87c89d7c1a3fcc9cbee976c1d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/bbae784f0456c5a87c89d7c1a3fcc9cbee976c1d",
+ "reference": "bbae784f0456c5a87c89d7c1a3fcc9cbee976c1d",
+ "shasum": ""
+ },
+ "require": {
+ "monolog/monolog": "^3",
+ "php": ">=8.2",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/service-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "symfony/console": "<6.4",
+ "symfony/http-foundation": "<6.4",
+ "symfony/security-core": "<6.4"
+ },
+ "require-dev": {
+ "symfony/console": "^6.4|^7.0",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/mailer": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/mime": "^6.4|^7.0",
+ "symfony/security-core": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0"
+ },
+ "type": "symfony-bridge",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bridge\\Monolog\\": ""
+ },
+ "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 integration for Monolog with various Symfony components",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/monolog-bridge/tree/v7.2.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-10-14T18:16:08+00:00"
+ },
+ {
+ "name": "symfony/monolog-bundle",
+ "version": "v3.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/monolog-bundle.git",
+ "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181",
+ "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181",
+ "shasum": ""
+ },
+ "require": {
+ "monolog/monolog": "^1.25.1 || ^2.0 || ^3.0",
+ "php": ">=7.2.5",
+ "symfony/config": "^5.4 || ^6.0 || ^7.0",
+ "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0",
+ "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
+ "symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0"
+ },
+ "require-dev": {
+ "symfony/console": "^5.4 || ^6.0 || ^7.0",
+ "symfony/phpunit-bridge": "^6.3 || ^7.0",
+ "symfony/yaml": "^5.4 || ^6.0 || ^7.0"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\MonologBundle\\": ""
+ },
+ "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": "Symfony MonologBundle",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "log",
+ "logging"
+ ],
+ "support": {
+ "issues": "https://github.com/symfony/monolog-bundle/issues",
+ "source": "https://github.com/symfony/monolog-bundle/tree/v3.10.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-11-06T17:08:13+00:00"
+ },
{
"name": "symfony/password-hasher",
"version": "v7.2.0",
@@ -7895,6 +8157,75 @@
}
],
"packages-dev": [
+ {
+ "name": "dama/doctrine-test-bundle",
+ "version": "v8.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dmaicher/doctrine-test-bundle.git",
+ "reference": "11846789ca2a86f6277316b42448f7fccb3965ff"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/11846789ca2a86f6277316b42448f7fccb3965ff",
+ "reference": "11846789ca2a86f6277316b42448f7fccb3965ff",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/dbal": "^3.3 || ^4.0",
+ "doctrine/doctrine-bundle": "^2.11.0",
+ "php": ">= 8.1",
+ "psr/cache": "^2.0 || ^3.0",
+ "symfony/cache": "^6.4 || ^7.2",
+ "symfony/framework-bundle": "^6.4 || ^7.2"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<10.0"
+ },
+ "require-dev": {
+ "behat/behat": "^3.0",
+ "friendsofphp/php-cs-fixer": "^3.27",
+ "phpstan/phpstan": "^2.0",
+ "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
+ "symfony/process": "^6.4 || ^7.2",
+ "symfony/yaml": "^6.4 || ^7.2"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "8.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "DAMA\\DoctrineTestBundle\\": "src/DAMA/DoctrineTestBundle"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "David Maicher",
+ "email": "mail@dmaicher.de"
+ }
+ ],
+ "description": "Symfony bundle to isolate doctrine database tests and improve test performance",
+ "keywords": [
+ "doctrine",
+ "isolation",
+ "performance",
+ "symfony",
+ "testing",
+ "tests"
+ ],
+ "support": {
+ "issues": "https://github.com/dmaicher/doctrine-test-bundle/issues",
+ "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.3.0"
+ },
+ "time": "2025-03-04T10:07:03+00:00"
+ },
{
"name": "doctrine/data-fixtures",
"version": "2.0.2",
@@ -8128,70 +8459,315 @@
"time": "2024-11-21T13:46:39+00:00"
},
{
- "name": "nikic/php-parser",
- "version": "v5.5.0",
+ "name": "masterminds/html5",
+ "version": "2.9.0",
"source": {
"type": "git",
- "url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
+ "url": "https://github.com/Masterminds/html5-php.git",
+ "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
- "reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6",
+ "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6",
"shasum": ""
},
"require": {
- "ext-ctype": "*",
- "ext-json": "*",
- "ext-tokenizer": "*",
- "php": ">=7.4"
+ "ext-dom": "*",
+ "php": ">=5.3.0"
},
"require-dev": {
- "ircmaxell/php-yacc": "^0.0.7",
- "phpunit/phpunit": "^9.0"
+ "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
},
- "bin": [
- "bin/php-parse"
- ],
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.0-dev"
+ "dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
- "PhpParser\\": "lib/PhpParser"
+ "Masterminds\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "BSD-3-Clause"
+ "MIT"
],
"authors": [
{
- "name": "Nikita Popov"
+ "name": "Matt Butcher",
+ "email": "technosophos@gmail.com"
+ },
+ {
+ "name": "Matt Farina",
+ "email": "matt@mattfarina.com"
+ },
+ {
+ "name": "Asmir Mustafic",
+ "email": "goetas@gmail.com"
}
],
- "description": "A PHP parser written in PHP",
+ "description": "An HTML5 parser and serializer.",
+ "homepage": "http://masterminds.github.io/html5-php",
"keywords": [
+ "HTML5",
+ "dom",
+ "html",
"parser",
- "php"
+ "querypath",
+ "serializer",
+ "xml"
],
"support": {
- "issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0"
+ "issues": "https://github.com/Masterminds/html5-php/issues",
+ "source": "https://github.com/Masterminds/html5-php/tree/2.9.0"
},
- "time": "2025-05-31T08:24:38+00:00"
+ "time": "2024-03-31T07:05:07+00:00"
},
{
- "name": "phpstan/extension-installer",
- "version": "1.4.3",
+ "name": "myclabs/deep-copy",
+ "version": "1.13.1",
"source": {
"type": "git",
- "url": "https://github.com/phpstan/extension-installer.git",
- "reference": "85e90b3942d06b2326fba0403ec24fe912372936"
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
+ "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
+ "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.1"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-04-29T12:36:36+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
+ "reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
+ "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.0-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.5.0"
+ },
+ "time": "2025-05-31T08:24:38+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": "phpstan/extension-installer",
+ "version": "1.4.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/extension-installer.git",
+ "reference": "85e90b3942d06b2326fba0403ec24fe912372936"
},
"dist": {
"type": "zip",
@@ -8483,117 +9059,1806 @@
"time": "2025-05-14T07:00:05+00:00"
},
{
- "name": "symfony/maker-bundle",
- "version": "v1.63.0",
+ "name": "phpunit/php-code-coverage",
+ "version": "12.3.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/maker-bundle.git",
- "reference": "69478ab39bc303abfbe3293006a78b09a8512425"
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "9075a8efc66e11bc55c319062e147bdb06777267"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/69478ab39bc303abfbe3293006a78b09a8512425",
- "reference": "69478ab39bc303abfbe3293006a78b09a8512425",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9075a8efc66e11bc55c319062e147bdb06777267",
+ "reference": "9075a8efc66e11bc55c319062e147bdb06777267",
"shasum": ""
},
"require": {
- "doctrine/inflector": "^2.0",
- "nikic/php-parser": "^5.0",
- "php": ">=8.1",
- "symfony/config": "^6.4|^7.0",
- "symfony/console": "^6.4|^7.0",
- "symfony/dependency-injection": "^6.4|^7.0",
- "symfony/deprecation-contracts": "^2.2|^3",
- "symfony/filesystem": "^6.4|^7.0",
- "symfony/finder": "^6.4|^7.0",
- "symfony/framework-bundle": "^6.4|^7.0",
- "symfony/http-kernel": "^6.4|^7.0",
- "symfony/process": "^6.4|^7.0"
- },
- "conflict": {
- "doctrine/doctrine-bundle": "<2.10",
- "doctrine/orm": "<2.15"
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^5.4.0",
+ "php": ">=8.3",
+ "phpunit/php-file-iterator": "^6.0",
+ "phpunit/php-text-template": "^5.0",
+ "sebastian/complexity": "^5.0",
+ "sebastian/environment": "^8.0",
+ "sebastian/lines-of-code": "^4.0",
+ "sebastian/version": "^6.0",
+ "theseer/tokenizer": "^1.2.3"
},
"require-dev": {
- "composer/semver": "^3.0",
- "doctrine/doctrine-bundle": "^2.5.0",
- "doctrine/orm": "^2.15|^3",
- "symfony/http-client": "^6.4|^7.0",
- "symfony/phpunit-bridge": "^6.4.1|^7.0",
- "symfony/security-core": "^6.4|^7.0",
- "symfony/yaml": "^6.4|^7.0",
- "twig/twig": "^3.0|^4.x-dev"
+ "phpunit/phpunit": "^12.1"
},
- "type": "symfony-bundle",
+ "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": "1.x-dev"
+ "dev-main": "12.3.x-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Bundle\\MakerBundle\\": "src/"
- }
+ "classmap": [
+ "src/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
}
],
- "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.",
- "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html",
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
- "code generator",
- "dev",
- "generator",
- "scaffold",
- "scaffolding"
+ "coverage",
+ "testing",
+ "xunit"
],
"support": {
- "issues": "https://github.com/symfony/maker-bundle/issues",
- "source": "https://github.com/symfony/maker-bundle/tree/v1.63.0"
+ "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/12.3.0"
},
"funding": [
{
- "url": "https://symfony.com/sponsor",
- "type": "custom"
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
},
{
- "url": "https://github.com/fabpot",
- "type": "github"
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
},
{
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
"type": "tidelift"
}
],
- "time": "2025-04-26T01:41:37+00:00"
+ "time": "2025-05-23T15:49:03+00:00"
},
{
- "name": "symfony/process",
- "version": "v7.2.5",
+ "name": "phpunit/php-file-iterator",
+ "version": "6.0.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/process.git",
- "reference": "87b7c93e57df9d8e39a093d32587702380ff045d"
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "961bc913d42fe24a257bfff826a5068079ac7782"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d",
- "reference": "87b7c93e57df9d8e39a093d32587702380ff045d",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782",
+ "reference": "961bc913d42fe24a257bfff826a5068079ac7782",
"shasum": ""
},
"require": {
- "php": ">=8.2"
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
"autoload": {
- "psr-4": {
+ "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/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:58:37+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406",
+ "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^12.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "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": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:58:58+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53",
+ "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.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",
+ "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/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:59:16+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc",
+ "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.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",
+ "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:59:38+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "12.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "f3f6587947f9f53f009af8d18026776375d59145"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3f6587947f9f53f009af8d18026776375d59145",
+ "reference": "f3f6587947f9f53f009af8d18026776375d59145",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.13.1",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=8.3",
+ "phpunit/php-code-coverage": "^12.3.0",
+ "phpunit/php-file-iterator": "^6.0.0",
+ "phpunit/php-invoker": "^6.0.0",
+ "phpunit/php-text-template": "^5.0.0",
+ "phpunit/php-timer": "^8.0.0",
+ "sebastian/cli-parser": "^4.0.0",
+ "sebastian/comparator": "^7.0.1",
+ "sebastian/diff": "^7.0.0",
+ "sebastian/environment": "^8.0.2",
+ "sebastian/exporter": "^7.0.0",
+ "sebastian/global-state": "^8.0.0",
+ "sebastian/object-enumerator": "^7.0.0",
+ "sebastian/type": "^6.0.2",
+ "sebastian/version": "^6.0.0",
+ "staabm/side-effects-detector": "^1.0.5"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "12.2-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/12.2.0"
+ },
+ "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": "2025-06-06T02:58:21+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/6d584c727d9114bcdc14c86711cd1cad51778e7c",
+ "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.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": "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/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:53:50+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "7.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "b478f34614f934e0291598d0c08cbaba9644bee5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b478f34614f934e0291598d0c08cbaba9644bee5",
+ "reference": "b478f34614f934e0291598d0c08cbaba9644bee5",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.3",
+ "sebastian/diff": "^7.0",
+ "sebastian/exporter": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "suggest": {
+ "ext-bcmath": "For comparing BcMath\\Number objects"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.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/7.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-03-07T07:00:32+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb",
+ "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.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",
+ "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/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:55:25+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "7ab1ea946c012266ca32390913653d844ecd085f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f",
+ "reference": "7ab1ea946c012266ca32390913653d844ecd085f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0",
+ "symfony/process": "^7.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.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",
+ "security": "https://github.com/sebastianbergmann/diff/security/policy",
+ "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:55:46+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "8.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "d364b9e5d0d3b18a2573351a1786fbf96b7e0792"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d364b9e5d0d3b18a2573351a1786fbf96b7e0792",
+ "reference": "d364b9e5d0d3b18a2573351a1786fbf96b7e0792",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.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 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/8.0.2"
+ },
+ "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/environment",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-05-21T15:05:44+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "76432aafc58d50691a00d86d0632f1217a47b688"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/76432aafc58d50691a00d86d0632f1217a47b688",
+ "reference": "76432aafc58d50691a00d86d0632f1217a47b688",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.3",
+ "sebastian/recursion-context": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.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",
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:56:42+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/570a2aeb26d40f057af686d63c4e99b075fb6cbc",
+ "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3",
+ "sebastian/object-reflector": "^5.0",
+ "sebastian/recursion-context": "^7.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "8.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/8.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:56:59+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f",
+ "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.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": "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/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:57:28+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894",
+ "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3",
+ "sebastian/object-reflector": "^5.0",
+ "sebastian/recursion-context": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.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",
+ "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:57:48+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "4bfa827c969c98be1e527abd576533293c634f6a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a",
+ "reference": "4bfa827c969c98be1e527abd576533293c634f6a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.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": "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",
+ "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T04:58:17+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "c405ae3a63e01b32eb71577f8ec1604e39858a7c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/c405ae3a63e01b32eb71577f8ec1604e39858a7c",
+ "reference": "c405ae3a63e01b32eb71577f8ec1604e39858a7c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.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/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T05:00:01+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "1d7cd6e514384c36d7a390347f57c385d4be6069"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/1d7cd6e514384c36d7a390347f57c385d4be6069",
+ "reference": "1d7cd6e514384c36d7a390347f57c385d4be6069",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^12.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": "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",
+ "security": "https://github.com/sebastianbergmann/type/security/policy",
+ "source": "https://github.com/sebastianbergmann/type/tree/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-03-18T13:37:31+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c",
+ "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.3"
+ },
+ "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": "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",
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-07T05:00:38+00:00"
+ },
+ {
+ "name": "staabm/side-effects-detector",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/staabm/side-effects-detector.git",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163",
+ "reference": "d8334211a140ce329c13726d4a715adbddd0a163",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.4 || ^8.0"
+ },
+ "require-dev": {
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^1.12.6",
+ "phpunit/phpunit": "^9.6.21",
+ "symfony/var-dumper": "^5.4.43",
+ "tomasvotruba/type-coverage": "1.0.0",
+ "tomasvotruba/unused-public": "1.0.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "lib/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A static analysis tool to detect side effects in PHP code",
+ "keywords": [
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/staabm/side-effects-detector/issues",
+ "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/staabm",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-20T05:08:20+00:00"
+ },
+ {
+ "name": "symfony/browser-kit",
+ "version": "v7.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/browser-kit.git",
+ "reference": "8ce0ee23857d87d5be493abba2d52d1f9e49da61"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/8ce0ee23857d87d5be493abba2d52d1f9e49da61",
+ "reference": "8ce0ee23857d87d5be493abba2d52d1f9e49da61",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/dom-crawler": "^6.4|^7.0"
+ },
+ "require-dev": {
+ "symfony/css-selector": "^6.4|^7.0",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/mime": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\BrowserKit\\": ""
+ },
+ "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": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/browser-kit/tree/v7.2.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": "2025-02-14T14:27:24+00:00"
+ },
+ {
+ "name": "symfony/css-selector",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/css-selector.git",
+ "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2",
+ "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\CssSelector\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Converts CSS selectors to XPath expressions",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/css-selector/tree/v7.2.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-25T14:21:43+00:00"
+ },
+ {
+ "name": "symfony/dom-crawler",
+ "version": "v7.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dom-crawler.git",
+ "reference": "19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7",
+ "reference": "19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7",
+ "shasum": ""
+ },
+ "require": {
+ "masterminds/html5": "^2.6",
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "require-dev": {
+ "symfony/css-selector": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DomCrawler\\": ""
+ },
+ "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 DOM navigation for HTML and XML documents",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/dom-crawler/tree/v7.2.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": "2025-02-17T15:53:07+00:00"
+ },
+ {
+ "name": "symfony/http-client",
+ "version": "v7.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client.git",
+ "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6",
+ "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6",
+ "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.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": "2025-02-13T10:27:23+00:00"
+ },
+ {
+ "name": "symfony/http-client-contracts",
+ "version": "v3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client-contracts.git",
+ "reference": "75d7043853a42837e68111812f4d964b01e5101c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c",
+ "reference": "75d7043853a42837e68111812f4d964b01e5101c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-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.6.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": "2025-04-29T11:18:49+00:00"
+ },
+ {
+ "name": "symfony/maker-bundle",
+ "version": "v1.63.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/maker-bundle.git",
+ "reference": "69478ab39bc303abfbe3293006a78b09a8512425"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/69478ab39bc303abfbe3293006a78b09a8512425",
+ "reference": "69478ab39bc303abfbe3293006a78b09a8512425",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/inflector": "^2.0",
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.1",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/console": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/deprecation-contracts": "^2.2|^3",
+ "symfony/filesystem": "^6.4|^7.0",
+ "symfony/finder": "^6.4|^7.0",
+ "symfony/framework-bundle": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0"
+ },
+ "conflict": {
+ "doctrine/doctrine-bundle": "<2.10",
+ "doctrine/orm": "<2.15"
+ },
+ "require-dev": {
+ "composer/semver": "^3.0",
+ "doctrine/doctrine-bundle": "^2.5.0",
+ "doctrine/orm": "^2.15|^3",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/phpunit-bridge": "^6.4.1|^7.0",
+ "symfony/security-core": "^6.4|^7.0",
+ "symfony/yaml": "^6.4|^7.0",
+ "twig/twig": "^3.0|^4.x-dev"
+ },
+ "type": "symfony-bundle",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Bundle\\MakerBundle\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.",
+ "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html",
+ "keywords": [
+ "code generator",
+ "dev",
+ "generator",
+ "scaffold",
+ "scaffolding"
+ ],
+ "support": {
+ "issues": "https://github.com/symfony/maker-bundle/issues",
+ "source": "https://github.com/symfony/maker-bundle/tree/v1.63.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": "2025-04-26T01:41:37+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v7.2.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "87b7c93e57df9d8e39a093d32587702380ff045d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d",
+ "reference": "87b7c93e57df9d8e39a093d32587702380ff045d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
"Symfony\\Component\\Process\\": ""
},
"exclude-from-classmap": [
@@ -8717,6 +10982,56 @@
],
"time": "2025-02-14T14:27:24+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": "zenstruck/assert",
"version": "v1.5.1",
diff --git a/config/bundles.php b/config/bundles.php
index f4ff40d..139a0e0 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -13,4 +13,6 @@
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true],
+ Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
+ DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
];
diff --git a/config/packages/dev/routing.yaml b/config/packages/dev/routing.yaml
new file mode 100644
index 0000000..999d5dc
--- /dev/null
+++ b/config/packages/dev/routing.yaml
@@ -0,0 +1,3 @@
+framework:
+ router:
+ default_uri: 'http://api.esv.localhost'
diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml
new file mode 100644
index 0000000..3733059
--- /dev/null
+++ b/config/packages/monolog.yaml
@@ -0,0 +1,54 @@
+monolog:
+ channels:
+ - deprecation
+
+when@dev:
+ monolog:
+ handlers:
+ main:
+ type: stream
+ path: "%kernel.logs_dir%/%kernel.environment%.log"
+ level: debug
+ channels: ["!event"]
+ console:
+ type: console
+ process_psr_3_messages: false
+ channels: ["!event", "!doctrine", "!console"]
+
+when@test:
+ monolog:
+ handlers:
+ main:
+ type: fingers_crossed
+ action_level: error
+ handler: nested
+ excluded_http_codes: [404, 405]
+ channels: ["!event"]
+ nested:
+ type: stream
+ path: "%kernel.logs_dir%/%kernel.environment%.log"
+ level: debug
+
+when@prod:
+ monolog:
+ handlers:
+ main:
+ type: fingers_crossed
+ action_level: error
+ handler: nested
+ excluded_http_codes: [404, 405]
+ buffer_size: 50
+ nested:
+ type: stream
+ path: php://stderr
+ level: debug
+ formatter: monolog.formatter.json
+ console:
+ type: console
+ process_psr_3_messages: false
+ channels: ["!event", "!doctrine"]
+ deprecation:
+ type: stream
+ channels: [deprecation]
+ path: php://stderr
+ formatter: monolog.formatter.json
diff --git a/config/packages/prod/routing.yaml b/config/packages/prod/routing.yaml
new file mode 100644
index 0000000..b3e6a0a
--- /dev/null
+++ b/config/packages/prod/routing.yaml
@@ -0,0 +1,3 @@
+framework:
+ router:
+ strict_requirements: null
diff --git a/config/packages/routing.yaml b/config/packages/routing.yaml
index 9437fff..0ab0b53 100644
--- a/config/packages/routing.yaml
+++ b/config/packages/routing.yaml
@@ -1,8 +1,3 @@
framework:
router:
default_uri: 'https://www.esports-videos.com/'
-
-when@prod:
- framework:
- router:
- strict_requirements: null
diff --git a/config/packages/test/dama_doctrine_test_bundle.yaml b/config/packages/test/dama_doctrine_test_bundle.yaml
new file mode 100644
index 0000000..80b0091
--- /dev/null
+++ b/config/packages/test/dama_doctrine_test_bundle.yaml
@@ -0,0 +1,4 @@
+dama_doctrine_test:
+ enable_static_connection: true
+ enable_static_meta_data_cache: true
+ enable_static_query_cache: true
diff --git a/config/packages/test/routing.yaml b/config/packages/test/routing.yaml
new file mode 100644
index 0000000..c0af4bd
--- /dev/null
+++ b/config/packages/test/routing.yaml
@@ -0,0 +1,3 @@
+framework:
+ router:
+ default_uri: 'http://localhost'
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 1da776e..b00c474 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -21,6 +21,7 @@ COPY --chown=www-data:www-data config config/
COPY --chown=www-data:www-data public public/
COPY --chown=www-data:www-data migrations migrations/
COPY --chown=www-data:www-data src src/
+COPY --chown=www-data:www-data templates templates/
RUN set -eux; \
mkdir -p var/cache var/log; \
diff --git a/fixtures/Data/UserFixtures.php b/fixtures/Data/UserFixtures.php
index a317a2c..b5d7f46 100644
--- a/fixtures/Data/UserFixtures.php
+++ b/fixtures/Data/UserFixtures.php
@@ -8,6 +8,7 @@
class UserFixtures extends AbstractFixture
{
+ public const string DEFAULT_PASSWORD = 'esvESV123456';
public const string ADMIN_ULID = '019712ef-fb77-347f-1cd9-2b1c22d259e6';
public const string USER_ULID = '019712ef-fb78-602c-a546-f7c694bd83be';
public const string UNVERIFIED_USER_ULID = '01973bad-1197-b299-2e4d-349108befb29';
diff --git a/fixtures/Factory/UserFactory.php b/fixtures/Factory/UserFactory.php
index e7b8bc0..75187e3 100644
--- a/fixtures/Factory/UserFactory.php
+++ b/fixtures/Factory/UserFactory.php
@@ -10,8 +10,6 @@
*/
final class UserFactory extends PersistentProxyObjectFactory
{
- public const string DEFAULT_PASSWORD = 'esv';
-
public static function class(): string
{
return User::class;
diff --git a/phpstan.neon b/phpstan.neon
index 618f267..997488d 100755
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -5,6 +5,7 @@ parameters:
- config/
- src/
- fixtures/
+ - tests/
symfony:
containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
scanDirectories:
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..5e37635
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tests/Smoke
+
+
+ tests/Functional
+
+
+
+
+
+ src
+
+
+
+ Doctrine\Deprecations\Deprecation::trigger
+ Doctrine\Deprecations\Deprecation::delegateTriggerToBackend
+ trigger_deprecation
+
+
+
+
+
+
+
diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php
deleted file mode 100644
index 28ae215..0000000
--- a/src/Controller/HomeController.php
+++ /dev/null
@@ -1,16 +0,0 @@
- 'Hello there']);
- }
-}
diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php
index f46ffa2..f16e9c9 100644
--- a/src/Controller/RegistrationController.php
+++ b/src/Controller/RegistrationController.php
@@ -8,7 +8,8 @@
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\UriSigner;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+use Symfony\Component\HttpKernel\Exception\GoneHttpException;
use Symfony\Component\Routing\Attribute\Route;
class RegistrationController extends AbstractController
@@ -16,8 +17,12 @@ class RegistrationController extends AbstractController
#[Route('/users/{id}/verify/email', name: 'verify_email', methods: ['GET'])]
public function index(Request $request, User $user, UriSigner $uriSigner, EntityManagerInterface $entityManager): JsonResponse
{
- if (!$uriSigner->checkRequest($request) || $user->isEmailVerified()) {
- throw new NotFoundHttpException();
+ if ($user->isEmailVerified()) {
+ throw new GoneHttpException();
+ }
+
+ if (!$uriSigner->checkRequest($request)) {
+ throw new BadRequestHttpException();
}
$user->setEmailVerified(true);
diff --git a/src/Dto/UserRegistrationDto.php b/src/Dto/UserRegistrationDto.php
index 4e4304e..fc79611 100644
--- a/src/Dto/UserRegistrationDto.php
+++ b/src/Dto/UserRegistrationDto.php
@@ -21,7 +21,9 @@ class UserRegistrationDto
public mixed $username = null;
#[Assert\NotBlank(allowNull: false)]
- // #[Assert\PasswordStrength]
+ #[Assert\PasswordStrength(
+ minScore: Assert\PasswordStrength::STRENGTH_WEAK,
+ )]
#[Groups('user:write')]
public mixed $password = null;
diff --git a/src/Entity/User.php b/src/Entity/User.php
index c29846f..1556a8f 100644
--- a/src/Entity/User.php
+++ b/src/Entity/User.php
@@ -17,9 +17,7 @@
#[ApiResource(
operations: [
- new Get(
- security: "is_granted('ROLE_ADMIN') or object == user"
- ),
+ new Get(),
new GetCollection(
security: "is_granted('ROLE_ADMIN')"
),
@@ -40,7 +38,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
{
use EntityIdTrait;
- #[Groups(['user:read'])]
+ #[Groups(['admin:user:read'])]
#[ORM\Column(length: 255, unique: true)]
private string $email;
diff --git a/src/Service/Registration/GenerateSignedUriService.php b/src/Service/Registration/GenerateSignedUriService.php
index 086107e..4be98ab 100644
--- a/src/Service/Registration/GenerateSignedUriService.php
+++ b/src/Service/Registration/GenerateSignedUriService.php
@@ -3,13 +3,17 @@
namespace App\Service\Registration;
use App\Entity\User;
+use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\HttpFoundation\UriSigner;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
final readonly class GenerateSignedUriService
{
- public function __construct(private UriSigner $uriSigner, private UrlGeneratorInterface $urlGenerator)
- {
+ public function __construct(
+ private UriSigner $uriSigner,
+ private UrlGeneratorInterface $urlGenerator,
+ private ClockInterface $clock,
+ ) {
}
public function generateSignedUri(User $user): string
@@ -20,7 +24,7 @@ public function generateSignedUri(User $user): string
['id' => $user->getId()],
UrlGeneratorInterface::ABSOLUTE_URL
),
- new \DateTimeImmutable('+1 day')
+ $this->clock->now()->modify('+1 day')
);
}
}
diff --git a/symfony.lock b/symfony.lock
index b117627..bda41b6 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -13,6 +13,15 @@
"src/ApiResource/.gitignore"
]
},
+ "dama/doctrine-test-bundle": {
+ "version": "8.3",
+ "recipe": {
+ "repo": "github.com/symfony/recipes-contrib",
+ "branch": "main",
+ "version": "8.3",
+ "ref": "dfc51177476fb39d014ed89944cde53dc3326d23"
+ }
+ },
"doctrine/deprecations": {
"version": "1.1",
"recipe": {
@@ -97,6 +106,21 @@
"phpstan.dist.neon"
]
},
+ "phpunit/phpunit": {
+ "version": "12.2",
+ "recipe": {
+ "repo": "github.com/symfony/recipes",
+ "branch": "main",
+ "version": "11.1",
+ "ref": "c6658a60fc9d594805370eacdf542c3d6b5c0869"
+ },
+ "files": [
+ ".env.test",
+ "phpunit.dist.xml",
+ "tests/bootstrap.php",
+ "bin/phpunit"
+ ]
+ },
"symfony/console": {
"version": "7.2",
"recipe": {
@@ -162,6 +186,18 @@
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
}
},
+ "symfony/monolog-bundle": {
+ "version": "3.10",
+ "recipe": {
+ "repo": "github.com/symfony/recipes",
+ "branch": "main",
+ "version": "3.7",
+ "ref": "aff23899c4440dd995907613c1dd709b6f59503f"
+ },
+ "files": [
+ "config/packages/monolog.yaml"
+ ]
+ },
"symfony/routing": {
"version": "7.2",
"recipe": {
diff --git a/tests/Functional/ApiTestCase.php b/tests/Functional/ApiTestCase.php
new file mode 100644
index 0000000..add1d42
--- /dev/null
+++ b/tests/Functional/ApiTestCase.php
@@ -0,0 +1,35 @@
+client = self::createClient([], ['headers' => ['Accept' => 'application/ld+json']]);
+ }
+
+ public function authenticateTestClientAs(string $id): Client
+ {
+ $user = static::getContainer()->get('doctrine')->getRepository(User::class)->findOneById($id);
+
+ if (!$user instanceof User) {
+ throw new \RuntimeException('User not found');
+ }
+
+ $tokenManager = static::getContainer()->get(JWTTokenManagerInterface::class);
+ $token = $tokenManager->create($user);
+
+ $this->client->setDefaultOptions(['headers' => ['Authorization' => 'Bearer '.$token]]);
+
+ return $this->client;
+ }
+}
diff --git a/tests/Functional/User/RegistrationTest.php b/tests/Functional/User/RegistrationTest.php
new file mode 100644
index 0000000..ca20c1f
--- /dev/null
+++ b/tests/Functional/User/RegistrationTest.php
@@ -0,0 +1,253 @@
+register([
+ 'email' => 'new_account+alias@esports-videos.com',
+ 'username' => 'new_account',
+ 'password' => UserFixtures::DEFAULT_PASSWORD,
+ ]);
+
+ self::assertResponseStatusCodeSame(Response::HTTP_CREATED);
+ self::assertEmailCount(1);
+ }
+
+ /**
+ * @param array $violations
+ */
+ #[Test]
+ #[DataProvider('invalidData')]
+ public function asAnonymousICannotRegisterWithInvalidData(?string $email, ?string $password, ?string $username, ?string $countryCode, array $violations = []): void
+ {
+ $data = array_filter([
+ 'email' => $email,
+ 'password' => $password,
+ 'username' => $username,
+ 'country' => $countryCode,
+ ], static fn ($value) => null !== $value);
+
+ $this->register($data);
+
+ self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
+ self::assertResponseHeaderSame('content-type', 'application/problem+json; charset=utf-8');
+ self::assertJsonContains([
+ '@type' => 'ConstraintViolation',
+ 'title' => 'An error occurred',
+ 'status' => 422,
+ 'violations' => $violations,
+ ]);
+ self::assertEmailCount(0);
+ }
+
+ #[Test]
+ public function asAnonymousICanVerifyMyAccount(): void
+ {
+ $container = static::getContainer();
+ $generateSignedUriService = $container->get(GenerateSignedUriService::class);
+ $user = UserFactory::find(UserFixtures::UNVERIFIED_USER_ULID);
+
+ $this->client->request('GET', $generateSignedUriService->generateSignedUri($user));
+
+ self::assertResponseStatusCodeSame(Response::HTTP_OK);
+ }
+
+ #[Test]
+ public function asAnonymousICantVerifyWithExpiredLink(): void
+ {
+ $container = static::getContainer();
+ $clock = new MockClock(new \DateTimeImmutable('-48 hours'));
+
+ $generateSignedUriService = new GenerateSignedUriService(
+ $container->get(UriSigner::class),
+ $container->get(UrlGeneratorInterface::class),
+ $clock
+ );
+ $user = UserFactory::find(UserFixtures::UNVERIFIED_USER_ULID);
+
+ $signedUri = $generateSignedUriService->generateSignedUri($user);
+
+ $this->client->request('GET', $signedUri);
+ self::assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
+ }
+
+ #[Test]
+ public function asAnonymousICantVerifyWithSameLinkTwice(): void
+ {
+ $container = static::getContainer();
+ /** @var GenerateSignedUriService $generateSignedUriService */
+ $generateSignedUriService = $container->get(GenerateSignedUriService::class);
+ $user = UserFactory::find(UserFixtures::UNVERIFIED_USER_ULID);
+
+ $signedUri = $generateSignedUriService->generateSignedUri($user);
+
+ $this->client->request('GET', $signedUri);
+ self::assertResponseStatusCodeSame(Response::HTTP_OK);
+
+ $this->client->request('GET', $signedUri);
+ self::assertResponseStatusCodeSame(Response::HTTP_GONE);
+ }
+
+ #[Test]
+ public function asAnonymousICantVerifyAnAccountAlreadyValidated(): void
+ {
+ $container = static::getContainer();
+ $generateSignedUriService = $container->get(GenerateSignedUriService::class);
+ $user = UserFactory::find(UserFixtures::ADMIN_ULID);
+
+ $this->client->request('GET', $generateSignedUriService->generateSignedUri($user));
+
+ self::assertResponseStatusCodeSame(Response::HTTP_GONE);
+ }
+
+ #[Test]
+ public function asAnonymousICantVerifyAnAccountWithInvalidHash(): void
+ {
+ $this->client->request('GET', sprintf('/users/%s/verify/email?_hash=invalid', UserFixtures::UNVERIFIED_USER_ULID));
+
+ self::assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
+ }
+
+ #[Test]
+ public function asAnonymousICannotRegisterWithInvalidMimeType(): void
+ {
+ $this->client->request('POST', '/users', [
+ 'headers' => [
+ 'Content-Type' => 'text/plain',
+ ],
+ 'body' => '{"email":"foo@example.com"}',
+ ]);
+
+ self::assertResponseStatusCodeSame(Response::HTTP_UNSUPPORTED_MEDIA_TYPE);
+ }
+
+ public static function invalidData(): \Generator
+ {
+ yield 'empty_values' => [
+ 'email' => null,
+ 'password' => null,
+ 'countryCode' => null,
+ 'username' => null,
+ 'violations' => [
+ [
+ 'propertyPath' => 'email',
+ 'message' => 'This value should not be blank.',
+ ],
+ [
+ 'propertyPath' => 'username',
+ 'message' => 'This value should not be blank.',
+ ],
+ [
+ 'propertyPath' => 'password',
+ 'message' => 'This value should not be blank.',
+ ],
+ ],
+ ];
+
+ yield 'omitted_email' => [
+ 'email' => null,
+ 'password' => UserFixtures::DEFAULT_PASSWORD,
+ 'countryCode' => 'FR',
+ 'username' => 'correct_username',
+ 'violations' => [
+ [
+ 'propertyPath' => 'email',
+ 'message' => 'This value should not be blank.',
+ ],
+ ],
+ ];
+
+ yield 'omitted_password' => [
+ 'email' => 'correct_email@esports-videos.com',
+ 'password' => null,
+ 'countryCode' => 'FR',
+ 'username' => 'correct_username',
+ 'violations' => [
+ [
+ 'propertyPath' => 'password',
+ 'message' => 'This value should not be blank.',
+ ],
+ ],
+ ];
+
+ yield 'invalid_email' => [
+ 'email' => 'invalidEmail',
+ 'password' => UserFixtures::DEFAULT_PASSWORD,
+ 'countryCode' => 'FR',
+ 'username' => 'correct_username',
+ 'violations' => [
+ [
+ 'propertyPath' => 'email',
+ 'message' => 'This value is not a valid email address.',
+ ],
+ ],
+ ];
+
+ yield 'invalid_country_code' => [
+ 'email' => 'correct_email@esports-videos.com',
+ 'password' => UserFixtures::DEFAULT_PASSWORD,
+ 'countryCode' => 'BAD',
+ 'username' => 'correct_username',
+ 'violations' => [
+ [
+ 'propertyPath' => 'country',
+ 'message' => 'This value is not a valid country.',
+ ],
+ ],
+ ];
+
+ yield 'invalid_password' => [
+ 'email' => 'correct_email@esports-videos.com',
+ 'password' => 'badPassword',
+ 'countryCode' => 'FR',
+ 'username' => 'correct_username',
+ 'violations' => [
+ [
+ 'propertyPath' => 'password',
+ 'message' => 'The password strength is too low. Please use a stronger password.',
+ ],
+ ],
+ ];
+
+ yield 'email_already_exists' => [
+ 'email' => 'admin@esports-videos.com',
+ 'password' => UserFixtures::DEFAULT_PASSWORD,
+ 'countryCode' => 'FR',
+ 'username' => 'correct_username',
+ 'violations' => [
+ [
+ 'propertyPath' => 'email',
+ 'message' => 'This value is already used.',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @param array $data
+ */
+ private function register(array $data): void
+ {
+ $this->client->request('POST', '/users', [
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ ],
+ 'body' => json_encode($data, JSON_THROW_ON_ERROR),
+ ]);
+ }
+}
diff --git a/tests/Functional/User/UserTest.php b/tests/Functional/User/UserTest.php
new file mode 100644
index 0000000..bccc77a
--- /dev/null
+++ b/tests/Functional/User/UserTest.php
@@ -0,0 +1,119 @@
+authenticateTestClientAs($userUlid)
+ : $this->client;
+
+ $client->request('GET', '/users');
+
+ if (Response::HTTP_OK === $expectedStatusCode) {
+ self::assertResponseIsSuccessful();
+ self::assertResponseHeaderSame('Content-Type', 'application/ld+json; charset=utf-8');
+ } else {
+ self::assertResponseStatusCodeSame($expectedStatusCode);
+ }
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function getUserCollectionAccessCases(): iterable
+ {
+ yield 'anonymous cannot access' => [
+ 'userUlid' => null,
+ 'expectedStatusCode' => Response::HTTP_UNAUTHORIZED,
+ ];
+
+ yield 'user cannot access' => [
+ 'userUlid' => UserFixtures::USER_ULID,
+ 'expectedStatusCode' => Response::HTTP_FORBIDDEN,
+ ];
+
+ yield 'admin can access' => [
+ 'userUlid' => UserFixtures::ADMIN_ULID,
+ 'expectedStatusCode' => Response::HTTP_OK,
+ ];
+ }
+
+ /**
+ * @param array $expectedData
+ * @param array $forbiddenFields
+ */
+ #[Test]
+ #[DataProvider('getUserAccessCases')]
+ public function iCanGetAUser(?string $userUlid, array $expectedData = [], array $forbiddenFields = []): void
+ {
+ $client = null !== $userUlid
+ ? $this->authenticateTestClientAs($userUlid)
+ : $this->client;
+
+ $response = $client->request('GET', sprintf('/users/%s', UserFixtures::ADMIN_ULID));
+
+ self::assertResponseIsSuccessful();
+ self::assertResponseHeaderSame('Content-Type', 'application/ld+json; charset=utf-8');
+
+ $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
+
+ foreach ($forbiddenFields as $field) {
+ self::assertArrayNotHasKey($field, $data);
+ }
+
+ self::assertJsonContains($expectedData);
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function getUserAccessCases(): iterable
+ {
+ yield 'anonymous' => [
+ 'userUlid' => null,
+ 'expectedData' => [
+ '@context' => '/contexts/User',
+ '@type' => 'User',
+ 'username' => 'admin',
+ 'country' => 'FR',
+ ],
+ 'forbiddenFields' => ['email', 'password'],
+ ];
+
+ yield 'user' => [
+ 'userUlid' => UserFixtures::USER_ULID,
+ 'expectedData' => [
+ '@context' => '/contexts/User',
+ '@type' => 'User',
+ 'username' => 'admin',
+ 'country' => 'FR',
+ ],
+ 'forbiddenFields' => ['email', 'password'],
+ ];
+
+ yield 'admin' => [
+ 'userUlid' => UserFixtures::ADMIN_ULID,
+ 'expectedData' => [
+ '@context' => '/contexts/User',
+ '@type' => 'User',
+ 'username' => 'admin',
+ 'country' => 'FR',
+ 'email' => 'admin@esports-videos.com',
+ ],
+ 'forbiddenFields' => ['password'],
+ ];
+ }
+}
diff --git a/tests/Smoke/ApplicationAvailabilityFunctionalTest.php b/tests/Smoke/ApplicationAvailabilityFunctionalTest.php
new file mode 100644
index 0000000..164b85b
--- /dev/null
+++ b/tests/Smoke/ApplicationAvailabilityFunctionalTest.php
@@ -0,0 +1,28 @@
+request('GET', $url);
+
+ self::assertNotSame(500, $client->getResponse()->getStatusCode());
+ }
+
+ public static function urlProvider(): \Generator
+ {
+ yield ['/users'];
+ yield [sprintf('/users/%s', UserFixtures::USER_ULID)];
+ yield ['/videos'];
+ yield [sprintf('/videos/%s', VideoFixtures::COMPLETE_ULID)];
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..27d6ef2
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,13 @@
+bootEnv(dirname(__DIR__).'/.env');
+}
+
+if (true === $_SERVER['APP_DEBUG']) {
+ umask(0000);
+}