From ab29fb363054b74aa57d8352f7b468f0363a5436 Mon Sep 17 00:00:00 2001 From: lucianoarm Date: Mon, 11 Aug 2025 20:41:32 -0300 Subject: [PATCH 1/8] =?UTF-8?q?Inicializa=C3=A7=C3=A3o=20do=20framework=20?= =?UTF-8?q?symfony?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-api/.editorconfig | 17 + test-api/.gitignore | 12 + test-api/README.md | 20 + test-api/bin/console | 21 + test-api/compose.override.yaml | 7 + test-api/compose.yaml | 25 + test-api/composer.json | 74 + test-api/composer.lock | 4099 +++++++++++++++++ test-api/config/bundles.php | 8 + test-api/config/packages/cache.yaml | 19 + test-api/config/packages/doctrine.yaml | 54 + .../config/packages/doctrine_migrations.yaml | 6 + test-api/config/packages/framework.yaml | 15 + test-api/config/packages/routing.yaml | 10 + test-api/config/preload.php | 5 + test-api/config/routes.yaml | 5 + test-api/config/routes/framework.yaml | 4 + test-api/config/services.yaml | 20 + test-api/migrations/.gitignore | 0 test-api/migrations/Version20250811222556.php | 35 + test-api/public/index.php | 9 + test-api/src/Controller/.gitignore | 0 test-api/src/Entity/.gitignore | 0 test-api/src/Entity/Investment.php | 80 + test-api/src/Entity/Owner.php | 28 + test-api/src/Kernel.php | 11 + test-api/src/Repository/.gitignore | 0 test-api/symfony.lock | 105 + 28 files changed, 4689 insertions(+) create mode 100644 test-api/.editorconfig create mode 100644 test-api/.gitignore create mode 100644 test-api/README.md create mode 100755 test-api/bin/console create mode 100644 test-api/compose.override.yaml create mode 100644 test-api/compose.yaml create mode 100644 test-api/composer.json create mode 100644 test-api/composer.lock create mode 100644 test-api/config/bundles.php create mode 100644 test-api/config/packages/cache.yaml create mode 100644 test-api/config/packages/doctrine.yaml create mode 100644 test-api/config/packages/doctrine_migrations.yaml create mode 100644 test-api/config/packages/framework.yaml create mode 100644 test-api/config/packages/routing.yaml create mode 100644 test-api/config/preload.php create mode 100644 test-api/config/routes.yaml create mode 100644 test-api/config/routes/framework.yaml create mode 100644 test-api/config/services.yaml create mode 100644 test-api/migrations/.gitignore create mode 100644 test-api/migrations/Version20250811222556.php create mode 100644 test-api/public/index.php create mode 100644 test-api/src/Controller/.gitignore create mode 100644 test-api/src/Entity/.gitignore create mode 100644 test-api/src/Entity/Investment.php create mode 100644 test-api/src/Entity/Owner.php create mode 100644 test-api/src/Kernel.php create mode 100644 test-api/src/Repository/.gitignore create mode 100644 test-api/symfony.lock diff --git a/test-api/.editorconfig b/test-api/.editorconfig new file mode 100644 index 000000000..66990769e --- /dev/null +++ b/test-api/.editorconfig @@ -0,0 +1,17 @@ +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[{compose.yaml,compose.*.yaml}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/test-api/.gitignore b/test-api/.gitignore new file mode 100644 index 000000000..05b866e84 --- /dev/null +++ b/test-api/.gitignore @@ -0,0 +1,12 @@ + +###> symfony/framework-bundle ### +/.env +/.env.dev +/.env.local +/.env.local.php +/.env.*.local +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ +/vendor/ +###< symfony/framework-bundle ### \ No newline at end of file diff --git a/test-api/README.md b/test-api/README.md new file mode 100644 index 000000000..ef08e2616 --- /dev/null +++ b/test-api/README.md @@ -0,0 +1,20 @@ +# SCRIPT SQL para iniciar o Banco de Dados da API +-- Remove o banco se já existir +DROP DATABASE IF EXISTS `db_test_api`; + +-- Cria o banco +CREATE DATABASE `db_test_api` + DEFAULT CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; + +-- Remove o usuário (se já existir, remove também privilégios) +DROP USER IF EXISTS 'usuario_api'@'localhost'; + +-- Cria o usuário novamente (host = localhost) +CREATE USER 'usuario_api'@'localhost' IDENTIFIED BY '.!usuarioApi2025!.'; + +-- Concede privilégios completos no banco criado +GRANT ALL PRIVILEGES ON `db_test_api`.* TO 'usuario_api'@'localhost'; + +-- Aplica mudanças +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/test-api/bin/console b/test-api/bin/console new file mode 100755 index 000000000..d8d530e2c --- /dev/null +++ b/test-api/bin/console @@ -0,0 +1,21 @@ +#!/usr/bin/env php + doctrine/doctrine-bundle ### + database: + ports: + - "5432" +###< doctrine/doctrine-bundle ### diff --git a/test-api/compose.yaml b/test-api/compose.yaml new file mode 100644 index 000000000..89c74d180 --- /dev/null +++ b/test-api/compose.yaml @@ -0,0 +1,25 @@ + +services: +###> doctrine/doctrine-bundle ### + database: + image: postgres:${POSTGRES_VERSION:-16}-alpine + environment: + POSTGRES_DB: ${POSTGRES_DB:-app} + # You should definitely change the password in production + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} + POSTGRES_USER: ${POSTGRES_USER:-app} + healthcheck: + test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"] + timeout: 5s + retries: 5 + start_period: 60s + volumes: + - database_data:/var/lib/postgresql/data:rw + # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! + # - ./docker/db/data:/var/lib/postgresql/data:rw +###< doctrine/doctrine-bundle ### + +volumes: +###> doctrine/doctrine-bundle ### + database_data: +###< doctrine/doctrine-bundle ### diff --git a/test-api/composer.json b/test-api/composer.json new file mode 100644 index 000000000..77ad2dbc6 --- /dev/null +++ b/test-api/composer.json @@ -0,0 +1,74 @@ +{ + "type": "project", + "license": "proprietary", + "minimum-stability": "stable", + "prefer-stable": true, + "require": { + "php": ">=8.2", + "ext-ctype": "*", + "ext-iconv": "*", + "doctrine/dbal": "^3", + "doctrine/doctrine-bundle": "^2.15", + "doctrine/doctrine-migrations-bundle": "^3.4", + "doctrine/orm": "^3.5", + "symfony/console": "7.3.*", + "symfony/dotenv": "7.3.*", + "symfony/flex": "^2", + "symfony/framework-bundle": "7.3.*", + "symfony/runtime": "7.3.*", + "symfony/yaml": "7.3.*" + }, + "config": { + "allow-plugins": { + "php-http/discovery": true, + "symfony/flex": true, + "symfony/runtime": true + }, + "bump-after-update": true, + "sort-packages": true + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/" + } + }, + "replace": { + "symfony/polyfill-ctype": "*", + "symfony/polyfill-iconv": "*", + "symfony/polyfill-php72": "*", + "symfony/polyfill-php73": "*", + "symfony/polyfill-php74": "*", + "symfony/polyfill-php80": "*", + "symfony/polyfill-php81": "*", + "symfony/polyfill-php82": "*" + }, + "scripts": { + "auto-scripts": { + "cache:clear": "symfony-cmd", + "assets:install %PUBLIC_DIR%": "symfony-cmd" + }, + "post-install-cmd": [ + "@auto-scripts" + ], + "post-update-cmd": [ + "@auto-scripts" + ] + }, + "conflict": { + "symfony/symfony": "*" + }, + "extra": { + "symfony": { + "allow-contrib": false, + "require": "7.3.*" + } + }, + "require-dev": { + "symfony/maker-bundle": "^1.64" + } +} diff --git a/test-api/composer.lock b/test-api/composer.lock new file mode 100644 index 000000000..8f33ec184 --- /dev/null +++ b/test-api/composer.lock @@ -0,0 +1,4099 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "5daab07dd6e49e34bc9e92813dc447d4", + "packages": [ + { + "name": "doctrine/collections", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/2eb07e5953eed811ce1b309a7478a3b236f2273d", + "reference": "2eb07e5953eed811ce1b309a7478a3b236f2273d", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1", + "php": "^8.1", + "symfony/polyfill-php84": "^1.30" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "ext-json": "*", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "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": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/2.3.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcollections", + "type": "tidelift" + } + ], + "time": "2025-03-22T10:17:19+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.10.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "3626601014388095d3af9de7e9e958623b7ef005" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/3626601014388095d3af9de7e9e958623b7ef005", + "reference": "3626601014388095d3af9de7e9e958623b7ef005", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/deprecations": "^0.5.3|^1", + "doctrine/event-manager": "^1|^2", + "php": "^7.4 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "conflict": { + "doctrine/cache": "< 1.11" + }, + "require-dev": { + "doctrine/cache": "^1.11|^2.0", + "doctrine/coding-standard": "13.0.0", + "fig/log-test": "^1", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "2.1.17", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "9.6.23", + "slevomat/coding-standard": "8.16.2", + "squizlabs/php_codesniffer": "3.13.1", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/console": "^4.4|^5.4|^6.0|^7.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "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": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.10.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%2Fdbal", + "type": "tidelift" + } + ], + "time": "2025-08-05T12:18:06+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + }, + "time": "2025-04-07T20:06:18+00:00" + }, + { + "name": "doctrine/doctrine-bundle", + "version": "2.15.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineBundle.git", + "reference": "5a305c5e776f9d3eb87f5b94d40d50aff439211d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/5a305c5e776f9d3eb87f5b94d40d50aff439211d", + "reference": "5a305c5e776f9d3eb87f5b94d40d50aff439211d", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^3.7.0 || ^4.0", + "doctrine/persistence": "^3.1 || ^4", + "doctrine/sql-formatter": "^1.0.1", + "php": "^8.1", + "symfony/cache": "^6.4 || ^7.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/console": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/service-contracts": "^2.5 || ^3" + }, + "conflict": { + "doctrine/annotations": ">=3.0", + "doctrine/cache": "< 1.11", + "doctrine/orm": "<2.17 || >=4.0", + "symfony/var-exporter": "< 6.4.1 || 7.0.0", + "twig/twig": "<2.13 || >=3.0 <3.0.4" + }, + "require-dev": { + "doctrine/annotations": "^1 || ^2", + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^13", + "doctrine/deprecations": "^1.0", + "doctrine/orm": "^2.17 || ^3.1", + "friendsofphp/proxy-manager-lts": "^1.0", + "phpstan/phpstan": "2.1.1", + "phpstan/phpstan-phpunit": "2.0.3", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9.6.22", + "psr/log": "^1.1.4 || ^2.0 || ^3.0", + "symfony/doctrine-messenger": "^6.4 || ^7.0", + "symfony/messenger": "^6.4 || ^7.0", + "symfony/phpunit-bridge": "^7.2", + "symfony/property-info": "^6.4 || ^7.0", + "symfony/security-bundle": "^6.4 || ^7.0", + "symfony/stopwatch": "^6.4 || ^7.0", + "symfony/string": "^6.4 || ^7.0", + "symfony/twig-bridge": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0", + "symfony/var-exporter": "^6.4.1 || ^7.0.1", + "symfony/web-profiler-bundle": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0", + "twig/twig": "^2.13 || ^3.0.4" + }, + "suggest": { + "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", + "ext-pdo": "*", + "symfony/web-profiler-bundle": "To use the data collector." + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org/" + } + ], + "description": "Symfony DoctrineBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineBundle/issues", + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.15.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%2Fdoctrine-bundle", + "type": "tidelift" + } + ], + "time": "2025-07-30T15:48:28+00:00" + }, + { + "name": "doctrine/doctrine-migrations-bundle", + "version": "3.4.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", + "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/5a6ac7120c2924c4c070a869d08b11ccf9e277b9", + "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9", + "shasum": "" + }, + "require": { + "doctrine/doctrine-bundle": "^2.4", + "doctrine/migrations": "^3.2", + "php": "^7.2 || ^8.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "composer/semver": "^3.0", + "doctrine/coding-standard": "^12", + "doctrine/orm": "^2.6 || ^3", + "phpstan/phpstan": "^1.4 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpstan/phpstan-symfony": "^1.3 || ^2", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/phpunit-bridge": "^6.3 || ^7", + "symfony/var-exporter": "^5.4 || ^6 || ^7" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\MigrationsBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DoctrineMigrationsBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "dbal", + "migrations", + "schema" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.4.2" + }, + "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%2Fdoctrine-migrations-bundle", + "type": "tidelift" + } + ], + "time": "2025-03-11T17:36:26+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "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": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/2.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%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2024-05-22T20:47:39+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "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": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:23:10+00:00" + }, + { + "name": "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": "doctrine/migrations", + "version": "3.9.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "fa94c6f06b1bc6d4759481ec20b8b81d13e861be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/fa94c6f06b1bc6d4759481ec20b8b81d13e861be", + "reference": "fa94c6f06b1bc6d4759481ec20b8b81d13e861be", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/dbal": "^3.6 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2.0", + "php": "^8.1", + "psr/log": "^1.1.3 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.2 || ^7.0" + }, + "conflict": { + "doctrine/orm": "<2.12 || >=4" + }, + "require-dev": { + "doctrine/coding-standard": "^13", + "doctrine/orm": "^2.13 || ^3", + "doctrine/persistence": "^2 || ^3 || ^4", + "doctrine/sql-formatter": "^1.0", + "ext-pdo_sqlite": "*", + "fig/log-test": "^1", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpstan/phpstan-symfony": "^2", + "phpunit/phpunit": "^10.3 || ^11.0 || ^12.0", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "symfony/yaml": "Allows the use of yaml for migration configuration files." + }, + "bin": [ + "bin/doctrine-migrations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Migrations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Michael Simonson", + "email": "contact@mikesimonson.com" + } + ], + "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.", + "homepage": "https://www.doctrine-project.org/projects/migrations.html", + "keywords": [ + "database", + "dbal", + "migrations" + ], + "support": { + "issues": "https://github.com/doctrine/migrations/issues", + "source": "https://github.com/doctrine/migrations/tree/3.9.2" + }, + "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%2Fmigrations", + "type": "tidelift" + } + ], + "time": "2025-07-29T11:36:14+00:00" + }, + { + "name": "doctrine/orm", + "version": "3.5.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/orm.git", + "reference": "5a541b8b3a327ab1ea5f93b1615b4ff67a34e109" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/orm/zipball/5a541b8b3a327ab1ea5f93b1615b4ff67a34e109", + "reference": "5a541b8b3a327ab1ea5f93b1615b4ff67a34e109", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/collections": "^2.2", + "doctrine/dbal": "^3.8.2 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2", + "doctrine/inflector": "^1.4 || ^2.0", + "doctrine/instantiator": "^1.3 || ^2", + "doctrine/lexer": "^3", + "doctrine/persistence": "^3.3.1 || ^4", + "ext-ctype": "*", + "php": "^8.1", + "psr/cache": "^1 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.3.9 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "^13.0", + "phpbench/phpbench": "^1.0", + "phpdocumentor/guides-cli": "^1.4", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "2.0.3", + "phpstan/phpstan-deprecation-rules": "^2", + "phpunit/phpunit": "^10.4.0", + "psr/log": "^1 || ^2 || ^3", + "squizlabs/php_codesniffer": "3.12.0", + "symfony/cache": "^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-dom": "Provides support for XSD validation for XML mapping files", + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\ORM\\": "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": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Object-Relational-Mapper for PHP", + "homepage": "https://www.doctrine-project.org/projects/orm.html", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/doctrine/orm/issues", + "source": "https://github.com/doctrine/orm/tree/3.5.2" + }, + "time": "2025-08-08T17:00:40+00:00" + }, + { + "name": "doctrine/persistence", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "45004aca79189474f113cbe3a53847c2115a55fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/45004aca79189474f113cbe3a53847c2115a55fa", + "reference": "45004aca79189474f113cbe3a53847c2115a55fa", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^1 || ^2", + "php": "^8.1", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/common": "<2.10" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "1.12.7", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^9.6", + "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "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" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://www.doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/4.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", + "type": "tidelift" + } + ], + "time": "2024-11-01T21:49:07+00:00" + }, + { + "name": "doctrine/sql-formatter", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/sql-formatter.git", + "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/d6d00aba6fd2957fe5216fe2b7673e9985db20c8", + "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "ergebnis/phpunit-slow-test-detector": "^2.14", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5" + }, + "bin": [ + "bin/sql-formatter" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\SqlFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "https://jeremydorn.com/" + } + ], + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/doctrine/sql-formatter/", + "keywords": [ + "highlight", + "sql" + ], + "support": { + "issues": "https://github.com/doctrine/sql-formatter/issues", + "source": "https://github.com/doctrine/sql-formatter/tree/1.5.2" + }, + "time": "2025-01-24T11:45:48+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "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/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6", + "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-30T17:13:41+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "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\\Cache\\": "" + } + }, + "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 caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-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-03-13T15:25:07+00:00" + }, + { + "name": "symfony/config", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "faef36e271bbeb74a9d733be4b56419b157762e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/faef36e271bbeb74a9d733be4b56419b157762e2", + "reference": "faef36e271bbeb74a9d733be4b56419b157762e2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "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": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-26T13:55:06+00:00" + }, + { + "name": "symfony/console", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "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": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-30T17:13:41+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "6cd2a1a77e8a0676a26e8bcddf10acfe7b0ba352" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6cd2a1a77e8a0676a26e8bcddf10acfe7b0ba352", + "reference": "6cd2a1a77e8a0676a26e8bcddf10acfe7b0ba352", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.5", + "symfony/var-exporter": "^6.4.20|^7.2.5" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "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": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-30T17:31:46+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "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": { + "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.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": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/doctrine-bridge", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/doctrine-bridge.git", + "reference": "a2cbc12baf9bcc5d0c125e4c0f8330b98af841ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/a2cbc12baf9bcc5d0c125e4c0f8330b98af841ca", + "reference": "a2cbc12baf9bcc5d0c125e4c0f8330b98af841ca", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^2", + "doctrine/persistence": "^3.1|^4", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/collections": "<1.8", + "doctrine/dbal": "<3.6", + "doctrine/lexer": "<1.1", + "doctrine/orm": "<2.15", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/form": "<6.4.6|>=7,<7.0.6", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/lock": "<6.4", + "symfony/messenger": "<6.4", + "symfony/property-info": "<6.4", + "symfony/security-bundle": "<6.4", + "symfony/security-core": "<6.4", + "symfony/validator": "<6.4" + }, + "require-dev": { + "doctrine/collections": "^1.8|^2.0", + "doctrine/data-fixtures": "^1.1|^2", + "doctrine/dbal": "^3.6|^4", + "doctrine/orm": "^2.15|^3", + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/doctrine-messenger": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4.6|^7.0.6", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/type-info": "^7.1.8", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Doctrine\\": "" + }, + "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 Doctrine with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:36:08+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "2192790a11f9e22cbcf9dc705a3ff22a5503923a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/2192790a11f9e22cbcf9dc705a3ff22a5503923a", + "reference": "2192790a11f9e22cbcf9dc705a3ff22a5503923a", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/process": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Dotenv\\": "" + }, + "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": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-10T08:29:33+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3", + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "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 tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-07T08:17:57+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "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/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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 tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.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-22T09:11:45+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^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\\EventDispatcher\\": "" + } + }, + "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 dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-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": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "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.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-07T08:17:47+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", + "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.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T13:41:35+00:00" + }, + { + "name": "symfony/flex", + "version": "v2.8.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/flex.git", + "reference": "423c36e369361003dc31ef11c5f15fb589e52c01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/flex/zipball/423c36e369361003dc31ef11c5f15fb589e52c01", + "reference": "423c36e369361003dc31ef11c5f15fb589e52c01", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.1", + "php": ">=8.0" + }, + "conflict": { + "composer/semver": "<1.7.2" + }, + "require-dev": { + "composer/composer": "^2.1", + "symfony/dotenv": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Flex\\Flex" + }, + "autoload": { + "psr-4": { + "Symfony\\Flex\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien.potencier@gmail.com" + } + ], + "description": "Composer plugin for Symfony", + "support": { + "issues": "https://github.com/symfony/flex/issues", + "source": "https://github.com/symfony/flex/tree/v2.8.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": "2025-07-05T07:45:19+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "06c0f678129f99bda8b5cf8873b3d8ef5a0029e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/06c0f678129f99bda8b5cf8873b3d8ef5a0029e7", + "reference": "06c0f678129f99bda8b5cf8873b3d8ef5a0029e7", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.2", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^7.3", + "symfony/dependency-injection": "^7.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^7.3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/filesystem": "^7.1", + "symfony/finder": "^6.4|^7.0", + "symfony/http-foundation": "^7.3", + "symfony/http-kernel": "^7.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^6.4|^7.0" + }, + "conflict": { + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/asset": "<6.4", + "symfony/asset-mapper": "<6.4", + "symfony/clock": "<6.4", + "symfony/console": "<6.4", + "symfony/dom-crawler": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/json-streamer": ">=7.4", + "symfony/lock": "<6.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/object-mapper": ">=7.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/runtime": "<6.4.13|>=7.0,<7.1.6", + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", + "symfony/security-core": "<6.4", + "symfony/security-csrf": "<7.2", + "symfony/serializer": "<7.2.5", + "symfony/stopwatch": "<6.4", + "symfony/translation": "<7.3", + "symfony/twig-bridge": "<6.4", + "symfony/twig-bundle": "<6.4", + "symfony/validator": "<6.4", + "symfony/web-profiler-bundle": "<6.4", + "symfony/webhook": "<7.2", + "symfony/workflow": "<7.3.0-beta2" + }, + "require-dev": { + "doctrine/persistence": "^1.3|^2|^3", + "dragonmantank/cron-expression": "^3.1", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "seld/jsonlint": "^1.10", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/json-streamer": "7.3.*", + "symfony/lock": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0", + "symfony/object-mapper": "^v7.3.0-beta2", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/scheduler": "^6.4.4|^7.0.4", + "symfony/security-bundle": "^6.4|^7.0", + "symfony/semaphore": "^6.4|^7.0", + "symfony/serializer": "^7.2.5", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^7.3", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/type-info": "^7.1.8", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/webhook": "^7.2", + "symfony/workflow": "^7.3", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "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 a tight integration between Symfony components and the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/framework-bundle/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-30T17:13:41+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6877c122b3a6cc3695849622720054f6e6fa5fa6", + "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5", + "symfony/clock": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "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": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-10T08:47:49+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6ecc895559ec0097e221ed2fd5eb44d5fede083c", + "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^7.3", + "symfony/http-foundation": "^7.3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "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 a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-31T10:45:04+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.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-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "000df7860439609837bbe28670b0be15783b7fbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", + "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.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-02-20T12:04:08+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "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": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:36:08+00:00" + }, + { + "name": "symfony/runtime", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "9516056d432f8acdac9458eb41b80097da7a05c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/9516056d432f8acdac9458eb41b80097da7a05c9", + "reference": "9516056d432f8acdac9458eb41b80097da7a05c9", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.2" + }, + "conflict": { + "symfony/dotenv": "<6.4" + }, + "require-dev": { + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "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": "Enables decoupling PHP applications from global state", + "homepage": "https://symfony.com", + "keywords": [ + "runtime" + ], + "support": { + "source": "https://github.com/symfony/runtime/tree/v7.3.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": "2025-06-13T07:48:40+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.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-25T09:37:31+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "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 a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.3.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-02-24T10:49:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", + "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.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-10T08:47:49+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "53205bea27450dc5c65377518b3275e126d45e75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75", + "reference": "53205bea27450dc5c65377518b3275e126d45e75", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "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 mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-29T20:02:46+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "05b3e90654c097817325d6abd284f7938b05f467" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/05b3e90654c097817325d6abd284f7938b05f467", + "reference": "05b3e90654c097817325d6abd284f7938b05f467", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "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": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-10T08:47:49+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/b8d7d868da9eb0919e99c8830431ea087d6aae30", + "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-10T08:47:49+00:00" + } + ], + "packages-dev": [ + { + "name": "nikic/php-parser", + "version": "v5.6.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "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.6.0" + }, + "time": "2025-07-27T20:03:57+00:00" + }, + { + "name": "symfony/maker-bundle", + "version": "v1.64.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/maker-bundle.git", + "reference": "c86da84640b0586e92aee2b276ee3638ef2f425a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/c86da84640b0586e92aee2b276ee3638ef2f425a", + "reference": "c86da84640b0586e92aee2b276ee3638ef2f425a", + "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/security-http": "^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.64.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-06-23T16:12:08+00:00" + }, + { + "name": "symfony/process", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "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.3.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-17T09:11:12+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=8.2", + "ext-ctype": "*", + "ext-iconv": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/test-api/config/bundles.php b/test-api/config/bundles.php new file mode 100644 index 000000000..de8898b23 --- /dev/null +++ b/test-api/config/bundles.php @@ -0,0 +1,8 @@ + ['all' => true], + Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], + Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], + Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], +]; diff --git a/test-api/config/packages/cache.yaml b/test-api/config/packages/cache.yaml new file mode 100644 index 000000000..6899b7200 --- /dev/null +++ b/test-api/config/packages/cache.yaml @@ -0,0 +1,19 @@ +framework: + cache: + # Unique name of your app: used to compute stable namespaces for cache keys. + #prefix_seed: your_vendor_name/app_name + + # The "app" cache stores to the filesystem by default. + # The data in this cache should persist between deploys. + # Other options include: + + # Redis + #app: cache.adapter.redis + #default_redis_provider: redis://localhost + + # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) + #app: cache.adapter.apcu + + # Namespaced pools use the above "app" backend by default + #pools: + #my.dedicated.cache: null diff --git a/test-api/config/packages/doctrine.yaml b/test-api/config/packages/doctrine.yaml new file mode 100644 index 000000000..25138b979 --- /dev/null +++ b/test-api/config/packages/doctrine.yaml @@ -0,0 +1,54 @@ +doctrine: + dbal: + url: '%env(resolve:DATABASE_URL)%' + + # IMPORTANT: You MUST configure your server version, + # either here or in the DATABASE_URL env var (see .env file) + #server_version: '16' + + profiling_collect_backtrace: '%kernel.debug%' + use_savepoints: true + orm: + auto_generate_proxy_classes: true + enable_lazy_ghost_objects: true + report_fields_where_declared: true + validate_xml_mapping: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + identity_generation_preferences: + Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity + auto_mapping: true + mappings: + App: + type: attribute + is_bundle: false + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + controller_resolver: + auto_mapping: false + +when@test: + doctrine: + dbal: + # "TEST_TOKEN" is typically set by ParaTest + dbname_suffix: '_test%env(default::TEST_TOKEN)%' + +when@prod: + doctrine: + orm: + auto_generate_proxy_classes: false + proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' + query_cache_driver: + type: pool + pool: doctrine.system_cache_pool + result_cache_driver: + type: pool + pool: doctrine.result_cache_pool + + framework: + cache: + pools: + doctrine.result_cache_pool: + adapter: cache.app + doctrine.system_cache_pool: + adapter: cache.system diff --git a/test-api/config/packages/doctrine_migrations.yaml b/test-api/config/packages/doctrine_migrations.yaml new file mode 100644 index 000000000..29231d94b --- /dev/null +++ b/test-api/config/packages/doctrine_migrations.yaml @@ -0,0 +1,6 @@ +doctrine_migrations: + migrations_paths: + # namespace is arbitrary but should be different from App\Migrations + # as migrations classes should NOT be autoloaded + 'DoctrineMigrations': '%kernel.project_dir%/migrations' + enable_profiler: false diff --git a/test-api/config/packages/framework.yaml b/test-api/config/packages/framework.yaml new file mode 100644 index 000000000..7e1ee1f1e --- /dev/null +++ b/test-api/config/packages/framework.yaml @@ -0,0 +1,15 @@ +# see https://symfony.com/doc/current/reference/configuration/framework.html +framework: + secret: '%env(APP_SECRET)%' + + # Note that the session will be started ONLY if you read or write from it. + session: true + + #esi: true + #fragments: true + +when@test: + framework: + test: true + session: + storage_factory_id: session.storage.factory.mock_file diff --git a/test-api/config/packages/routing.yaml b/test-api/config/packages/routing.yaml new file mode 100644 index 000000000..8166181c6 --- /dev/null +++ b/test-api/config/packages/routing.yaml @@ -0,0 +1,10 @@ +framework: + router: + # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. + # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands + #default_uri: http://localhost + +when@prod: + framework: + router: + strict_requirements: null diff --git a/test-api/config/preload.php b/test-api/config/preload.php new file mode 100644 index 000000000..5ebcdb215 --- /dev/null +++ b/test-api/config/preload.php @@ -0,0 +1,5 @@ +addSql('CREATE TABLE investment (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, created_at DATE NOT NULL, initial_value DOUBLE PRECISION NOT NULL, redeemed_value DOUBLE PRECISION DEFAULT NULL, redeemed_at DATE DEFAULT NULL, INDEX IDX_43CA0AD67E3C61F9 (owner_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE owner (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(150) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE investment ADD CONSTRAINT FK_43CA0AD67E3C61F9 FOREIGN KEY (owner_id) REFERENCES owner (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE investment DROP FOREIGN KEY FK_43CA0AD67E3C61F9'); + $this->addSql('DROP TABLE investment'); + $this->addSql('DROP TABLE owner'); + } +} diff --git a/test-api/public/index.php b/test-api/public/index.php new file mode 100644 index 000000000..9982c218d --- /dev/null +++ b/test-api/public/index.php @@ -0,0 +1,9 @@ +id; + } + + public function getOwner(): Owner { + return $this->owner; + } + + public function setOwner(Owner $owner): self { + $this->owner = $owner; return $this; + } + + public function getCreatedAt(): DateTimeInterface { + return $this->createdAt; + } + + public function setCreatedAt(DateTimeInterface $createdAt): self { + $this->createdAt = $createdAt; return $this; + } + + public function getInitialValue(): float { + return $this->initialValue; + } + + public function setInitialValue(float $initialValue): self { + if ($initialValue < 0) { + throw new \InvalidArgumentException("O valor inicial não pode ser negativo."); + } + $this->initialValue = $initialValue; + return $this; + } + + public function getRedeemedValue(): ?float { + return $this->redeemedValue; + } + + public function setRedeemedValue(?float $redeemedValue): self { + $this->redeemedValue = $redeemedValue; + return $this; + } + + public function getRedeemedAt(): ?DateTimeInterface { + return $this->redeemedAt; + } + + public function setRedeemedAt(?DateTimeInterface $redeemedAt): self { + $this->redeemedAt = $redeemedAt; return $this; + } +} diff --git a/test-api/src/Entity/Owner.php b/test-api/src/Entity/Owner.php new file mode 100644 index 000000000..1654ca33b --- /dev/null +++ b/test-api/src/Entity/Owner.php @@ -0,0 +1,28 @@ +id; + } + + public function getName(): string { + return $this->name; + } + + public function setName(string $name): self { + $this->name = $name; return $this; + } +} diff --git a/test-api/src/Kernel.php b/test-api/src/Kernel.php new file mode 100644 index 000000000..779cd1f2b --- /dev/null +++ b/test-api/src/Kernel.php @@ -0,0 +1,11 @@ + Date: Wed, 13 Aug 2025 00:22:12 -0300 Subject: [PATCH 2/8] =?UTF-8?q?Inclus=C3=A3o=20da=20Base=20de=20Dados=20My?= =?UTF-8?q?Sql=20ao=20projeto=20com=20esquema,=20tabelas=20e=20classe=20de?= =?UTF-8?q?=20abstra=C3=A7=C3=A3o=20do=20framework.=20Classe=20de=20calcul?= =?UTF-8?q?o=20para=20computar=20os=20investimentos=20(Service).=20Impleme?= =?UTF-8?q?nta=C3=A7=C3=A3o=20dos=20endpoints=20Create,=20Show,=20Redeen?= =?UTF-8?q?=20e=20New=20Owner.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-api/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test-api/README.md b/test-api/README.md index ef08e2616..adbb530b7 100644 --- a/test-api/README.md +++ b/test-api/README.md @@ -17,4 +17,10 @@ CREATE USER 'usuario_api'@'localhost' IDENTIFIED BY '.!usuarioApi2025!.'; GRANT ALL PRIVILEGES ON `db_test_api`.* TO 'usuario_api'@'localhost'; -- Aplica mudanças -FLUSH PRIVILEGES; \ No newline at end of file +FLUSH PRIVILEGES; + +# Instruções especiais para copilação + +# Bibliotecar de terceiros Utilizadas (Porque utilizou e como foram usadas) + +# Link Para a documentaçãoda API () \ No newline at end of file From 6b809fa2e9a3f055e9c567508f7c084f817295e0 Mon Sep 17 00:00:00 2001 From: lucianoarm Date: Wed, 13 Aug 2025 00:22:49 -0300 Subject: [PATCH 3/8] new file: test-api/migrations/Version20250812151347.php new file: test-api/src/Controller/Api/InvestmentController.php modified: test-api/src/Entity/Investment.php modified: test-api/src/Entity/Owner.php new file: test-api/src/Repository/InvestmentRepository.php new file: test-api/src/Repository/OwnerRepository.php new file: test-api/src/Service/InvestmentService.php --- test-api/migrations/Version20250812151347.php | 31 ++ .../Controller/Api/InvestmentController.php | 392 ++++++++++++++++++ test-api/src/Entity/Investment.php | 30 +- test-api/src/Entity/Owner.php | 17 +- .../src/Repository/InvestmentRepository.php | 22 + test-api/src/Repository/OwnerRepository.php | 22 + test-api/src/Service/InvestmentService.php | 100 +++++ 7 files changed, 597 insertions(+), 17 deletions(-) create mode 100644 test-api/migrations/Version20250812151347.php create mode 100644 test-api/src/Controller/Api/InvestmentController.php create mode 100644 test-api/src/Repository/InvestmentRepository.php create mode 100644 test-api/src/Repository/OwnerRepository.php create mode 100644 test-api/src/Service/InvestmentService.php diff --git a/test-api/migrations/Version20250812151347.php b/test-api/migrations/Version20250812151347.php new file mode 100644 index 000000000..7da4c0099 --- /dev/null +++ b/test-api/migrations/Version20250812151347.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE owner ADD email VARCHAR(150) NOT NULL, CHANGE name name VARCHAR(250) NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE owner DROP email, CHANGE name name VARCHAR(150) NOT NULL'); + } +} diff --git a/test-api/src/Controller/Api/InvestmentController.php b/test-api/src/Controller/Api/InvestmentController.php new file mode 100644 index 000000000..d22190352 --- /dev/null +++ b/test-api/src/Controller/Api/InvestmentController.php @@ -0,0 +1,392 @@ +json([ + "status" => "Success", + 'response' => "Ping-Pong API trabalhando!", + ], 201); + } + + /** + * Criar investidor (Owner) + */ + #[Route('/newOwner', methods: ['POST'])] + public function new_owner(Request $request): JsonResponse + { + try { + $data = json_decode($request->getContent(), true); + if ($data === null) { + $data = $request->request->all(); + } + + // Validar nome do investidor + if (empty($data['name'])) { + throw new \InvalidArgumentException('Nome do investidor é obrigatório!'); + } + + // Validar email do investidor + if (empty($data['email'])) { + throw new \InvalidArgumentException('Email do investidor é obrigatório!'); + } + + // Criar investimento + $owner = new Owner(); + + // Seta parametros + $owner->setName($data['name']); + $owner->setEmail($data['email']); + + // Persistir + $em = $this->doctrine->getManager(); + $em->persist($owner); + $em->flush(); + + return $this->json([ + "status" => "Success", + 'id' => $owner->getId(), + 'name' => $owner->getName(), + 'email' => $owner->getEmail(), + ], 201); + } catch (\Exception $e) { + return $this->json([ + 'status' => 'error', + 'message' => 'Atenção : ' . $e->getMessage() + ], 428); + } + } + + /** + * Criar um novo investimento + */ + #[Route('/create', methods: ['POST'])] + public function create(Request $request): JsonResponse + { + try { + $data = json_decode($request->getContent(), true); + if ($data === null) { + $data = $request->request->all(); + } + + // Validar se owner foi informado + if (empty($data['ownerId'])) { + throw new \InvalidArgumentException('Id do investidor é obrigatório!'); + } + + // Verificar owner existe + $owner = $this->ownerRepository->find($data['ownerId']); + if (!$owner) { + throw new \InvalidArgumentException('Investidor não encontrado!'); + } + + // Validar se valor foi informado + if (empty($data['initialValue'])) { + throw new \InvalidArgumentException('Valor inicial é obrigatório!'); + } + + // Valor inicial >= 0 + $initialValue = (float) $data['initialValue']; + if ($initialValue <= 0) { + throw new \InvalidArgumentException('Valor inicial inválido!'); + } + + // Verificar se a data foi informada + if(!isset($data['createdAt']) || empty($data['createdAt'])) { + throw new \InvalidArgumentException('Data de inicio do investimento não informado!'); + } + + // Verificar se a data é válida + $date = DateTime::createFromFormat('Y-m-d', $data['createdAt']); + if (!$date || $date->format('Y-m-d') !== $data['createdAt']) { + throw new \InvalidArgumentException('Data de inicio do investimento inválida!'); + } + + // Verificar se é data futura + if ($date > new DateTime('now')) { + throw new \InvalidArgumentException('Data futura não é permitida, somente data atual ou data do passado!'); + } + + // Criar investimento + $investment = new Investment(); + + // Seta parametros + $investment->setOwner($owner); + $investment->setInitialValue($initialValue); + $investment->setCreatedAt($date); + + // Persistir + $em = $this->doctrine->getManager(); + $em->persist($investment); + $em->flush(); + + return $this->json([ + "status" => "Success", + 'id' => $investment->getId(), + 'ownerId' => $owner->getId(), + 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), + 'initialValue' => $investment->getInitialValue(), + ], 201); + + } catch (\Exception $e) { + return $this->json([ + 'status' => 'error', + 'message' => 'Atenção : ' . $e->getMessage() + ], 428); + } + } + + /** + * Visualizar detalhes de um investimento com saldo apurado + */ + #[Route('/show', methods: ['POST'])] + #[Route('/show/{id}', methods: ['POST'])] + public function show(?int $id, Request $request): JsonResponse + { + try { + // Verificar se o id foi informado + if (empty($id) || $id <= 0) { + throw new \InvalidArgumentException('Id do investimento inválido!'); + } + + // Verificar se o investimento existe + $investment = $this->investmentRepository->find($id); + if (!$investment) { + return $this->json(['error' => 'Investimento não encontrado'], 404); + } + + // Verifica se o investimento já foi resgatado + $redeemedAt = $investment->getRedeemedAt(); + if ($redeemedAt !== null) { + return $this->json([ + 'Status' => 'Success', + 'id' => $investment->getId(), + 'ownerId' => $investment->getOwner()->getId(), + 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), + 'initialValue' => $investment->getInitialValue(), + 'redeemed' => $investment->getRedeemedAt() !== null, + 'redeemedAt' => $investment->getRedeemedAt()?->format('Y-m-d'), + 'redeemedValue' => $investment->getRedeemedValue(), + ], 201); + } + + $data = json_decode($request->getContent(), true); + if ($data === null) { + $data = $request->request->all(); + } + + // caso a data nao for passada pega o data atual + if(!isset($data['redeemDate']) || empty($data['redeemDate'])) { + $data['redeemDate'] = date('Y-m-d'); + } + + // Verificar se a data é válida + $date = DateTime::createFromFormat('Y-m-d', $data['redeemDate']); + if (!$date || $date->format('Y-m-d') !== $data['redeemDate']) { + throw new \InvalidArgumentException('Data de resgate do investimento inválida!'); + } + + // Verificar se a data é futura ou anterior à data de criação do investimento + if ($date > new DateTime('now') || $date < $investment->getCreatedAt() ) { + throw new \InvalidArgumentException('Data não permitida, somente data posterior a data do investimento até a data atual!'); + } + + $result = $this->investmentService->redeemInvestment($investment, $date); + + // Atualiza investimento para registrar/salvar o resgate + $investment->setRedeemedAt($date); + $investment->setRedeemedValue($result['net']); + + return $this->json([ + 'status' => 'Success', + 'investment' => $investment->getId(), + 'ownerId' => $investment->getOwner()->getId(), + 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), + 'initialValue' => $investment->getInitialValue(), + 'redeemed' => false, + 'redeemedAt' => $investment->getRedeemedAt()?->format('Y-m-d'), + 'gross' => $result['gross'], + 'profit' => $result['profit'], + 'tax' => $result['tax'], + 'redeemedValue' => $result['net'], + ], 200); + + } catch (\Exception $e) { + return $this->json([ + 'status' => 'error', + 'message' => 'Atenção : ' . $e->getMessage() + ], 428); + } + } + + private function showRendiment() + { + return []; + } + + /** + * Resgatar um investimento + */ + #[Route('/redeem/{id}', methods: ['POST'])] + public function redeem(int $id, Request $request): JsonResponse + { + try { + + // Verificar se o investimento existe + $investment = $this->investmentRepository->find($id); + if (!$investment) { + throw new \InvalidArgumentException('Investimento não encontrado!'); + } + + // Verificar se o investimento já foi resgatado + if ($investment->getRedeemedAt() !== null) { + throw new \InvalidArgumentException('Investimento já resgatado.'); + } + + // Trata a data de resgate informada + $data = json_decode($request->getContent(), true); + if ($data === null) { + $data = $request->request->all(); + } + + // Verificar se a data foi informada + if(!isset($data['redeemDate']) || empty($data['redeemDate'])) { + throw new \InvalidArgumentException('A data de resgate do investimento não informado! (redeemDate)'); + } + + // Verificar se a data é válida + $date = DateTime::createFromFormat('Y-m-d', $data['redeemDate']); + if (!$date || $date->format('Y-m-d') !== $data['redeemDate']) { + throw new \InvalidArgumentException('Data de resgate do investimento inválida!'); + } + + // Verificar se a data é futura ou anterior à data de criação do investimento + if ($date > new DateTime('now') || $date < $investment->getCreatedAt() ) { + throw new \InvalidArgumentException('Data não permitida, somente data posterior a data do investimento até a data atual!'); + } + $result = $this->investmentService->redeemInvestment($investment, $date); + + // Atualiza investimento para registrar/salvar o resgate + $investment->setRedeemedAt($date); + $investment->setRedeemedValue($result['net']); + + // Persistir atualização do investimento (resgate) + $em = $this->doctrine->getManager(); + $em->persist($investment); + $em->flush(); + + return $this->json([ + 'status' => 'Success', + 'investment' => $investment->getId(), + 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), + 'initialValue' => $investment->getInitialValue(), + 'redeemed' => $investment->getRedeemedAt() !== null, + 'redeemedAt' => $investment->getRedeemedAt()?->format('Y-m-d'), + 'gross' => $result['gross'], + 'profit' => $result['profit'], + 'tax' => $result['tax'], + 'redeemedValue' => $result['net'], + ], 200); + + } catch (\Exception $e) { + return $this->json([ + 'status' => 'error', + 'message' => 'Atenção : ' . $e->getMessage() + ], 428); + } + } + + // 4. Listar investimentos de um proprietário com paginação + #[Route('/owner/{ownerId}', methods: ['GET'])] + public function listByOwner(int $ownerId, Request $request): JsonResponse + { + $x = 1; + + $data = []; + do { + $data[] = [ + 'id' => ++$x, + 'createdAt' => '2025-08-11', + 'initialValue' => '1000.00', + 'redeemed' => false, + 'expectedBalance' => '1200.00', + ]; + } while (20 >= $x ); + + return $this->json([ + 'page' => 1, + 'limit' => 100, + 'results' => $data, + ], 201); + + + // $owner = $this->ownerRepository->find($ownerId); + // if (!$owner) { + // return $this->json(['error' => 'Owner não encontrado'], 404); + // } + + // $page = max(1, (int)$request->query->get('page', 1)); + // $limit = max(1, min(50, (int)$request->query->get('limit', 10))); + + // $offset = ($page - 1) * $limit; + + // $repo = $this->investmentRepository; + + // $qb = $repo->createQueryBuilder('i') + // ->andWhere('i.owner = :owner') + // ->setParameter('owner', $owner) + // ->setFirstResult($offset) + // ->setMaxResults($limit) + // ->orderBy('i.createdAt', 'DESC'); + + // $investments = $qb->getQuery()->getResult(); + + // $data = []; + // foreach ($investments as $investment) { + // $balance = $this->investmentService->calculateExpectedBalance($investment); + // $data[] = [ + // 'id' => $investment->getId(), + // 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), + // 'initialValue' => $investment->getInitialValue(), + // 'redeemed' => $investment->getRedeemedAt() !== null, + // 'expectedBalance' => round($balance, 2), + // ]; + // } + + // return $this->json([ + // "status" => "Success", + // 'page' => $page, + // 'limit' => $limit, + // 'results' => $data, + // ], 201); + + } +} diff --git a/test-api/src/Entity/Investment.php b/test-api/src/Entity/Investment.php index b87823feb..c098b6bfb 100644 --- a/test-api/src/Entity/Investment.php +++ b/test-api/src/Entity/Investment.php @@ -1,5 +1,4 @@ id; } - public function getOwner(): Owner { + public function getOwner(): Owner { return $this->owner; } - public function setOwner(Owner $owner): self { - $this->owner = $owner; return $this; + public function setOwner(Owner $owner): self { + $this->owner = $owner; + return $this; } public function getCreatedAt(): DateTimeInterface { return $this->createdAt; } - - public function setCreatedAt(DateTimeInterface $createdAt): self { - $this->createdAt = $createdAt; return $this; + + public function setCreatedAt(DateTimeInterface $createdAt): self { + $this->createdAt = $createdAt; + return $this; } public function getInitialValue(): float { @@ -54,9 +55,6 @@ public function getInitialValue(): float { } public function setInitialValue(float $initialValue): self { - if ($initialValue < 0) { - throw new \InvalidArgumentException("O valor inicial não pode ser negativo."); - } $this->initialValue = $initialValue; return $this; } @@ -65,16 +63,18 @@ public function getRedeemedValue(): ?float { return $this->redeemedValue; } - public function setRedeemedValue(?float $redeemedValue): self { + public function setRedeemedValue(?float $redeemedValue): self { $this->redeemedValue = $redeemedValue; - return $this; + return $this; } - - public function getRedeemedAt(): ?DateTimeInterface { + + public function getRedeemedAt(): ?DateTimeInterface { return $this->redeemedAt; } public function setRedeemedAt(?DateTimeInterface $redeemedAt): self { - $this->redeemedAt = $redeemedAt; return $this; + $this->redeemedAt = $redeemedAt; + return $this; } + } diff --git a/test-api/src/Entity/Owner.php b/test-api/src/Entity/Owner.php index 1654ca33b..e933eb40f 100644 --- a/test-api/src/Entity/Owner.php +++ b/test-api/src/Entity/Owner.php @@ -11,9 +11,12 @@ class Owner #[ORM\Column] private ?int $id = null; - #[ORM\Column(length: 150)] + #[ORM\Column(length: 250)] private string $name; + #[ORM\Column(length: 150)] + private string $email; + public function getId(): ?int { return $this->id; } @@ -23,6 +26,16 @@ public function getName(): string { } public function setName(string $name): self { - $this->name = $name; return $this; + $this->name = $name; + return $this; + } + + public function setEmail(string $email): self { + $this->email = $email; + return $this; + } + + public function getEmail(): string { + return $this->email; } } diff --git a/test-api/src/Repository/InvestmentRepository.php b/test-api/src/Repository/InvestmentRepository.php new file mode 100644 index 000000000..fef0e03b9 --- /dev/null +++ b/test-api/src/Repository/InvestmentRepository.php @@ -0,0 +1,22 @@ + + * + * @method Investment|null find($id, $lockMode = null, $lockVersion = null) + * @method Investment|null findOneBy(array $criteria, array $orderBy = null) + * @method Investment[] findAll() + * @method Investment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class InvestmentRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Investment::class); + } +} diff --git a/test-api/src/Repository/OwnerRepository.php b/test-api/src/Repository/OwnerRepository.php new file mode 100644 index 000000000..a605920e5 --- /dev/null +++ b/test-api/src/Repository/OwnerRepository.php @@ -0,0 +1,22 @@ + + * + * @method Owner|null find($id, $lockMode = null, $lockVersion = null) + * @method Owner|null findOneBy(array $criteria, array $orderBy = null) + * @method Owner[] findAll() + * @method Owner[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class OwnerRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Owner::class); + } +} diff --git a/test-api/src/Service/InvestmentService.php b/test-api/src/Service/InvestmentService.php new file mode 100644 index 000000000..009fd9ebc --- /dev/null +++ b/test-api/src/Service/InvestmentService.php @@ -0,0 +1,100 @@ +getCreatedAt(); + // Se investimento resgatado, calcula até a data do resgate + $endDate = $investment->getRedeemedAt() ?? $asOfDate; + // Se a data para cálculo for anterior à criação, saldo = 0 + if ($endDate < $createdAt) { + return 0.0; + } + $months = $this->calculateMonthsBetween($createdAt, $endDate); + $initial = $investment->getInitialValue(); + // Juros compostos: saldo = valor_inicial * (1 + taxa) ^ meses + $balance = $initial * pow(1 + self::MONTHLY_INTEREST_RATE, $months); + return round($balance, 2); + } + + /** + * Calcula meses completos entre duas datas baseado no dia do mês da data inicial. + */ + private function calculateMonthsBetween(DateTimeInterface $start, DateTimeInterface $end): int + { + $startDay = (int) $start->format('d'); + $interval = $start->diff($end); + $months = $interval->y * 12 + $interval->m; + // Ajusta se o dia do mês em $end ainda não chegou + if ((int) $end->format('d') < $startDay) { + $months--; + } + return max(0, $months); + } + + /** + * Calcula o valor do imposto baseado no lucro e tempo do investimento. + * + * @param Investment $investment + * @param float $balance Valor antes do imposto + * @return float Valor do imposto a ser aplicado + */ + public function calculateTax(Investment $investment, float $balance): float + { + $initial = $investment->getInitialValue(); + $profit = $balance - $initial; + if ($profit <= 0) { + return 0.0; + } + $createdAt = $investment->getCreatedAt(); + $now = new DateTimeImmutable('now'); + $interval = $createdAt->diff($now); + $years = $interval->y; + if ($years < 1) { + $taxRate = 0.225; // 22.5% + } elseif ($years < 2) { + $taxRate = 0.185; // 18.5% + } else { + $taxRate = 0.15; // 15% + } + return round($profit * $taxRate, 2); + } + + /** + * Calcula o resgate do investimento, aplicando todas as regras e retornando valor líquido após imposto. + * + * @param Investment $investment + * @param DateTimeInterface $redeemDate Data do resgate informada pelo usuário + * @return array ['gross' => float, 'tax' => float, 'net' => float] + */ + public function redeemInvestment(Investment &$investment, DateTimeInterface $redeemDate): array + { + $gross = $this->calculateExpectedBalance($investment, $redeemDate); + $tax = $this->calculateTax($investment, $gross); + $net = $gross - $tax; + + return [ + 'gross' => round($gross, 2), + 'profit' => round($gross - $investment->getInitialValue(), 2), + 'tax' => $tax, + 'net' => round($net, 2), + ]; + } +} \ No newline at end of file From d06dc38142c26af3a2a8d8a622ba24742dbe7e32 Mon Sep 17 00:00:00 2001 From: lucianoarm Date: Wed, 13 Aug 2025 10:57:03 -0300 Subject: [PATCH 4/8] =?UTF-8?q?Finaliza=C3=A7=C3=A3o=20do=20metodo=20listB?= =?UTF-8?q?yOwner=20com=20pagina=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/Api/InvestmentController.php | 109 +++++++++--------- 1 file changed, 52 insertions(+), 57 deletions(-) diff --git a/test-api/src/Controller/Api/InvestmentController.php b/test-api/src/Controller/Api/InvestmentController.php index d22190352..1e19580bc 100644 --- a/test-api/src/Controller/Api/InvestmentController.php +++ b/test-api/src/Controller/Api/InvestmentController.php @@ -167,9 +167,8 @@ public function create(Request $request): JsonResponse /** * Visualizar detalhes de um investimento com saldo apurado */ - #[Route('/show', methods: ['POST'])] #[Route('/show/{id}', methods: ['POST'])] - public function show(?int $id, Request $request): JsonResponse + public function show(int $id, Request $request): JsonResponse { try { // Verificar se o id foi informado @@ -271,7 +270,6 @@ public function redeem(int $id, Request $request): JsonResponse throw new \InvalidArgumentException('Investimento já resgatado.'); } - // Trata a data de resgate informada $data = json_decode($request->getContent(), true); if ($data === null) { $data = $request->request->all(); @@ -324,69 +322,66 @@ public function redeem(int $id, Request $request): JsonResponse } } - // 4. Listar investimentos de um proprietário com paginação - #[Route('/owner/{ownerId}', methods: ['GET'])] + /** + * Listar investimentos de um proprietário com paginação + */ + #[Route('/owner/{ownerId}', methods: ['POST'])] public function listByOwner(int $ownerId, Request $request): JsonResponse { - $x = 1; - - $data = []; - do { - $data[] = [ - 'id' => ++$x, - 'createdAt' => '2025-08-11', - 'initialValue' => '1000.00', - 'redeemed' => false, - 'expectedBalance' => '1200.00', - ]; - } while (20 >= $x ); - - return $this->json([ - 'page' => 1, - 'limit' => 100, - 'results' => $data, - ], 201); - - - // $owner = $this->ownerRepository->find($ownerId); - // if (!$owner) { - // return $this->json(['error' => 'Owner não encontrado'], 404); - // } + $data = json_decode($request->getContent(), true); + if ($data === null) { + $data = $request->request->all(); + } - // $page = max(1, (int)$request->query->get('page', 1)); - // $limit = max(1, min(50, (int)$request->query->get('limit', 10))); + $owner = $this->ownerRepository->find($ownerId); + if (!$owner) { + throw new \InvalidArgumentException('Investidor não encontrado!'); + } - // $offset = ($page - 1) * $limit; + $page = empty($data['page']) ? 1 : (int)$data['page']; + $limit = empty($data['limit']) ? 100 : (int)$data['limit']; + $offset = ($page - 1) * $limit; - // $repo = $this->investmentRepository; + $repo = $this->investmentRepository; - // $qb = $repo->createQueryBuilder('i') - // ->andWhere('i.owner = :owner') - // ->setParameter('owner', $owner) - // ->setFirstResult($offset) - // ->setMaxResults($limit) - // ->orderBy('i.createdAt', 'DESC'); + $total = (int) $repo->createQueryBuilder('i') + ->select('COUNT(i.id)') + ->andWhere('i.owner = :owner') + ->setParameter('owner', $owner) + ->getQuery() + ->getSingleScalarResult(); - // $investments = $qb->getQuery()->getResult(); + $res = $repo->createQueryBuilder('i') + ->andWhere('i.owner = :owner') + ->setParameter('owner', $owner) + ->setFirstResult($offset) + ->setMaxResults($limit) + ->orderBy('i.createdAt', 'DESC'); - // $data = []; - // foreach ($investments as $investment) { - // $balance = $this->investmentService->calculateExpectedBalance($investment); - // $data[] = [ - // 'id' => $investment->getId(), - // 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), - // 'initialValue' => $investment->getInitialValue(), - // 'redeemed' => $investment->getRedeemedAt() !== null, - // 'expectedBalance' => round($balance, 2), - // ]; - // } + $investments = $res->getQuery()->getResult(); - // return $this->json([ - // "status" => "Success", - // 'page' => $page, - // 'limit' => $limit, - // 'results' => $data, - // ], 201); + $listaInvestments = []; + foreach ($investments as $investment) { + $balance = $this->investmentService->calculateExpectedBalance($investment); + $listaInvestments[] = [ + 'id' => $investment->getId(), + 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), + 'initialValue' => $investment->getInitialValue(), + 'redeemed' => $investment->getRedeemedAt() !== null, + 'expectedBalance' => $investment->getRedeemedAt() !== null ? $investment->getRedeemedValue() : round($balance, 2), + ]; + } + return $this->json([ + "status" => "Success", + 'ownerId' => $ownerId, + 'owner' => $owner->getName(), + 'totalInvestment' => $total, + 'totalPages' => ceil($total / $limit), + 'page' => $page, + 'limit' => $limit, + 'offset' => $offset, + 'listaInvestments' => $listaInvestments, + ], 200); } } From 3f8150c76afd025b6bb9b912f301b7db1d70133f Mon Sep 17 00:00:00 2001 From: lucianoarm Date: Wed, 13 Aug 2025 11:01:57 -0300 Subject: [PATCH 5/8] try.. catch.. --- .../Controller/Api/InvestmentController.php | 102 ++++++++++-------- 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/test-api/src/Controller/Api/InvestmentController.php b/test-api/src/Controller/Api/InvestmentController.php index 1e19580bc..18ebdab06 100644 --- a/test-api/src/Controller/Api/InvestmentController.php +++ b/test-api/src/Controller/Api/InvestmentController.php @@ -328,60 +328,68 @@ public function redeem(int $id, Request $request): JsonResponse #[Route('/owner/{ownerId}', methods: ['POST'])] public function listByOwner(int $ownerId, Request $request): JsonResponse { - $data = json_decode($request->getContent(), true); - if ($data === null) { - $data = $request->request->all(); - } + try { + $data = json_decode($request->getContent(), true); + if ($data === null) { + $data = $request->request->all(); + } - $owner = $this->ownerRepository->find($ownerId); - if (!$owner) { - throw new \InvalidArgumentException('Investidor não encontrado!'); - } + $owner = $this->ownerRepository->find($ownerId); + if (!$owner) { + throw new \InvalidArgumentException('Investidor não encontrado!'); + } - $page = empty($data['page']) ? 1 : (int)$data['page']; - $limit = empty($data['limit']) ? 100 : (int)$data['limit']; - $offset = ($page - 1) * $limit; + $page = empty($data['page']) ? 1 : (int)$data['page']; + $limit = empty($data['limit']) ? 100 : (int)$data['limit']; + $offset = ($page - 1) * $limit; - $repo = $this->investmentRepository; + $repo = $this->investmentRepository; - $total = (int) $repo->createQueryBuilder('i') - ->select('COUNT(i.id)') - ->andWhere('i.owner = :owner') - ->setParameter('owner', $owner) - ->getQuery() - ->getSingleScalarResult(); + $total = (int) $repo->createQueryBuilder('i') + ->select('COUNT(i.id)') + ->andWhere('i.owner = :owner') + ->setParameter('owner', $owner) + ->getQuery() + ->getSingleScalarResult(); - $res = $repo->createQueryBuilder('i') - ->andWhere('i.owner = :owner') - ->setParameter('owner', $owner) - ->setFirstResult($offset) - ->setMaxResults($limit) - ->orderBy('i.createdAt', 'DESC'); + $res = $repo->createQueryBuilder('i') + ->andWhere('i.owner = :owner') + ->setParameter('owner', $owner) + ->setFirstResult($offset) + ->setMaxResults($limit) + ->orderBy('i.createdAt', 'DESC'); - $investments = $res->getQuery()->getResult(); + $investments = $res->getQuery()->getResult(); - $listaInvestments = []; - foreach ($investments as $investment) { - $balance = $this->investmentService->calculateExpectedBalance($investment); - $listaInvestments[] = [ - 'id' => $investment->getId(), - 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), - 'initialValue' => $investment->getInitialValue(), - 'redeemed' => $investment->getRedeemedAt() !== null, - 'expectedBalance' => $investment->getRedeemedAt() !== null ? $investment->getRedeemedValue() : round($balance, 2), - ]; - } + $listaInvestments = []; + foreach ($investments as $investment) { + $balance = $this->investmentService->calculateExpectedBalance($investment); + $listaInvestments[] = [ + 'id' => $investment->getId(), + 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), + 'initialValue' => $investment->getInitialValue(), + 'redeemed' => $investment->getRedeemedAt() !== null, + 'expectedBalance' => $investment->getRedeemedAt() !== null ? $investment->getRedeemedValue() : round($balance, 2), + ]; + } - return $this->json([ - "status" => "Success", - 'ownerId' => $ownerId, - 'owner' => $owner->getName(), - 'totalInvestment' => $total, - 'totalPages' => ceil($total / $limit), - 'page' => $page, - 'limit' => $limit, - 'offset' => $offset, - 'listaInvestments' => $listaInvestments, - ], 200); + return $this->json([ + "status" => "Success", + 'ownerId' => $ownerId, + 'owner' => $owner->getName(), + 'totalInvestment' => $total, + 'totalPages' => ceil($total / $limit), + 'page' => $page, + 'limit' => $limit, + 'offset' => $offset, + 'listaInvestments' => $listaInvestments, + ], 200); + + } catch (\Exception $e) { + return $this->json([ + 'status' => 'error', + 'message' => 'Atenção : ' . $e->getMessage() + ], 428); + } } } From 43bd4134e117086bc441fb0ce93a1a7d455b7361 Mon Sep 17 00:00:00 2001 From: lucianoarm Date: Thu, 14 Aug 2025 09:07:26 -0300 Subject: [PATCH 6/8] =?UTF-8?q?Implementa=C3=A7=C3=A3o=20da=20documenta?= =?UTF-8?q?=C3=A7=C3=A3o=20da=20API=20Swagger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-api/composer.json | 7 +- test-api/composer.lock | 1678 ++++++++++++++--- test-api/config/bundles.php | 3 + test-api/config/packages/nelmio_api_doc.yaml | 11 + test-api/config/packages/property_info.yaml | 3 + test-api/config/packages/twig.yaml | 6 + test-api/config/routes/nelmio_api_doc.yaml | 12 + .../Controller/Api/InvestmentController.php | 624 ++++-- test-api/symfony.lock | 41 + test-api/templates/base.html.twig | 16 + 10 files changed, 1990 insertions(+), 411 deletions(-) create mode 100644 test-api/config/packages/nelmio_api_doc.yaml create mode 100644 test-api/config/packages/property_info.yaml create mode 100644 test-api/config/packages/twig.yaml create mode 100644 test-api/config/routes/nelmio_api_doc.yaml create mode 100644 test-api/templates/base.html.twig diff --git a/test-api/composer.json b/test-api/composer.json index 77ad2dbc6..9a133ecf2 100644 --- a/test-api/composer.json +++ b/test-api/composer.json @@ -11,12 +11,17 @@ "doctrine/doctrine-bundle": "^2.15", "doctrine/doctrine-migrations-bundle": "^3.4", "doctrine/orm": "^3.5", + "nelmio/api-doc-bundle": "^5.5", + "symfony/asset": "7.3.*", "symfony/console": "7.3.*", "symfony/dotenv": "7.3.*", "symfony/flex": "^2", "symfony/framework-bundle": "7.3.*", "symfony/runtime": "7.3.*", - "symfony/yaml": "7.3.*" + "symfony/twig-bundle": "7.3.*", + "symfony/yaml": "7.3.*", + "twig/extra-bundle": "^2.12|^3.0", + "twig/twig": "^2.12|^3.0" }, "config": { "allow-plugins": { diff --git a/test-api/composer.lock b/test-api/composer.lock index 8f33ec184..ca115acde 100644 --- a/test-api/composer.lock +++ b/test-api/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": "5daab07dd6e49e34bc9e92813dc447d4", + "content-hash": "7aa52568df162536a32450abc587b517", "packages": [ { "name": "doctrine/collections", @@ -1132,6 +1132,409 @@ }, "time": "2025-01-24T11:45:48+00:00" }, + { + "name": "nelmio/api-doc-bundle", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioApiDocBundle.git", + "reference": "ce6434107ccb57d0686e25ae6c7767c75a3c6e4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/ce6434107ccb57d0686e25ae6c7767c75a3c6e4e", + "reference": "ce6434107ccb57d0686e25ae6c7767c75a3c6e4e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "phpdocumentor/reflection-docblock": "^5.0", + "phpdocumentor/type-resolver": "^1.8.2", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/container": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/config": "^6.4 || ^7.1", + "symfony/console": "^6.4 || ^7.1", + "symfony/dependency-injection": "^6.4 || ^7.1", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/framework-bundle": "^6.4 || ^7.1", + "symfony/http-foundation": "^6.4 || ^7.1", + "symfony/http-kernel": "^6.4 || ^7.1", + "symfony/options-resolver": "^6.4 || ^7.1", + "symfony/property-info": "^6.4 || ^7.1", + "symfony/routing": "^6.4 || ^7.1", + "zircote/swagger-php": "^4.11.1 || ^5.0" + }, + "conflict": { + "zircote/swagger-php": "4.8.7" + }, + "require-dev": { + "api-platform/core": "^3.2", + "friendsofphp/php-cs-fixer": "^3.52", + "friendsofsymfony/rest-bundle": "^3.2.0", + "jms/serializer": "^3.32", + "jms/serializer-bundle": "^5.5", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.5", + "phpstan/phpstan-symfony": "^1.3", + "phpunit/phpunit": "^10.5", + "symfony/asset": "^6.4 || ^7.1", + "symfony/browser-kit": "^6.4 || ^7.1", + "symfony/cache": "^6.4 || ^7.1", + "symfony/dom-crawler": "^6.4 || ^7.1", + "symfony/expression-language": "^6.4 || ^7.1", + "symfony/finder": "^6.4 || ^7.1", + "symfony/form": "^6.4 || ^7.1", + "symfony/phpunit-bridge": "^6.4 || ^7.1", + "symfony/property-access": "^6.4 || ^7.1", + "symfony/security-csrf": "^6.4 || ^7.1", + "symfony/security-http": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.1", + "symfony/stopwatch": "^6.4 || ^7.1", + "symfony/templating": "^6.4 || ^7.1", + "symfony/translation": "^6.4 || ^7.1", + "symfony/twig-bundle": "^6.4 || ^7.1", + "symfony/uid": "^6.4 || ^7.1", + "symfony/validator": "^6.4 || ^7.1", + "willdurand/hateoas-bundle": "^2.7", + "willdurand/negotiation": "^3.0" + }, + "suggest": { + "api-platform/core": "For using an API oriented framework.", + "friendsofsymfony/rest-bundle": "For using the parameters annotations.", + "jms/serializer-bundle": "For describing your models.", + "symfony/asset": "For using the Swagger UI.", + "symfony/cache": "For using a PSR-6 compatible cache implementation with the API doc generator.", + "symfony/form": "For describing your form type models.", + "symfony/monolog-bundle": "For using a PSR-3 compatible logger implementation with the API PHP describer.", + "symfony/security-csrf": "For using csrf protection tokens in forms.", + "symfony/serializer": "For describing your models.", + "symfony/twig-bundle": "For using the Swagger UI.", + "symfony/validator": "For describing the validation constraints in your models.", + "willdurand/hateoas-bundle": "For extracting HATEOAS metadata." + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-4.x": "4.x-dev", + "dev-5.x": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Nelmio\\ApiDocBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioApiDocBundle/contributors" + } + ], + "description": "Generates documentation for your REST API from attributes", + "keywords": [ + "api", + "doc", + "documentation", + "rest" + ], + "support": { + "issues": "https://github.com/nelmio/NelmioApiDocBundle/issues", + "source": "https://github.com/nelmio/NelmioApiDocBundle/tree/v5.5.0" + }, + "funding": [ + { + "url": "https://github.com/DjordyKoert", + "type": "github" + } + ], + "time": "2025-08-04T14:41:29+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.6.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "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.6.0" + }, + "time": "2025-07-27T20:03:57+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", + "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" + }, + "time": "2025-04-13T19:20:35+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + }, + "time": "2024-11-09T15:12:26+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" + }, + "time": "2025-07-13T07:04:09+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -1335,33 +1738,102 @@ "time": "2024-09-11T13:17:53+00:00" }, { - "name": "symfony/cache", - "version": "v7.3.2", + "name": "symfony/asset", + "version": "v7.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6" + "url": "https://github.com/symfony/asset.git", + "reference": "56c4d9f759247c4e07d8549e3baf7493cb9c3e4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6", - "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6", + "url": "https://api.github.com/repos/symfony/asset/zipball/56c4d9f759247c4e07d8549e3baf7493cb9c3e4b", + "reference": "56c4d9f759247c4e07d8549e3baf7493cb9c3e4b", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/cache": "^2.0|^3.0", - "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^3.6", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.4|^7.0" + "php": ">=8.2" }, "conflict": { - "doctrine/dbal": "<3.6", - "symfony/dependency-injection": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/var-dumper": "<6.4" + "symfony/http-foundation": "<6.4" + }, + "require-dev": { + "symfony/http-client": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Asset\\": "" + }, + "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": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset/tree/v7.3.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-03-05T10:15:41+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6", + "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" }, "provide": { "psr/cache-implementation": "2.0|3.0", @@ -2833,6 +3305,77 @@ ], "time": "2025-07-31T10:45:04+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37", + "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37", + "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.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:36:08+00:00" + }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.32.0", @@ -3078,34 +3621,441 @@ "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "000df7860439609837bbe28670b0be15783b7fbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", + "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.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-02-20T12:04:08+00:00" + }, + { + "name": "symfony/property-info", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "90586acbf2a6dd13bee4f09f09111c8bd4773970" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/90586acbf2a6dd13bee4f09f09111c8bd4773970", + "reference": "90586acbf2a6dd13bee4f09f09111c8bd4773970", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0", + "symfony/type-info": "~7.2.8|^7.3.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v7.3.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": "2025-06-27T19:55:54+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "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": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:36:08+00:00" + }, + { + "name": "symfony/runtime", + "version": "v7.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/runtime.git", + "reference": "9516056d432f8acdac9458eb41b80097da7a05c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/runtime/zipball/9516056d432f8acdac9458eb41b80097da7a05c9", + "reference": "9516056d432f8acdac9458eb41b80097da7a05c9", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": ">=8.2" + }, + "conflict": { + "symfony/dotenv": "<6.4" + }, + "require-dev": { + "composer/composer": "^2.6", + "symfony/console": "^6.4|^7.0", + "symfony/dotenv": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Runtime\\": "", + "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + }, + "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": "Enables decoupling PHP applications from global state", + "homepage": "https://symfony.com", + "keywords": [ + "runtime" + ], + "support": { + "source": "https://github.com/symfony/runtime/tree/v7.3.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": "2025-06-13T07:48:40+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" + "Symfony\\Contracts\\Service\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3122,16 +4072,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "description": "Generic abstractions related to writing services", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -3147,41 +4099,33 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { - "name": "symfony/polyfill-php84", - "version": "v1.32.0", + "name": "symfony/stopwatch", + "version": "v7.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "000df7860439609837bbe28670b0be15783b7fbf" + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", - "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" + "Symfony\\Component\\Stopwatch\\": "" }, - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3190,24 +4134,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" + "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" }, "funding": [ { @@ -3223,43 +4161,47 @@ "type": "tidelift" } ], - "time": "2025-02-20T12:04:08+00:00" + "time": "2025-02-24T10:49:57+00:00" }, { - "name": "symfony/routing", + "name": "symfony/string", "version": "v7.3.2", "source": { "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4" + "url": "https://github.com/symfony/string.git", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4", - "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/config": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/yaml": "<6.4" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "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\\Routing\\": "" + "Symfony\\Component\\String\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3271,24 +4213,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Maps an HTTP request to a set of configuration variables", + "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": [ - "router", - "routing", - "uri", - "url" + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.2" + "source": "https://github.com/symfony/string/tree/v7.3.2" }, "funding": [ { @@ -3308,47 +4252,41 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:36:08+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { - "name": "symfony/runtime", - "version": "v7.3.1", + "name": "symfony/translation-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/symfony/runtime.git", - "reference": "9516056d432f8acdac9458eb41b80097da7a05c9" + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/9516056d432f8acdac9458eb41b80097da7a05c9", - "reference": "9516056d432f8acdac9458eb41b80097da7a05c9", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0|^2.0", - "php": ">=8.2" - }, - "conflict": { - "symfony/dotenv": "<6.4" - }, - "require-dev": { - "composer/composer": "^2.6", - "symfony/console": "^6.4|^7.0", - "symfony/dotenv": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0" + "php": ">=8.1" }, - "type": "composer-plugin", + "type": "library", "extra": { - "class": "Symfony\\Component\\Runtime\\Internal\\ComposerPlugin" + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } }, "autoload": { "psr-4": { - "Symfony\\Component\\Runtime\\": "", - "Symfony\\Runtime\\Symfony\\Component\\": "Internal/" + "Symfony\\Contracts\\Translation\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3365,13 +4303,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Enables decoupling PHP applications from global state", + "description": "Generic abstractions related to translation", "homepage": "https://symfony.com", "keywords": [ - "runtime" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v7.3.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { @@ -3387,46 +4330,82 @@ "type": "tidelift" } ], - "time": "2025-06-13T07:48:40+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { - "name": "symfony/service-contracts", - "version": "v3.6.0", + "name": "symfony/twig-bridge", + "version": "v7.3.2", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "81d1c69769cf913240afdd4c9673304ddca964b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/81d1c69769cf913240afdd4c9673304ddca964b0", + "reference": "81d1c69769cf913240afdd4c9673304ddca964b0", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.21" }, "conflict": { - "ext-psr": "<1.1|>=2" + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.4", + "symfony/serializer": "<6.4", + "symfony/translation": "<6.4", + "symfony/workflow": "<6.4" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/emoji": "^7.1", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4.20|^7.2.5", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^7.3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/cssinliner-extra": "^3", + "twig/inky-extra": "^3", + "twig/markdown-extra": "^3" }, + "type": "symfony-bridge", "autoload": { "psr-4": { - "Symfony\\Contracts\\Service\\": "" + "Symfony\\Bridge\\Twig\\": "" }, "exclude-from-classmap": [ - "/Test/" + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3434,27 +4413,19 @@ "MIT" ], "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/twig-bridge/tree/v7.3.2" }, "funding": [ { @@ -3465,35 +4436,61 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-26T16:47:03+00:00" }, { - "name": "symfony/stopwatch", - "version": "v7.3.0", + "name": "symfony/twig-bundle", + "version": "v7.3.2", "source": { "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "5d85220df4d8d79e6a9ca57eea6f70004de39657" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/5d85220df4d8d79e6a9ca57eea6f70004de39657", + "reference": "5d85220df4d8d79e6a9ca57eea6f70004de39657", "shasum": "" }, "require": { + "composer-runtime-api": ">=2.1", "php": ">=8.2", - "symfony/service-contracts": "^2.5|^3" + "symfony/config": "^7.3", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^7.3", + "twig/twig": "^3.12" }, - "type": "library", + "conflict": { + "symfony/framework-bundle": "<6.4", + "symfony/translation": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "symfony-bundle", "autoload": { "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" + "Symfony\\Bundle\\TwigBundle\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3513,10 +4510,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a way to profile code", + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" + "source": "https://github.com/symfony/twig-bundle/tree/v7.3.2" }, "funding": [ { @@ -3527,52 +4524,46 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-24T10:49:57+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { - "name": "symfony/string", + "name": "symfony/type-info", "version": "v7.3.2", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" + "url": "https://github.com/symfony/type-info.git", + "reference": "b72d44c7d6638480fce101b7c4cd3abea4c2efba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", + "url": "https://api.github.com/repos/symfony/type-info/zipball/b72d44c7d6638480fce101b7c4cd3abea4c2efba", + "reference": "b72d44c7d6638480fce101b7c4cd3abea4c2efba", "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" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "symfony/translation-contracts": "<2.5" + "phpstan/phpdoc-parser": "<1.30" }, "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" + "phpstan/phpdoc-parser": "^1.30|^2.0" }, "type": "library", "autoload": { - "files": [ - "Resources/functions.php" - ], "psr-4": { - "Symfony\\Component\\String\\": "" + "Symfony\\Component\\TypeInfo\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3584,26 +4575,28 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.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", + "description": "Extracts PHP types information.", "homepage": "https://symfony.com", "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" + "PHPStan", + "phpdoc", + "symfony", + "type" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.2" + "source": "https://github.com/symfony/type-info/tree/v7.3.2" }, "funding": [ { @@ -3623,7 +4616,7 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:47:49+00:00" + "time": "2025-07-10T05:39:45+00:00" }, { "name": "symfony/var-dumper", @@ -3868,67 +4861,306 @@ } ], "time": "2025-07-10T08:47:49+00:00" - } - ], - "packages-dev": [ + }, { - "name": "nikic/php-parser", - "version": "v5.6.0", + "name": "twig/extra-bundle", + "version": "v3.21.0", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/62d1cf47a1aa009cbd07b21045b97d3d5cb79896", + "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/framework-bundle": "^5.4|^6.4|^7.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0", + "twig/twig": "^3.2|^4.0" + }, + "require-dev": { + "league/commonmark": "^1.0|^2.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "twig/cache-extra": "^3.0", + "twig/cssinliner-extra": "^3.0", + "twig/html-extra": "^3.0", + "twig/inky-extra": "^3.0", + "twig/intl-extra": "^3.0", + "twig/markdown-extra": "^3.0", + "twig/string-extra": "^3.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Twig\\Extra\\TwigExtraBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Symfony bundle for extra Twig extensions", + "homepage": "https://twig.symfony.com", + "keywords": [ + "bundle", + "extra", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.21.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2025-02-19T14:29:33+00:00" + }, + { + "name": "twig/twig", + "version": "v3.21.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "shasum": "" + }, + "require": { + "php": ">=8.1.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.21.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2025-05-03T07:21:55+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", "shasum": "" }, "require": { "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "zircote/swagger-php", + "version": "5.2.0", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "b82e160f9dcc9c5c5e59bf9a4904ed4a8ffa86a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/b82e160f9dcc9c5c5e59bf9a4904ed4a8ffa86a4", + "reference": "b82e160f9dcc9c5c5e59bf9a4904ed4a8ffa86a4", + "shasum": "" + }, + "require": { "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" + "nikic/php-parser": "^4.19 || ^5.0", + "php": ">=7.4", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": "^5.0 || ^6.0 || ^7.0", + "symfony/yaml": "^5.0 || ^6.0 || ^7.0" + }, + "conflict": { + "symfony/process": ">=6, <6.4.14" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "composer/package-versions-deprecated": "^1.11", + "doctrine/annotations": "^2.0", + "friendsofphp/php-cs-fixer": "^3.62.0", + "phpstan/phpstan": "^1.6 || ^2.0", + "phpunit/phpunit": "^9.0", + "rector/rector": "^1.0 || ^2.0", + "vimeo/psalm": "^4.30 || ^5.0" + }, + "suggest": { + "doctrine/annotations": "^2.0" }, "bin": [ - "bin/php-parse" + "bin/openapi" ], "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" + "OpenApi\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Nikita Popov" + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" } ], - "description": "A PHP parser written in PHP", + "description": "Generate interactive documentation for your RESTful API using PHP attributes (preferred) or PHPDoc annotations", + "homepage": "https://github.com/zircote/swagger-php", "keywords": [ - "parser", - "php" + "api", + "json", + "rest", + "service discovery" ], "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/5.2.0" }, - "time": "2025-07-27T20:03:57+00:00" - }, + "time": "2025-08-11T04:26:13+00:00" + } + ], + "packages-dev": [ { "name": "symfony/maker-bundle", "version": "v1.64.0", diff --git a/test-api/config/bundles.php b/test-api/config/bundles.php index de8898b23..008630c59 100644 --- a/test-api/config/bundles.php +++ b/test-api/config/bundles.php @@ -5,4 +5,7 @@ Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], + Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], ]; diff --git a/test-api/config/packages/nelmio_api_doc.yaml b/test-api/config/packages/nelmio_api_doc.yaml new file mode 100644 index 000000000..8813038d2 --- /dev/null +++ b/test-api/config/packages/nelmio_api_doc.yaml @@ -0,0 +1,11 @@ +nelmio_api_doc: + documentation: + info: + title: API de Investimentos + description: > + API REST para gerenciamento de investimentos.
+ Permite: criar investidor, criar investimento, visualizar investimento, resgatar investimento e listar os investimentos de um investidor. + version: 1.0.0 + areas: # to filter documented areas + path_patterns: + - ^/api(?!/doc$) # Accepts routes under /api except /api/doc diff --git a/test-api/config/packages/property_info.yaml b/test-api/config/packages/property_info.yaml new file mode 100644 index 000000000..dd31b9da2 --- /dev/null +++ b/test-api/config/packages/property_info.yaml @@ -0,0 +1,3 @@ +framework: + property_info: + with_constructor_extractor: true diff --git a/test-api/config/packages/twig.yaml b/test-api/config/packages/twig.yaml new file mode 100644 index 000000000..3f795d921 --- /dev/null +++ b/test-api/config/packages/twig.yaml @@ -0,0 +1,6 @@ +twig: + file_name_pattern: '*.twig' + +when@test: + twig: + strict_variables: true diff --git a/test-api/config/routes/nelmio_api_doc.yaml b/test-api/config/routes/nelmio_api_doc.yaml new file mode 100644 index 000000000..ed218c328 --- /dev/null +++ b/test-api/config/routes/nelmio_api_doc.yaml @@ -0,0 +1,12 @@ + +# JSON da especificação OpenAPI +app.swagger: + path: /api/doc.json + methods: GET + defaults: { _controller: nelmio_api_doc.controller.swagger } + +# Interface Swagger UI (Twig + Asset) +app.swagger_ui: + path: /api/doc + methods: GET + defaults: { _controller: nelmio_api_doc.controller.swagger_ui } \ No newline at end of file diff --git a/test-api/src/Controller/Api/InvestmentController.php b/test-api/src/Controller/Api/InvestmentController.php index 18ebdab06..786448abd 100644 --- a/test-api/src/Controller/Api/InvestmentController.php +++ b/test-api/src/Controller/Api/InvestmentController.php @@ -13,7 +13,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Annotation\Route; -use Symfony\Component\Validator\Validator\ValidatorInterface; +use OpenApi\Attributes as OA; +use Nelmio\ApiDocBundle\Attribute\Model; #[Route('/api/investments')] class InvestmentController extends AbstractController @@ -25,48 +26,104 @@ public function __construct( private InvestmentRepository $investmentRepository, ) {} - /** - * Verifica se a API está funcionando (Ping-Pong) - */ - #[Route('', methods: ['POST'])] - public function ping(): JsonResponse + #[Route('', methods: ['GET'])] + #[OA\Get( + path: "/api/investments", + summary: "Inicialmente implementado para verifica se a API está Online", + description: "Este endpoint retorna informações sobre o estado da API e suas URLs.", + responses: [ + new OA\Response( + response: 200, + description: "Retorna um JSON, com as seguintes informações:
+ status: Status da API
+ url_doc: Url para acessar a documentação pelo Swagger
+ url_json: Url para acessar a API em formato JSON", + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "status", type: "string"), + new OA\Property(property: "url_doc", type: "string"), + new OA\Property(property: "url_json", type: "string") + ] + ) + ) + ] + )] + public function index(Request $request): JsonResponse { return $this->json([ - "status" => "Success", - 'response' => "Ping-Pong API trabalhando!", - ], 201); + "status" => "Online", + "url_doc" => $request->getSchemeAndHttpHost() . $request->getBasePath() . "/api/doc", + "url_json" => $request->getSchemeAndHttpHost() . $request->getBasePath() . "/api/doc.json", + ], 200); } - /** - * Criar investidor (Owner) - */ #[Route('/newOwner', methods: ['POST'])] + #[OA\Post( + path: "/api/investments/newOwner", + summary: "Endpoint para registrar/cadastrar um novo investidor!", + description: "Este endpoint permite que um novo investidor seja registrado na API.
+ É necessário cadastrar um investidor para fazer uso dos proximos endpoints relacionados a investimentos.", + requestBody: new OA\RequestBody( + required: true, + description: "Os campos name e email são obrigatórios.
+ Os campos abaixo devem ser passados no Body da requisição.", + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "name", type: "string", description: "Nome do investidor", example: "Luciano Meneses"), + new OA\Property(property: "email", type: "string", description: "Email do investidor", example: "luciano@teste.com") + ] + ) + ), + responses: [ + new OA\Response( + response: 201, + description: "

Cadastrado com sucesso, um JSON será retornado com as seguintes informações:


+ status: Status da operação
+ id: Id do investidor
+ name: Nome do investidor
+ email: Email do investidor", + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "status", type: "string", example: "Success"), + new OA\Property(property: "id", type: "integer", example: 1), + new OA\Property(property: "name", type: "string", example: "Luciano Meneses"), + new OA\Property(property: "email", type: "string", example: "luciano@teste.com"), + ] + ) + ), + new OA\Response( + response: 428, + description: "

Erro na operação, um JSON será retornado com as seguintes informações:


+ status: Status da operação
+ message: Mensagem de erro
", + content: new OA\JsonContent( + allOf: [ + new OA\Schema( + properties: [ + new OA\Property(property: "status", type: "string", example: "Error"), + new OA\Property(property: "message", type: "string", example: "Atenção : Nome e email são obrigatórios!"), + ] + ) + ] + ) + ) + ] + )] public function new_owner(Request $request): JsonResponse { try { - $data = json_decode($request->getContent(), true); - if ($data === null) { - $data = $request->request->all(); + $data = json_decode($request->getContent(), true) ?: $request->request->all(); + if (empty($data['name']) || empty($data['email'])) { + throw new \InvalidArgumentException('Nome e email são obrigatórios!'); } - // Validar nome do investidor - if (empty($data['name'])) { - throw new \InvalidArgumentException('Nome do investidor é obrigatório!'); - } - - // Validar email do investidor - if (empty($data['email'])) { - throw new \InvalidArgumentException('Email do investidor é obrigatório!'); - } - - // Criar investimento $owner = new Owner(); - - // Seta parametros $owner->setName($data['name']); $owner->setEmail($data['email']); - - // Persistir + $em = $this->doctrine->getManager(); $em->persist($owner); $em->flush(); @@ -77,73 +134,100 @@ public function new_owner(Request $request): JsonResponse 'name' => $owner->getName(), 'email' => $owner->getEmail(), ], 201); + } catch (\Exception $e) { - return $this->json([ - 'status' => 'error', - 'message' => 'Atenção : ' . $e->getMessage() - ], 428); + return $this->json(['status' => 'Error','message' => 'Atenção : ' . $e->getMessage()], 428); } } - /** - * Criar um novo investimento - */ #[Route('/create', methods: ['POST'])] + #[OA\Post( + path: "/api/investments/create", + summary: "Endpoint para registrar/cadastrar um novo investimento de um investidor!", + description: "Este endpoint permite que um novo investimento seja registrado na API.
+ É necessário cadastrar um investimento para fazer uso dos proximos endpoints relacionados as consultas e resgate de investimentos.", + requestBody: new OA\RequestBody( + required: true, + description: "Os campos ownerId, initialValue e createdAt são obrigatórios.
+ Para o campo initialValue, será permitido somente valores acima de 0, não é permitido valores negativos ou zerados.
+ Para o campo createdAt, será permitido data atual ou uma data do passado, não é permitida datas futuras.
+ Os campos abaixo devem ser passados no Body da requisição.", + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "ownerId", type: "integer", description: "ID do investidor", example: 1), + new OA\Property(property: "initialValue", type: "number", description: "Valor inicial do investimento", example: 1000.00), + new OA\Property(property: "createdAt", type: "string", format: "date", description: "Data de criação do investimento", example: "2023-01-01"), + ] + ) + ), + responses: [ + new OA\Response( + response: 201, + description: "

Cadastrado com sucesso, um JSON será retornado com as seguintes informações:


+ status: Status da operação
+ id: Id do investimento
+ ownerId: Id do investidor
+ creatAt: Data de criação do investimento
+ initialValue: Valor do investimento", + + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "status", type: "string", example: "Success"), + new OA\Property(property: "id", type: "integer", example: 1), + new OA\Property(property: "ownerId", type: "integer", example: 1), + new OA\Property(property: "createdAt", type: "string", example: "2025-08-13"), + new OA\Property(property: "initialValue", type: "string", example: "1000"), + ] + ) + ), + new OA\Response( + response: 428, + description: "

Erro na operação, um JSON será retornado com as seguintes informações:


+ status: Status da operação
+ message: Mensagem de erro
", + content: new OA\JsonContent( + allOf: [ + new OA\Schema( + properties: [ + new OA\Property(property: "status", type: "string", example: "Error"), + new OA\Property(property: "message", type: "string", example: "Atenção : Todos os campos são obrigatórios!"), + ] + ) + ] + ) + ) + ] + )] public function create(Request $request): JsonResponse { try { - $data = json_decode($request->getContent(), true); - if ($data === null) { - $data = $request->request->all(); - } - - // Validar se owner foi informado - if (empty($data['ownerId'])) { - throw new \InvalidArgumentException('Id do investidor é obrigatório!'); + $data = json_decode($request->getContent(), true) ?: $request->request->all(); + if (empty($data['ownerId']) || empty($data['initialValue']) || empty($data['createdAt'])) { + throw new \InvalidArgumentException('Todos os campos são obrigatórios!'); } - // Verificar owner existe $owner = $this->ownerRepository->find($data['ownerId']); if (!$owner) { throw new \InvalidArgumentException('Investidor não encontrado!'); - } - - // Validar se valor foi informado - if (empty($data['initialValue'])) { - throw new \InvalidArgumentException('Valor inicial é obrigatório!'); - } + } - // Valor inicial >= 0 $initialValue = (float) $data['initialValue']; if ($initialValue <= 0) { throw new \InvalidArgumentException('Valor inicial inválido!'); } - // Verificar se a data foi informada - if(!isset($data['createdAt']) || empty($data['createdAt'])) { - throw new \InvalidArgumentException('Data de inicio do investimento não informado!'); - } - - // Verificar se a data é válida $date = DateTime::createFromFormat('Y-m-d', $data['createdAt']); - if (!$date || $date->format('Y-m-d') !== $data['createdAt']) { - throw new \InvalidArgumentException('Data de inicio do investimento inválida!'); - } - - // Verificar se é data futura - if ($date > new DateTime('now')) { - throw new \InvalidArgumentException('Data futura não é permitida, somente data atual ou data do passado!'); + if (!$date || $date > new DateTime('now')) { + throw new \InvalidArgumentException('Data inválida!'); } - // Criar investimento $investment = new Investment(); - - // Seta parametros $investment->setOwner($owner); $investment->setInitialValue($initialValue); $investment->setCreatedAt($date); - - // Persistir + $em = $this->doctrine->getManager(); $em->persist($investment); $em->flush(); @@ -157,72 +241,113 @@ public function create(Request $request): JsonResponse ], 201); } catch (\Exception $e) { - return $this->json([ - 'status' => 'error', - 'message' => 'Atenção : ' . $e->getMessage() - ], 428); + return $this->json(['status' => 'Error','message' => 'Atenção : ' . $e->getMessage()], 428); } } - /** - * Visualizar detalhes de um investimento com saldo apurado - */ #[Route('/show/{id}', methods: ['POST'])] + #[OA\Post( + path: "/api/investments/show/{id}", + summary: "Endpoint para visualizar um determinado investimento de um investidor!", + description: "Este endpoint permite visualizar detalhes de um investimento na API.
+ É necessário informar o id do investimento para obter os detalhes.", + parameters: [ + new OA\Parameter(name: "id", in: "path", required: true, schema: new OA\Schema(type: "integer")) + ], + requestBody: new OA\RequestBody( + required: false, + description: "Os campos redeemDate, não é obrigatório, se não informar, assume a data atual.
+ No campo redeemDate, será permitido uma data entre a data da criação ate a data atual.
+ Investimentos resgatados, redeemDate assumira a data do resgate.
+ O campo abaixo devem ser passados no Body da requisição.", + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "redeemDate", type: "string", format: "date", description: "Data resgate do investimento", example: "2023-01-01"), + ] + ) + ), + responses: [ + new OA\Response( + response: 200, + description: "

Visualização de um investimento, um JSON será retornado com as seguintes informações:


+ status: Status da operação
+ investment: Id unico do investimento
+ ownerId: Id unico do investidor
+ createdAt: Data em que o investimento foi criado
+ initialValue: Valor inicial do investimento
+ redeemed: Booleano indicando se o investimento foi resgatado
+ redeemedAt: Data usada como base de cálculo do resgate
+ gross: Valor bruto do investimento (em relação a data base)
+ profit: Lucro do investimento (em relação a data base)
+ tax: Imposto do investimento (em relação a data base)
+ redeemedValue: Valor líquido resgatado do investimento (em relação a data base)
", + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "status", type: "string", example: "Success"), + new OA\Property(property: "investment", type: "integer", example: 1), + new OA\Property(property: "ownerId", type: "integer", example: 1), + new OA\Property(property: "createdAt", type: "string", example: "2020-08-13"), + new OA\Property(property: "initialValue", type: "string", example: "1200.00"), + new OA\Property(property: "redeemed", type: "string", example: "false"), + new OA\Property(property: "redeemedAt", type: "string", example: "2021-08-13"), + new OA\Property(property: "gross", type: "string", example: "1638.06"), + new OA\Property(property: "profit", type: "string", example: "438.06"), + new OA\Property(property: "tax", type: "string", example: "65.71"), + new OA\Property(property: "redeemedValue", type: "string", example: "1572.35"), + ] + ) + ), + new OA\Response( + response: 428, + description: "

Erro na operação, um JSON será retornado com as seguintes informações:


+ status: Status da operação
+ message: Mensagem de erro
", + content: new OA\JsonContent( + allOf: [ + new OA\Schema( + properties: [ + new OA\Property(property: "status", type: "string", example: "Error"), + new OA\Property(property: "message", type: "string", example: "Atenção : Investimento não encontrado!"), + ] + ) + ] + ) + ) + ] + )] public function show(int $id, Request $request): JsonResponse { try { - // Verificar se o id foi informado + $data = json_decode($request->getContent(), true) ?: $request->request->all(); + if (empty($id) || $id <= 0) { - throw new \InvalidArgumentException('Id do investimento inválido!'); + throw new \InvalidArgumentException('Id inválido!'); } - // Verificar se o investimento existe $investment = $this->investmentRepository->find($id); if (!$investment) { - return $this->json(['error' => 'Investimento não encontrado'], 404); + throw new \InvalidArgumentException('Investimento não encontrado!'); } - // Verifica se o investimento já foi resgatado - $redeemedAt = $investment->getRedeemedAt(); - if ($redeemedAt !== null) { - return $this->json([ - 'Status' => 'Success', - 'id' => $investment->getId(), - 'ownerId' => $investment->getOwner()->getId(), - 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), - 'initialValue' => $investment->getInitialValue(), - 'redeemed' => $investment->getRedeemedAt() !== null, - 'redeemedAt' => $investment->getRedeemedAt()?->format('Y-m-d'), - 'redeemedValue' => $investment->getRedeemedValue(), - ], 201); + $redeemed = $investment->getRedeemedAt() !== null; + if($redeemed) { + $data['redeemDate'] = $investment->getRedeemedAt()->format('Y-m-d'); } - $data = json_decode($request->getContent(), true); - if ($data === null) { - $data = $request->request->all(); + if(!$redeemed && empty($data['redeemDate'])) { + $data['redeemDate'] = (new DateTime('now'))->format('Y-m-d'); } - // caso a data nao for passada pega o data atual - if(!isset($data['redeemDate']) || empty($data['redeemDate'])) { - $data['redeemDate'] = date('Y-m-d'); - } - - // Verificar se a data é válida $date = DateTime::createFromFormat('Y-m-d', $data['redeemDate']); - if (!$date || $date->format('Y-m-d') !== $data['redeemDate']) { - throw new \InvalidArgumentException('Data de resgate do investimento inválida!'); - } - - // Verificar se a data é futura ou anterior à data de criação do investimento - if ($date > new DateTime('now') || $date < $investment->getCreatedAt() ) { - throw new \InvalidArgumentException('Data não permitida, somente data posterior a data do investimento até a data atual!'); + if (!$date || $date > new DateTime('now') || $date < $investment->getCreatedAt()) { + throw new \InvalidArgumentException('Data inválida!'); } - + $result = $this->investmentService->redeemInvestment($investment, $date); - - // Atualiza investimento para registrar/salvar o resgate + $investment->setRedeemedAt($date); - $investment->setRedeemedValue($result['net']); return $this->json([ 'status' => 'Success', @@ -230,77 +355,113 @@ public function show(int $id, Request $request): JsonResponse 'ownerId' => $investment->getOwner()->getId(), 'createdAt' => $investment->getCreatedAt()->format('Y-m-d'), 'initialValue' => $investment->getInitialValue(), - 'redeemed' => false, - 'redeemedAt' => $investment->getRedeemedAt()?->format('Y-m-d'), - 'gross' => $result['gross'], - 'profit' => $result['profit'], - 'tax' => $result['tax'], - 'redeemedValue' => $result['net'], + 'redeemed' => $redeemed, + 'redeemedAt' => $investment->getRedeemedAt()->format('Y-m-d'), + 'gross' => $redeemed ? null : $result['gross'], + 'profit' => $redeemed ? null : $result['profit'], + 'tax' => $redeemed ? null : $result['tax'], + 'redeemedValue' => $redeemed ? $investment->getRedeemedValue() : $result['net'], ], 200); } catch (\Exception $e) { - return $this->json([ - 'status' => 'error', - 'message' => 'Atenção : ' . $e->getMessage() - ], 428); + return $this->json(['status' => 'Error','message' => 'Atenção : ' . $e->getMessage()], 428); } } - private function showRendiment() - { - return []; - } - - /** - * Resgatar um investimento - */ #[Route('/redeem/{id}', methods: ['POST'])] + #[OA\Post( + path: "/api/investments/redeem/{id}", + summary: "Endpoint para efetuar o resgate de um determinado investimento!", + description: "Este endpoint permite efetuar o resgate de um investimento na API.
+ É necessário informar o id do investimento para efetuar o resgate e passar parametros no BODY.", + parameters: [ + new OA\Parameter(name: "id", in: "path", required: true, schema: new OA\Schema(type: "integer")) + ], + requestBody: new OA\RequestBody( + required: true, + description: "Os campos redeemDate, é obrigatório.
+ É permitido uma data entre a data da criação do investimento até a data atual no campo redeemDate,.
+ O campo abaixo devem ser passados no Body da requisição.", + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "redeemDate", type: "string", format: "date", description: "Data resgate do investimento", example: "2023-01-01"), + ] + ) + ), + responses: [ + new OA\Response( + response: 200, + description: "

Resgate de um investimento, um JSON será retornado com as seguintes informações:


+ status: Status da operação
+ investment: Id do investimento
+ ownerId: Id do investidor
+ createdAt: Data de criação do investimento
+ initialValue: Valor inicial do investimento
+ redeemed: Indicando se o investimento foi resgatado
+ redeemedAt: Data usada como base de cálculo do resgate
+ gross: Valor bruto do investimento (em relação a data base)
+ profit: Lucro do investimento (em relação a data base)
+ tax: Imposto do investimento (em relação a data base)
+ redeemedValue: Valor líquido resgatado do investimento (em relação a data base)
", + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "status", type: "string", example: "Success"), + new OA\Property(property: "investment", type: "integer", example: 1), + new OA\Property(property: "ownerId", type: "integer", example: 1), + new OA\Property(property: "createdAt", type: "string", example: "2020-08-13"), + new OA\Property(property: "initialValue", type: "string", example: "1200.00"), + new OA\Property(property: "redeemed", type: "string", example: "false"), + new OA\Property(property: "redeemedAt", type: "string", example: "2021-08-13"), + new OA\Property(property: "gross", type: "string", example: "1638.06"), + new OA\Property(property: "profit", type: "string", example: "438.06"), + new OA\Property(property: "tax", type: "string", example: "65.71"), + new OA\Property(property: "redeemedValue", type: "string", example: "1572.35"), + ] + ) + ), + new OA\Response( + response: 428, + description: "

Erro na operação, um JSON será retornado com as seguintes informações:


+ status: Status da operação
+ message: Mensagem de erro
", + content: new OA\JsonContent( + allOf: [ + new OA\Schema( + properties: [ + new OA\Property(property: "status", type: "string", example: "Error"), + new OA\Property(property: "message", type: "string", example: "Atenção : Investimento não encontrado!"), + ] + ) + ] + ) + ) + ] + )] public function redeem(int $id, Request $request): JsonResponse { try { - - // Verificar se o investimento existe $investment = $this->investmentRepository->find($id); - if (!$investment) { - throw new \InvalidArgumentException('Investimento não encontrado!'); - } - - // Verificar se o investimento já foi resgatado - if ($investment->getRedeemedAt() !== null) { - throw new \InvalidArgumentException('Investimento já resgatado.'); - } - - $data = json_decode($request->getContent(), true); - if ($data === null) { - $data = $request->request->all(); + if (!$investment || $investment->getRedeemedAt() !== null) { + throw new \InvalidArgumentException('Investimento inválido ou já resgatado'); } - - // Verificar se a data foi informada - if(!isset($data['redeemDate']) || empty($data['redeemDate'])) { - throw new \InvalidArgumentException('A data de resgate do investimento não informado! (redeemDate)'); + + $data = json_decode($request->getContent(), true) ?: $request->request->all(); + if (!isset($data['redeemDate'])) { + throw new \InvalidArgumentException('Data de resgate obrigatória'); } - // Verificar se a data é válida $date = DateTime::createFromFormat('Y-m-d', $data['redeemDate']); - if (!$date || $date->format('Y-m-d') !== $data['redeemDate']) { - throw new \InvalidArgumentException('Data de resgate do investimento inválida!'); - } - - // Verificar se a data é futura ou anterior à data de criação do investimento - if ($date > new DateTime('now') || $date < $investment->getCreatedAt() ) { - throw new \InvalidArgumentException('Data não permitida, somente data posterior a data do investimento até a data atual!'); - } $result = $this->investmentService->redeemInvestment($investment, $date); - // Atualiza investimento para registrar/salvar o resgate $investment->setRedeemedAt($date); $investment->setRedeemedValue($result['net']); - - // Persistir atualização do investimento (resgate) + $em = $this->doctrine->getManager(); $em->persist($investment); $em->flush(); - + return $this->json([ 'status' => 'Success', 'investment' => $investment->getId(), @@ -315,23 +476,118 @@ public function redeem(int $id, Request $request): JsonResponse ], 200); } catch (\Exception $e) { - return $this->json([ - 'status' => 'error', - 'message' => 'Atenção : ' . $e->getMessage() - ], 428); + return $this->json(['status' => 'Error','message' => 'Atenção : ' . $e->getMessage()], 428); } } - /** - * Listar investimentos de um proprietário com paginação - */ #[Route('/owner/{ownerId}', methods: ['POST'])] + #[OA\Post( + path: "/api/investments/owner/{ownerId}", + summary: "Endpoint para efetuar a consulta de todos os investimentos de um investidor!", + description: "Este endpoint permite buscar todos os investimentos de um investidor na API.
+ É necessário informar o ownerId do investidor para efetuar a consulta e passar parametros no BODY.", + parameters: [ + new OA\Parameter(name: "ownerId", in: "path", required: true, schema: new OA\Schema(type: "integer")) + ], + requestBody: new OA\RequestBody( + required: true, + description: "Os campos page e limit são obrigatórios.
+ O campo page vai setar a pagina que será apresentada.
+ O campo limit vai setar a quantidade de itens que será apresentada por pagina.
+ O campo abaixo devem ser passados no Body da requisição.", + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "page", type: "integer", description: "Número da página para paginação", example: 1), + new OA\Property(property: "limit", type: "integer", description: "Número de itens por página", example: 100), + ] + ) + ), + responses: [ + new OA\Response( + response: 200, + description: "

Resgate de um investimento, um JSON será retornado com as seguintes informações:


+ status: Status da operação
+ ownerId: Id do investidor
+ owner: Nome do investidor
+ totalInvestment: Total de investimentos do investidor
+ totalPages: Total de páginas disponíveis
+ page: Página atual
+ limit: Limite de itens por página
+ investments: Lista de investimentos do investidor

+

Na lista investments, um vetor com objetos JSON será retornado com as seguintes informações:


+ id: Id do investimento
+ createdAt: Data de criação do investimento
+ initialValue: Valor inicial do investimento
+ redeemed: Indicando se o investimento foi resgatado
+ expectedBalance: Valor esperado do investimento
", + content: new OA\JsonContent( + type: "object", + properties: [ + new OA\Property(property: "status", type: "string", example: "Success"), + new OA\Property(property: "ownerId", type: "integer", example: 1), + new OA\Property(property: "owner", type: "string", example: "Luciano Meneses"), + new OA\Property(property: "totalInvestment", type: "integer", example: 3), + new OA\Property(property: "totalPages", type: "integer", example: 3), + new OA\Property(property: "page", type: "integer", example: 1), + new OA\Property(property: "limit", type: "integer", example: 100), + new OA\Property( + property: "listaInvestments", + type: "array", + example: [ + [ + "id" => 1, + "createdAt" => "2020-08-13", + "initialValue" => "1200.00", + "redeemed" => false, + "expectedBalance" => "1638.06" + ], + [ + "id" => 2, + "createdAt" => "2021-01-10", + "initialValue" => "1500.00", + "redeemed" => true, + "expectedBalance" => "1700.00" + ] + ], + items: new OA\Items( + type: "object", + properties: [ + new OA\Property(property: "id", type: "integer"), + new OA\Property(property: "createdAt", type: "string"), + new OA\Property(property: "initialValue", type: "string"), + new OA\Property(property: "redeemed", type: "boolean"), + new OA\Property(property: "expectedBalance", type: "string") + ] + ) + ) + ] + ) + ), + new OA\Response( + response: 428, + description: "

Erro na operação, um JSON será retornado com as seguintes informações:


+ status: Status da operação
+ message: Mensagem de erro
", + content: new OA\JsonContent( + allOf: [ + new OA\Schema( + properties: [ + new OA\Property(property: "status", type: "string", example: "Error"), + new OA\Property(property: "message", type: "string", example: "Atenção : Investimento não encontrado!"), + ] + ) + ] + ) + ) + ] + )] public function listByOwner(int $ownerId, Request $request): JsonResponse { try { - $data = json_decode($request->getContent(), true); - if ($data === null) { - $data = $request->request->all(); + $data = json_decode($request->getContent(), true) ?: $request->request->all(); + if (empty($ownerId) || $ownerId <= 0) { + throw new \InvalidArgumentException('Id inválido!'); } $owner = $this->ownerRepository->find($ownerId); @@ -343,24 +599,22 @@ public function listByOwner(int $ownerId, Request $request): JsonResponse $limit = empty($data['limit']) ? 100 : (int)$data['limit']; $offset = ($page - 1) * $limit; - $repo = $this->investmentRepository; - - $total = (int) $repo->createQueryBuilder('i') + $total = (int) $this->investmentRepository->createQueryBuilder('i') ->select('COUNT(i.id)') ->andWhere('i.owner = :owner') ->setParameter('owner', $owner) ->getQuery() ->getSingleScalarResult(); - $res = $repo->createQueryBuilder('i') + $investments = $this->investmentRepository->createQueryBuilder('i') ->andWhere('i.owner = :owner') ->setParameter('owner', $owner) ->setFirstResult($offset) ->setMaxResults($limit) - ->orderBy('i.createdAt', 'DESC'); - - $investments = $res->getQuery()->getResult(); - + ->orderBy('i.createdAt', 'DESC') + ->getQuery() + ->getResult(); + $listaInvestments = []; foreach ($investments as $investment) { $balance = $this->investmentService->calculateExpectedBalance($investment); @@ -384,12 +638,8 @@ public function listByOwner(int $ownerId, Request $request): JsonResponse 'offset' => $offset, 'listaInvestments' => $listaInvestments, ], 200); - } catch (\Exception $e) { - return $this->json([ - 'status' => 'error', - 'message' => 'Atenção : ' . $e->getMessage() - ], 428); + return $this->json(['status' => 'Error','message' => 'Atenção : ' . $e->getMessage()], 428); } } } diff --git a/test-api/symfony.lock b/test-api/symfony.lock index e5d8b1387..358e8c0f7 100644 --- a/test-api/symfony.lock +++ b/test-api/symfony.lock @@ -35,6 +35,19 @@ "migrations/.gitignore" ] }, + "nelmio/api-doc-bundle": { + "version": "5.5", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "3.0", + "ref": "c8e0c38e1a280ab9e37587a8fa32b251d5bc1c94" + }, + "files": [ + "config/packages/nelmio_api_doc.yaml", + "config/routes/nelmio_api_doc.yaml" + ] + }, "symfony/console": { "version": "7.3", "recipe": { @@ -89,6 +102,18 @@ "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" } }, + "symfony/property-info": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.3", + "ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7" + }, + "files": [ + "config/packages/property_info.yaml" + ] + }, "symfony/routing": { "version": "7.3", "recipe": { @@ -101,5 +126,21 @@ "config/packages/routing.yaml", "config/routes.yaml" ] + }, + "symfony/twig-bundle": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877" + }, + "files": [ + "config/packages/twig.yaml", + "templates/base.html.twig" + ] + }, + "twig/extra-bundle": { + "version": "v3.21.0" } } diff --git a/test-api/templates/base.html.twig b/test-api/templates/base.html.twig new file mode 100644 index 000000000..1069c1476 --- /dev/null +++ b/test-api/templates/base.html.twig @@ -0,0 +1,16 @@ + + + + + {% block title %}Welcome!{% endblock %} + + {% block stylesheets %} + {% endblock %} + + {% block javascripts %} + {% endblock %} + + + {% block body %}{% endblock %} + + From d9d7a79ad39d3dfc383b02b8a973f2f5e80c5b31 Mon Sep 17 00:00:00 2001 From: lucianoarm Date: Thu, 14 Aug 2025 10:28:02 -0300 Subject: [PATCH 7/8] Atualizando o README.md --- test-api/README.md | 95 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/test-api/README.md b/test-api/README.md index adbb530b7..9b63f894f 100644 --- a/test-api/README.md +++ b/test-api/README.md @@ -1,26 +1,85 @@ -# SCRIPT SQL para iniciar o Banco de Dados da API --- Remove o banco se já existir -DROP DATABASE IF EXISTS `db_test_api`; +# API de Investimentos --- Cria o banco -CREATE DATABASE `db_test_api` - DEFAULT CHARACTER SET utf8mb4 - COLLATE utf8mb4_unicode_ci; +API REST em **Symfony 7** para gerenciamento de investidores, investimentos e resgates. +Utiliza **Doctrine ORM** para persistência de dados e **NelmioApiDocBundle** para documentação interativa no formato OpenAPI/Swagger. --- Remove o usuário (se já existir, remove também privilégios) -DROP USER IF EXISTS 'usuario_api'@'localhost'; +## Tecnologias principais --- Cria o usuário novamente (host = localhost) -CREATE USER 'usuario_api'@'localhost' IDENTIFIED BY '.!usuarioApi2025!.'; +- **PHP 8.3+** +- **Symfony 7** +- **Doctrine ORM** +- **MySQL** +- **Swagger/OpenAPI** --- Concede privilégios completos no banco criado -GRANT ALL PRIVILEGES ON `db_test_api`.* TO 'usuario_api'@'localhost'; +--- --- Aplica mudanças -FLUSH PRIVILEGES; +## Bibliotecas de terceiros adicionadas -# Instruções especiais para copilação +| Biblioteca | Versão | Descrição no Projeto | +|-----------------------------------------|--------|--------------------------------------------------------------------------------------------------| +| **doctrine/dbal** | 3.10.1 | Camada de abstração de banco usada pelo Doctrine ORM para consultas SQL e manipulação do schema. | +| **doctrine/doctrine-bundle** | 2.15.1 | Integra o Doctrine ORM ao Symfony, lendo configs do `doctrine.yaml`. | +| **doctrine/doctrine-migrations-bundle** | 3.4.2 | Permite criar e rodar migrations para atualizar o schema do banco. | +| **doctrine/orm** | 3.5.2 | Mapeia entidades PHP para tabelas no banco de dados. | +| **nelmio/api-doc-bundle** | 5.5.0 | Gera documentação interativa dos endpoints usando Swagger UI. | +| +--- -# Bibliotecar de terceiros Utilizadas (Porque utilizou e como foram usadas) +## Configuração e Compilação -# Link Para a documentaçãoda API () \ No newline at end of file +**Pré-requisitos :** +- PHP 8.3+ +- Composer 2.x +- MySQL ou outro banco suportado +- Extensões PHP: `pdo_mysql`, `mbstring`, `xml`, `intl`, `ctype`, `tokenizer` + +### Clonar o projeto + +> git clone https://github.com/lucianoarm/backend-test.git +> cd backend-test/test-api + +### Instalar dependências + +> composer install + +### Configurar variáveis de ambiente + +> **Copie o arquivo .env para .env.local e ajuste :** +> DATABASE_URL="mysql://usuario:senha@127.0.0.1:3306/investimentos" +> APP_ENV=dev +> APP_SECRET=algumasecret + +### Criar banco de dados e aplicar migrations + +> php bin/console doctrine:database:create +> php bin/console doctrine:migrations:migrate + +### Rodar servidor local + +> symfony server:start + +API em: http://127.0.0.1:8000 <- esta url será usada para acessar a documnetação interativa +--- + +## Documentação da API + +> **A documentação interativa gerada pelo Swagger está disponível em :** + +> - http://127.0.0.1:8000/api/doc + +> A url e porta neste exemplo é a mesma retornada no passo anterior. +> Neste documentação será possível testar os endpoints diretamente pelo navegador. + +## Estrutura do Projeto + +src/ + ├── Controller/ + | └── Api/ → Endpoints da API + ├── Entity/ → Entidades Doctrine + ├── Repository/ → Repositórios de dados + ├── Service/ → Calculo/Lógica de negócio +config/ + ├── packages/ → Configuração de bundles + ├── routes/ → Arquivos de rotas +public/ + └── index.php → Ponto de entrada da aplicação From 8ea0b0ba64b22a6f826794b95efcf3bd422f5aba Mon Sep 17 00:00:00 2001 From: lucianoarm Date: Thu, 14 Aug 2025 14:07:43 -0300 Subject: [PATCH 8/8] =?UTF-8?q?Implementa=C3=A7=C3=A3o=20do=20disparo=20de?= =?UTF-8?q?=20Email?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test-api/README.md | 6 +- test-api/compose.override.yaml | 11 + test-api/composer.json | 1 + test-api/composer.lock | 324 +++++++++++++++++- test-api/config/packages/mailer.yaml | 3 + .../Controller/Api/InvestmentController.php | 79 ++++- test-api/src/Service/EmailService.php | 33 ++ test-api/symfony.lock | 12 + test-api/templates/emails/default.html.twig | 4 + 9 files changed, 469 insertions(+), 4 deletions(-) create mode 100644 test-api/config/packages/mailer.yaml create mode 100644 test-api/src/Service/EmailService.php create mode 100644 test-api/templates/emails/default.html.twig diff --git a/test-api/README.md b/test-api/README.md index 9b63f894f..1c8228e79 100644 --- a/test-api/README.md +++ b/test-api/README.md @@ -22,7 +22,7 @@ Utiliza **Doctrine ORM** para persistência de dados e **NelmioApiDocBundle** pa | **doctrine/doctrine-migrations-bundle** | 3.4.2 | Permite criar e rodar migrations para atualizar o schema do banco. | | **doctrine/orm** | 3.5.2 | Mapeia entidades PHP para tabelas no banco de dados. | | **nelmio/api-doc-bundle** | 5.5.0 | Gera documentação interativa dos endpoints usando Swagger UI. | -| +| **symfony/mailer** | 7.3.x | Envio de emails a partir da aplicação, usado no serviço de notificação. | --- ## Configuração e Compilação @@ -49,6 +49,10 @@ Utiliza **Doctrine ORM** para persistência de dados e **NelmioApiDocBundle** pa > APP_ENV=dev > APP_SECRET=algumasecret +> **symfony/mailer** +> MAILER_DSN=smtp://seu_usuario:sua_senha@smtp.gmail.com:587 +> EMAIL_TO=email_destino_aviso@gmail.com + ### Criar banco de dados e aplicar migrations > php bin/console doctrine:database:create diff --git a/test-api/compose.override.yaml b/test-api/compose.override.yaml index c5612b0ad..8dc54dec2 100644 --- a/test-api/compose.override.yaml +++ b/test-api/compose.override.yaml @@ -5,3 +5,14 @@ services: ports: - "5432" ###< doctrine/doctrine-bundle ### + +###> symfony/mailer ### + mailer: + image: axllent/mailpit + ports: + - "1025" + - "8025" + environment: + MP_SMTP_AUTH_ACCEPT_ANY: 1 + MP_SMTP_AUTH_ALLOW_INSECURE: 1 +###< symfony/mailer ### diff --git a/test-api/composer.json b/test-api/composer.json index 9a133ecf2..2d95d4d6e 100644 --- a/test-api/composer.json +++ b/test-api/composer.json @@ -17,6 +17,7 @@ "symfony/dotenv": "7.3.*", "symfony/flex": "^2", "symfony/framework-bundle": "7.3.*", + "symfony/mailer": "7.3.*", "symfony/runtime": "7.3.*", "symfony/twig-bundle": "7.3.*", "symfony/yaml": "7.3.*", diff --git a/test-api/composer.lock b/test-api/composer.lock index ca115acde..3f671fc7e 100644 --- a/test-api/composer.lock +++ b/test-api/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": "7aa52568df162536a32450abc587b517", + "content-hash": "1fe3aafa1e3c2a1d22a1b275d73a973b", "packages": [ { "name": "doctrine/collections", @@ -1132,6 +1132,73 @@ }, "time": "2025-01-24T11:45:48+00:00" }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" + }, { "name": "nelmio/api-doc-bundle", "version": "v5.5.0", @@ -3305,6 +3372,178 @@ ], "time": "2025-07-31T10:45:04+00:00" }, + { + "name": "symfony/mailer", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/d43e84d9522345f96ad6283d5dfccc8c1cfc299b", + "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/mime": "^7.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "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": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:36:08+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1", + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "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": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T13:41:35+00:00" + }, { "name": "symfony/options-resolver", "version": "v7.3.2", @@ -3454,6 +3693,89 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.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-10T14:38:51+00:00" + }, { "name": "symfony/polyfill-intl-normalizer", "version": "v1.32.0", diff --git a/test-api/config/packages/mailer.yaml b/test-api/config/packages/mailer.yaml new file mode 100644 index 000000000..56a650d89 --- /dev/null +++ b/test-api/config/packages/mailer.yaml @@ -0,0 +1,3 @@ +framework: + mailer: + dsn: '%env(MAILER_DSN)%' diff --git a/test-api/src/Controller/Api/InvestmentController.php b/test-api/src/Controller/Api/InvestmentController.php index 786448abd..f09f4c760 100644 --- a/test-api/src/Controller/Api/InvestmentController.php +++ b/test-api/src/Controller/Api/InvestmentController.php @@ -7,6 +7,7 @@ use App\Repository\InvestmentRepository; use App\Repository\OwnerRepository; use App\Service\InvestmentService; +use App\Service\EmailService; use DateTime; use Doctrine\Persistence\ManagerRegistry; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -21,6 +22,7 @@ class InvestmentController extends AbstractController { public function __construct( private InvestmentService $investmentService, + private EmailService $emailService, private ManagerRegistry $doctrine, private OwnerRepository $ownerRepository, private InvestmentRepository $investmentRepository, @@ -51,6 +53,16 @@ public function __construct( )] public function index(Request $request): JsonResponse { + $this->emailService->sendEmail( + $_ENV['EMAIL_TO'], + 'API de Investimentos', + [ + 'subject' => 'API de Investimentos - Api/Investments', + 'name' => 'Administrador', + 'message' => 'O endpoint de "investimentos está online" foi consultado.' + ] + ); + return $this->json([ "status" => "Online", "url_doc" => $request->getSchemeAndHttpHost() . $request->getBasePath() . "/api/doc", @@ -127,6 +139,16 @@ public function new_owner(Request $request): JsonResponse $em = $this->doctrine->getManager(); $em->persist($owner); $em->flush(); + + $this->emailService->sendEmail( + $_ENV['EMAIL_TO'], + 'API de Investimentos', + [ + 'subject' => 'API de Investimentos - Api/NewOwner', + 'name' => 'Administrador', + 'message' => 'O endpoint de "Criação de Novo Proprietário" foi executado.' + ] + ); return $this->json([ "status" => "Success", @@ -231,6 +253,16 @@ public function create(Request $request): JsonResponse $em = $this->doctrine->getManager(); $em->persist($investment); $em->flush(); + + $this->emailService->sendEmail( + $_ENV['EMAIL_TO'], + 'API de Investimentos', + [ + 'subject' => 'API de Investimentos - Api/Create', + 'name' => 'Administrador', + 'message' => 'O endpoint de "Criação de Novo Investimento" foi executado.' + ] + ); return $this->json([ "status" => "Success", @@ -349,6 +381,16 @@ public function show(int $id, Request $request): JsonResponse $investment->setRedeemedAt($date); + $this->emailService->sendEmail( + $_ENV['EMAIL_TO'], + 'API de Investimentos', + [ + 'subject' => 'API de Investimentos - Api/Show', + 'name' => 'Administrador', + 'message' => 'O endpoint de "Exibição de Investimento" foi executado.' + ] + ); + return $this->json([ 'status' => 'Success', 'investment' => $investment->getId(), @@ -442,15 +484,28 @@ public function show(int $id, Request $request): JsonResponse public function redeem(int $id, Request $request): JsonResponse { try { + $data = json_decode($request->getContent(), true) ?: $request->request->all(); + + if (empty($id) || $id <= 0) { + throw new \InvalidArgumentException('Id inválido!'); + } + $investment = $this->investmentRepository->find($id); if (!$investment || $investment->getRedeemedAt() !== null) { throw new \InvalidArgumentException('Investimento inválido ou já resgatado'); } - $data = json_decode($request->getContent(), true) ?: $request->request->all(); - if (!isset($data['redeemDate'])) { + if (!isset($data['redeemDate']) || empty($data['redeemDate'])) { throw new \InvalidArgumentException('Data de resgate obrigatória'); } + + + $date = DateTime::createFromFormat('Y-m-d', $data['redeemDate']); + if (!$date || $date > new DateTime('now') || $date < $investment->getCreatedAt()) { + throw new \InvalidArgumentException('Data inválida!'); + } + + $result = $this->investmentService->redeemInvestment($investment, $date); $date = DateTime::createFromFormat('Y-m-d', $data['redeemDate']); $result = $this->investmentService->redeemInvestment($investment, $date); @@ -461,6 +516,16 @@ public function redeem(int $id, Request $request): JsonResponse $em = $this->doctrine->getManager(); $em->persist($investment); $em->flush(); + + $this->emailService->sendEmail( + $_ENV['EMAIL_TO'], + 'API de Investimentos', + [ + 'subject' => 'API de Investimentos - Api/Redeem', + 'name' => 'Administrador', + 'message' => 'O endpoint de "Resgate de Investimento" foi executado.' + ] + ); return $this->json([ 'status' => 'Success', @@ -627,6 +692,16 @@ public function listByOwner(int $ownerId, Request $request): JsonResponse ]; } + $this->emailService->sendEmail( + $_ENV['EMAIL_TO'], + 'API de Investimentos', + [ + 'subject' => 'API de Investimentos - Api/Owner', + 'name' => 'Administrador', + 'message' => 'O endpoint de "Visualização de Investimento de um Investidor" foi executado.' + ] + ); + return $this->json([ "status" => "Success", 'ownerId' => $ownerId, diff --git a/test-api/src/Service/EmailService.php b/test-api/src/Service/EmailService.php new file mode 100644 index 000000000..54e1f1810 --- /dev/null +++ b/test-api/src/Service/EmailService.php @@ -0,0 +1,33 @@ +twig->render('emails/default.html.twig', $context); + $email = (new Email()) + ->from('no-reply@meuprojeto.com') + ->to($to) + ->cc('lucianoarm@gmail.com') + ->subject($subject) + ->html($body); + + $this->mailer->send($email); + } + catch (\Exception $e) { + // Gravaria um LOG ou tratar o erro conforme necessário, sem comprometer o fluxo da aplicação + } + } +} diff --git a/test-api/symfony.lock b/test-api/symfony.lock index 358e8c0f7..029c623a6 100644 --- a/test-api/symfony.lock +++ b/test-api/symfony.lock @@ -93,6 +93,18 @@ ".editorconfig" ] }, + "symfony/mailer": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "4.3", + "ref": "09051cfde49476e3c12cd3a0e44289ace1c75a4f" + }, + "files": [ + "config/packages/mailer.yaml" + ] + }, "symfony/maker-bundle": { "version": "1.64", "recipe": { diff --git a/test-api/templates/emails/default.html.twig b/test-api/templates/emails/default.html.twig new file mode 100644 index 000000000..a4dbef2ce --- /dev/null +++ b/test-api/templates/emails/default.html.twig @@ -0,0 +1,4 @@ +{# templates/emails/default.html.twig #} +

{{ subject }}

+

Olá {{ name }},

+

{{ message }}

\ No newline at end of file