From 86fb772be8f9b348cf9d6e59d81093853b6aed5e Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Wed, 3 Sep 2025 15:07:02 -0300 Subject: [PATCH 01/25] Initial config of dev enviroment --- api-investimentos/.editorconfig | 17 + api-investimentos/.env | 30 + api-investimentos/.env.dev | 4 + api-investimentos/.gitignore | 10 + api-investimentos/bin/console | 21 + api-investimentos/composer.json | 78 + api-investimentos/composer.lock | 4129 +++++++++++++++++ api-investimentos/config/bundles.php | 8 + api-investimentos/config/packages/cache.yaml | 19 + .../config/packages/doctrine.yaml | 54 + .../config/packages/doctrine_migrations.yaml | 6 + .../config/packages/framework.yaml | 15 + .../config/packages/routing.yaml | 10 + api-investimentos/config/preload.php | 5 + api-investimentos/config/routes.yaml | 5 + .../config/routes/framework.yaml | 4 + api-investimentos/config/services.yaml | 20 + api-investimentos/migrations/.gitignore | 0 api-investimentos/public/index.php | 9 + api-investimentos/src/Controller/.gitignore | 0 api-investimentos/src/Entity/.gitignore | 0 api-investimentos/src/Kernel.php | 11 + api-investimentos/src/Repository/.gitignore | 0 api-investimentos/symfony.lock | 105 + 24 files changed, 4560 insertions(+) create mode 100644 api-investimentos/.editorconfig create mode 100644 api-investimentos/.env create mode 100644 api-investimentos/.env.dev create mode 100644 api-investimentos/.gitignore create mode 100644 api-investimentos/bin/console create mode 100644 api-investimentos/composer.json create mode 100644 api-investimentos/composer.lock create mode 100644 api-investimentos/config/bundles.php create mode 100644 api-investimentos/config/packages/cache.yaml create mode 100644 api-investimentos/config/packages/doctrine.yaml create mode 100644 api-investimentos/config/packages/doctrine_migrations.yaml create mode 100644 api-investimentos/config/packages/framework.yaml create mode 100644 api-investimentos/config/packages/routing.yaml create mode 100644 api-investimentos/config/preload.php create mode 100644 api-investimentos/config/routes.yaml create mode 100644 api-investimentos/config/routes/framework.yaml create mode 100644 api-investimentos/config/services.yaml create mode 100644 api-investimentos/migrations/.gitignore create mode 100644 api-investimentos/public/index.php create mode 100644 api-investimentos/src/Controller/.gitignore create mode 100644 api-investimentos/src/Entity/.gitignore create mode 100644 api-investimentos/src/Kernel.php create mode 100644 api-investimentos/src/Repository/.gitignore create mode 100644 api-investimentos/symfony.lock diff --git a/api-investimentos/.editorconfig b/api-investimentos/.editorconfig new file mode 100644 index 000000000..66990769e --- /dev/null +++ b/api-investimentos/.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/api-investimentos/.env b/api-investimentos/.env new file mode 100644 index 000000000..1f79f5b6a --- /dev/null +++ b/api-investimentos/.env @@ -0,0 +1,30 @@ +# In all environments, the following files are loaded if they exist, +# the latter taking precedence over the former: +# +# * .env contains default values for the environment variables needed by the app +# * .env.local uncommitted file with local overrides +# * .env.$APP_ENV committed environment-specific defaults +# * .env.$APP_ENV.local uncommitted environment-specific overrides +# +# Real environment variables win over .env files. +# +# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. +# https://symfony.com/doc/current/configuration/secrets.html +# +# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). +# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration + +###> symfony/framework-bundle ### +APP_ENV=dev +APP_SECRET= +###< symfony/framework-bundle ### + +###> doctrine/doctrine-bundle ### +# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url +# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml +# +# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db" +# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" +# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" +DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" +###< doctrine/doctrine-bundle ### diff --git a/api-investimentos/.env.dev b/api-investimentos/.env.dev new file mode 100644 index 000000000..943d9b3ae --- /dev/null +++ b/api-investimentos/.env.dev @@ -0,0 +1,4 @@ + +###> symfony/framework-bundle ### +APP_SECRET=b6937ee0134e5ebe604e1e78abec7737 +###< symfony/framework-bundle ### diff --git a/api-investimentos/.gitignore b/api-investimentos/.gitignore new file mode 100644 index 000000000..a67f91e25 --- /dev/null +++ b/api-investimentos/.gitignore @@ -0,0 +1,10 @@ + +###> symfony/framework-bundle ### +/.env.local +/.env.local.php +/.env.*.local +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ +/vendor/ +###< symfony/framework-bundle ### diff --git a/api-investimentos/bin/console b/api-investimentos/bin/console new file mode 100644 index 000000000..d8d530e2c --- /dev/null +++ b/api-investimentos/bin/console @@ -0,0 +1,21 @@ +#!/usr/bin/env php +=8.2", + "ext-ctype": "*", + "ext-iconv": "*", + "doctrine/dbal": "^3", + "doctrine/doctrine-bundle": "^2.16", + "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/http-foundation": "7.3.*", + "symfony/routing": "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/api-investimentos/composer.lock b/api-investimentos/composer.lock new file mode 100644 index 000000000..4e92dcce1 --- /dev/null +++ b/api-investimentos/composer.lock @@ -0,0 +1,4129 @@ +{ + "_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": "0aee3da215fdbcd8ad350f8110de3f42", + "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.16.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineBundle.git", + "reference": "cb2ad28708f870ff9534e82798c557bdc79809ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/cb2ad28708f870ff9534e82798c557bdc79809ba", + "reference": "cb2ad28708f870ff9534e82798c557bdc79809ba", + "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.16.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%2Fdoctrine-bundle", + "type": "tidelift" + } + ], + "time": "2025-09-02T17:41:12+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.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c", + "reference": "1b88fcb812f2cd6e77c83d16db60e3cf1e35c66c", + "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.4" + }, + "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-08-19T06:41:07+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.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "dcbdfe4b211ae09478e192289cae7ab0987b29a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/dcbdfe4b211ae09478e192289cae7ab0987b29a4", + "reference": "dcbdfe4b211ae09478e192289cae7ab0987b29a4", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^1 || ^2", + "php": "^8.1", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "1.12.7", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.6", + "phpunit/phpunit": "^9.6", + "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^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.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%2Fpersistence", + "type": "tidelift" + } + ], + "time": "2025-08-21T16:00:31+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.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "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.3" + }, + "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-08-25T06:35:40+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "ab6c38dad5da9b15b1f7afb2f5c5814112e70261" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ab6c38dad5da9b15b1f7afb2f5c5814112e70261", + "reference": "ab6c38dad5da9b15b1f7afb2f5c5814112e70261", + "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.3" + }, + "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-08-14T09:54:27+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.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/doctrine-bridge.git", + "reference": "b371ded46da25415e1a3a7422e4acd2ec34214c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/b371ded46da25415e1a3a7422e4acd2ec34214c5", + "reference": "b371ded46da25415e1a3a7422e4acd2ec34214c5", + "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.3" + }, + "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-08-18T13:10:53+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.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "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.3" + }, + "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-08-13T11:49:31+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.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/flex.git", + "reference": "f356aa35f3cf3d2f46c31d344c1098eb2d260426" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/flex/zipball/f356aa35f3cf3d2f46c31d344c1098eb2d260426", + "reference": "f356aa35f3cf3d2f46c31d344c1098eb2d260426", + "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.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-08-22T07:17:23+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "19ec4ab6be90322ed190e041e2404a976ed22571" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/19ec4ab6be90322ed190e041e2404a976ed22571", + "reference": "19ec4ab6be90322ed190e041e2404a976ed22571", + "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.3" + }, + "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-08-27T07:45:05+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00", + "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00", + "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.3" + }, + "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-08-20T08:04:18+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/72c304de37e1a1cec6d5d12b81187ebd4850a17b", + "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b", + "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.3" + }, + "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-08-29T08:23:45+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "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.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.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.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.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.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "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.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+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.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "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.3" + }, + "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-08-25T06:35:40+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", + "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.3" + }, + "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-08-13T11:49:31+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", + "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", + "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.3" + }, + "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-08-18T13:10:53+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", + "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.3" + }, + "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-08-27T11:34:33+00:00" + } + ], + "packages-dev": [ + { + "name": "nikic/php-parser", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + }, + "time": "2025-08-13T20:13:15+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.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", + "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.3" + }, + "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-08-18T09:42:54+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/api-investimentos/config/bundles.php b/api-investimentos/config/bundles.php new file mode 100644 index 000000000..de8898b23 --- /dev/null +++ b/api-investimentos/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/api-investimentos/config/packages/cache.yaml b/api-investimentos/config/packages/cache.yaml new file mode 100644 index 000000000..6899b7200 --- /dev/null +++ b/api-investimentos/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/api-investimentos/config/packages/doctrine.yaml b/api-investimentos/config/packages/doctrine.yaml new file mode 100644 index 000000000..25138b979 --- /dev/null +++ b/api-investimentos/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/api-investimentos/config/packages/doctrine_migrations.yaml b/api-investimentos/config/packages/doctrine_migrations.yaml new file mode 100644 index 000000000..29231d94b --- /dev/null +++ b/api-investimentos/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/api-investimentos/config/packages/framework.yaml b/api-investimentos/config/packages/framework.yaml new file mode 100644 index 000000000..7e1ee1f1e --- /dev/null +++ b/api-investimentos/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/api-investimentos/config/packages/routing.yaml b/api-investimentos/config/packages/routing.yaml new file mode 100644 index 000000000..8166181c6 --- /dev/null +++ b/api-investimentos/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/api-investimentos/config/preload.php b/api-investimentos/config/preload.php new file mode 100644 index 000000000..5ebcdb215 --- /dev/null +++ b/api-investimentos/config/preload.php @@ -0,0 +1,5 @@ + Date: Wed, 3 Sep 2025 17:00:29 -0300 Subject: [PATCH 02/25] Remove dotenv from versioning --- api-investimentos/.env | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 api-investimentos/.env diff --git a/api-investimentos/.env b/api-investimentos/.env deleted file mode 100644 index 1f79f5b6a..000000000 --- a/api-investimentos/.env +++ /dev/null @@ -1,30 +0,0 @@ -# In all environments, the following files are loaded if they exist, -# the latter taking precedence over the former: -# -# * .env contains default values for the environment variables needed by the app -# * .env.local uncommitted file with local overrides -# * .env.$APP_ENV committed environment-specific defaults -# * .env.$APP_ENV.local uncommitted environment-specific overrides -# -# Real environment variables win over .env files. -# -# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. -# https://symfony.com/doc/current/configuration/secrets.html -# -# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). -# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration - -###> symfony/framework-bundle ### -APP_ENV=dev -APP_SECRET= -###< symfony/framework-bundle ### - -###> doctrine/doctrine-bundle ### -# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url -# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml -# -# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db" -# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" -# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" -DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" -###< doctrine/doctrine-bundle ### From 71796bf37a22b4cb0e6fe96345abc9c2791e72bc Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Wed, 3 Sep 2025 17:02:27 -0300 Subject: [PATCH 03/25] Remove dotenvdotdev from versioning --- api-investimentos/.env.dev | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 api-investimentos/.env.dev diff --git a/api-investimentos/.env.dev b/api-investimentos/.env.dev deleted file mode 100644 index 943d9b3ae..000000000 --- a/api-investimentos/.env.dev +++ /dev/null @@ -1,4 +0,0 @@ - -###> symfony/framework-bundle ### -APP_SECRET=b6937ee0134e5ebe604e1e78abec7737 -###< symfony/framework-bundle ### From c9dbd0a1dfbc016f00d190130b590a6f6dcb9c1d Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Wed, 3 Sep 2025 17:06:16 -0300 Subject: [PATCH 04/25] Database create, entity investment creat and done migration --- api-investimentos/.gitignore | 2 + .../migrations/Version20250903195059.php | 31 +++++++++ api-investimentos/src/Entity/Investment.php | 65 +++++++++++++++++++ .../src/Repository/InvestmentRepository.php | 43 ++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 api-investimentos/migrations/Version20250903195059.php create mode 100644 api-investimentos/src/Entity/Investment.php create mode 100644 api-investimentos/src/Repository/InvestmentRepository.php diff --git a/api-investimentos/.gitignore b/api-investimentos/.gitignore index a67f91e25..a586de575 100644 --- a/api-investimentos/.gitignore +++ b/api-investimentos/.gitignore @@ -1,5 +1,7 @@ ###> symfony/framework-bundle ### +.env +.env.dev /.env.local /.env.local.php /.env.*.local diff --git a/api-investimentos/migrations/Version20250903195059.php b/api-investimentos/migrations/Version20250903195059.php new file mode 100644 index 000000000..96bcf2e69 --- /dev/null +++ b/api-investimentos/migrations/Version20250903195059.php @@ -0,0 +1,31 @@ +addSql('CREATE TABLE investment (id INT AUTO_INCREMENT NOT NULL, owner VARCHAR(100) NOT NULL, creation_date DATETIME NOT NULL, investment_value DOUBLE PRECISION NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE investment'); + } +} diff --git a/api-investimentos/src/Entity/Investment.php b/api-investimentos/src/Entity/Investment.php new file mode 100644 index 000000000..b1b52499b --- /dev/null +++ b/api-investimentos/src/Entity/Investment.php @@ -0,0 +1,65 @@ +id; + } + + public function getOwner(): ?string + { + return $this->owner; + } + + public function setOwner(string $owner): static + { + $this->owner = $owner; + + return $this; + } + + public function getCreationDate(): ?\DateTime + { + return $this->creationDate; + } + + public function setCreationDate(\DateTime $creationDate): static + { + $this->creationDate = $creationDate; + + return $this; + } + + public function getInvestmentValue(): ?float + { + return $this->investmentValue; + } + + public function setInvestmentValue(float $investmentValue): static + { + $this->investmentValue = $investmentValue; + + return $this; + } +} diff --git a/api-investimentos/src/Repository/InvestmentRepository.php b/api-investimentos/src/Repository/InvestmentRepository.php new file mode 100644 index 000000000..d0034c838 --- /dev/null +++ b/api-investimentos/src/Repository/InvestmentRepository.php @@ -0,0 +1,43 @@ + + */ +class InvestmentRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Investment::class); + } + + // /** + // * @return Investment[] Returns an array of Investment objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('i') + // ->andWhere('i.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('i.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Investment + // { + // return $this->createQueryBuilder('i') + // ->andWhere('i.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} From 8a73dee9d41277332a553bb810933404fb97114e Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Wed, 3 Sep 2025 20:14:07 -0300 Subject: [PATCH 05/25] First rout test included --- .../src/Controller/InvestimentoController.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 api-investimentos/src/Controller/InvestimentoController.php diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php new file mode 100644 index 000000000..798990d4d --- /dev/null +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -0,0 +1,18 @@ +json([ + 'message' => 'Bem-vindo à API de Investimentos', + 'status' => 'okay', + ]); + } +} From 26c17e71a2aa73c383acf83f49f823a3fe71aedf Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Wed, 3 Sep 2025 22:34:44 -0300 Subject: [PATCH 06/25] Controller created for a investment cadastration --- .../src/Controller/InvestimentoController.php | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php index 798990d4d..1794c1270 100644 --- a/api-investimentos/src/Controller/InvestimentoController.php +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -2,17 +2,41 @@ namespace App\Controller; +use Doctrine\ORM\EntityManagerInterface; +use App\Entity\Investment; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Annotation\Route; - +use Symfony\Component\HttpFoundation\Request; class InvestimentoController extends AbstractController { - #[Route('/api/investimentos', name: 'listar_investimentos', methods: ['GET'])] - public function index(): JsonResponse { + #[Route('/api/investments', name: 'create_investments', methods: ['POST'])] + public function create(Request $request, EntityManagerInterface $em): JsonResponse { + + $data = json_decode($request->getContent(), true); + + if (!$data || !isset($data['owner'], $data['creationDate'], $data['investmentValue'])) { + + return $this->json([ + 'error' => 'Parâmetros inválidos. Informe: nome do proprietário (owner), a data da criação do investimento (creationDate) e o valor do investimento (investmentValue).' + ], 400); + } + + $investment = new Investment(); + $investment->setOwner($data['owner']); + $investment->setCreationDate(new \DateTime($data['creationDate'])); + $investment->setInvestmentValue((float) $data['investmentValue']); + + $em->persist($investment); + $em->flush(); return $this->json([ - 'message' => 'Bem-vindo à API de Investimentos', - 'status' => 'okay', - ]); + 'message' => 'Investimento criado com sucesso.', + 'Investment' => [ + 'id' => $investment->getId(), + 'ownerName' => $investment->getOwner(), + 'creationDate' => $investment->getCreationDate()->format('Y-m-d H:i:s'), + 'investmentValue' => $investment->getInvestmentValue(), + ] + ], 201); } } From c4731b9fd58c65e39578ed283fa7ddb972e444aa Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Thu, 4 Sep 2025 11:03:02 -0300 Subject: [PATCH 07/25] Development of the profit listing rout and function to calculate it --- .../src/Controller/InvestimentoController.php | 48 ++++++++++++++++++- api-investimentos/src/Entity/Investment.php | 9 ++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php index 1794c1270..374a053e2 100644 --- a/api-investimentos/src/Controller/InvestimentoController.php +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -9,7 +9,7 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Request; class InvestimentoController extends AbstractController { - #[Route('/api/investments', name: 'create_investments', methods: ['POST'])] + #[Route('/api/investments/create', name: 'create_investments', methods: ['POST'])] public function create(Request $request, EntityManagerInterface $em): JsonResponse { $data = json_decode($request->getContent(), true); @@ -39,4 +39,50 @@ public function create(Request $request, EntityManagerInterface $em): JsonRespon ] ], 201); } + + #[Route('/api/investments/list/{id}', name: 'list_investments', methods: ['GET'])] + public function investmentList(int $id, EntityManagerInterface $em): JsonResponse { + + $investments = $em->getRepository(Investment::class)->findBy(['owner' => $id]); + + if (!$investments) { + return $this->json([ + 'message' => 'Nenhum investimento encontrado para este propritário.' + ], 404); + } + + $result = []; + foreach ($investments as $investment) { + $valueWinnings = $this->calculateInvestment($investment); + $result[] = [ + 'id' => $investment->getId(), + 'owner' => $investment->getOwner(), + 'creationDate' => $investment->getCreationDate(), + 'investmentValue' => $investment->getInvestmentValue(), + 'valueWithWinnings' => $valueWinnings, + ]; + } + + return $this->json([ + 'investments' => $result + ]); + } + + /* + Calcula o valor dos ganhos do investimento com o acrécimo de 0,52%/mes + */ + + private function calculateInvestment(Investment $investment): float { + + $initValue = $investment->getInvestmentValue(); + $creationDate = $investment->getCreationDate(); + $currentDate = new \DateTime(); + + $interval = $creationDate->diff($currentDate); + $months = ($interval->y * 12) + $interval->m; + + $finalValue = $initValue * pow(1.0052, $months); + + return round($finalValue, 2); + } } diff --git a/api-investimentos/src/Entity/Investment.php b/api-investimentos/src/Entity/Investment.php index b1b52499b..d83ece7d7 100644 --- a/api-investimentos/src/Entity/Investment.php +++ b/api-investimentos/src/Entity/Investment.php @@ -46,6 +46,11 @@ public function getCreationDate(): ?\DateTime public function setCreationDate(\DateTime $creationDate): static { + $now = new \DateTime(); + if ($creationDate > $now) { + throw new \InvalidArgumentException('A data de criação não pode ser futura'); + } + $this->creationDate = $creationDate; return $this; @@ -58,6 +63,10 @@ public function getInvestmentValue(): ?float public function setInvestmentValue(float $investmentValue): static { + if ($investmentValue < 0) { + throw new \InvalidArgumentException('O valor do investimento não pode ser negativo.'); + } + $this->investmentValue = $investmentValue; return $this; From 21e964db2967b19814298d143810d11be42013bc Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Thu, 4 Sep 2025 11:27:04 -0300 Subject: [PATCH 08/25] Bug fix: owner are not found in the route --- api-investimentos/src/Controller/InvestimentoController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php index 374a053e2..85d491999 100644 --- a/api-investimentos/src/Controller/InvestimentoController.php +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -43,7 +43,7 @@ public function create(Request $request, EntityManagerInterface $em): JsonRespon #[Route('/api/investments/list/{id}', name: 'list_investments', methods: ['GET'])] public function investmentList(int $id, EntityManagerInterface $em): JsonResponse { - $investments = $em->getRepository(Investment::class)->findBy(['owner' => $id]); + $investments = $em->getRepository(Investment::class)->findBy(['id' => $id]); if (!$investments) { return $this->json([ From 8f8750364ccbd1e636bf21bc9db1720c0ceca08b Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Thu, 4 Sep 2025 13:11:33 -0300 Subject: [PATCH 09/25] Method moved to a separate document from the controller --- .../src/Controller/InvestimentoController.php | 22 ++---------- .../src/Entity/InvestmentCalculator.php | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+), 19 deletions(-) create mode 100644 api-investimentos/src/Entity/InvestmentCalculator.php diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php index 85d491999..b76dce1d4 100644 --- a/api-investimentos/src/Controller/InvestimentoController.php +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -8,6 +8,8 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Request; +use App\Utils\InvestmentCalculator; + class InvestimentoController extends AbstractController { #[Route('/api/investments/create', name: 'create_investments', methods: ['POST'])] public function create(Request $request, EntityManagerInterface $em): JsonResponse { @@ -53,7 +55,7 @@ public function investmentList(int $id, EntityManagerInterface $em): JsonRespons $result = []; foreach ($investments as $investment) { - $valueWinnings = $this->calculateInvestment($investment); + $valueWinnings = InvestmentCalculator::calculateInvestment($investment); $result[] = [ 'id' => $investment->getId(), 'owner' => $investment->getOwner(), @@ -67,22 +69,4 @@ public function investmentList(int $id, EntityManagerInterface $em): JsonRespons 'investments' => $result ]); } - - /* - Calcula o valor dos ganhos do investimento com o acrécimo de 0,52%/mes - */ - - private function calculateInvestment(Investment $investment): float { - - $initValue = $investment->getInvestmentValue(); - $creationDate = $investment->getCreationDate(); - $currentDate = new \DateTime(); - - $interval = $creationDate->diff($currentDate); - $months = ($interval->y * 12) + $interval->m; - - $finalValue = $initValue * pow(1.0052, $months); - - return round($finalValue, 2); - } } diff --git a/api-investimentos/src/Entity/InvestmentCalculator.php b/api-investimentos/src/Entity/InvestmentCalculator.php new file mode 100644 index 000000000..38f3a0d7f --- /dev/null +++ b/api-investimentos/src/Entity/InvestmentCalculator.php @@ -0,0 +1,34 @@ +getInvestmentValue(); + $creationDate = $investment->getCreationDate(); + + if (empty($creationDate)) { + throw new \InvalidArgumentException('Não é possível calcular o valor: data de criação nula.'); + } + + if (!$creationDate instanceof \DateTime) { + try { + $creationDate = new \DateTime($creationDate); + } catch (\Exception $e) { + throw new \InvalidArgumentException('Formato de data de criação inválido.'); + } + } + + $currentDate = new \DateTime(); + $interval = $creationDate->diff($currentDate); + $months = ($interval->y * 12) + $interval->m; + + $finalValue = $initValue * pow(1.0052, $months); + + return round($finalValue, 2); + } +} From 91a1258d2f2f4fefc87ac05997cd12d5c9910425 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Thu, 4 Sep 2025 17:12:13 -0300 Subject: [PATCH 10/25] Created: take-out function and controller for it --- .../src/Controller/InvestimentoController.php | 30 +++++++++++++++++++ .../src/Entity/TakeInvestmentOut.php | 20 +++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 api-investimentos/src/Entity/TakeInvestmentOut.php diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php index b76dce1d4..db68b870b 100644 --- a/api-investimentos/src/Controller/InvestimentoController.php +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -9,6 +9,7 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Request; use App\Utils\InvestmentCalculator; +use App\Utils\TakeInvestmentOut; class InvestimentoController extends AbstractController { #[Route('/api/investments/create', name: 'create_investments', methods: ['POST'])] @@ -62,6 +63,7 @@ public function investmentList(int $id, EntityManagerInterface $em): JsonRespons 'creationDate' => $investment->getCreationDate(), 'investmentValue' => $investment->getInvestmentValue(), 'valueWithWinnings' => $valueWinnings, + ]; } @@ -69,4 +71,32 @@ public function investmentList(int $id, EntityManagerInterface $em): JsonRespons 'investments' => $result ]); } + + #[Route('/api/investments/draw/{id}', name: 'draw_investments', methods: ['PUT'])] + public function drawInvestmentAccount(int $id, EntityManagerInterface $em): JsonResponse { + $investment = $em->getRepository(Investment::class)->find($id); + + if (!$investment) { + return $this->json([ + 'error' => 'Investimento não encontrado' + ], 400); + } + + try { + $withdrawValue = TakeInvestmentOut::TakeOutInvestment($investment); + $em->persist($investment); + $em->flush(); + } catch (\InvalidArgumentException $e) { + return $this->json([ + 'error' => $e->getMessage() + ], 400); + } + + return $this->json([ + 'message' => 'Saque realizado com sucesso', + 'withdrawValue' => $withdrawValue, + 'investmentId' => $investment->getId(), + 'owner' => $investment->getOwner() + ]); + } } diff --git a/api-investimentos/src/Entity/TakeInvestmentOut.php b/api-investimentos/src/Entity/TakeInvestmentOut.php new file mode 100644 index 000000000..a960a1bce --- /dev/null +++ b/api-investimentos/src/Entity/TakeInvestmentOut.php @@ -0,0 +1,20 @@ +getInvestmentValue(); + $ownerName = $investment->getOwner(); + + if ($balance == 0) { + throw new \InvalidArgumentException('Não há saldo nesta conta'); + } + + $investment->setInvestmentValue(0); + + return $balance; + } +} From 56a82310338e496edb01700721cf5e5cf5be3078 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Fri, 5 Sep 2025 15:30:26 -0300 Subject: [PATCH 11/25] Created new functions for reuse and created owner table --- .../src/Controller/InvestimentoController.php | 7 +-- .../src/Entity/InvestmentCalculator.php | 19 +++++++- api-investimentos/src/Entity/Owner.php | 35 +++++++++++++++ .../src/Entity/TakeInvestmentOut.php | 24 ++++++++++- .../src/Repository/OwnerRepository.php | 43 +++++++++++++++++++ 5 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 api-investimentos/src/Entity/Owner.php create mode 100644 api-investimentos/src/Repository/OwnerRepository.php diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php index db68b870b..ca4b3c627 100644 --- a/api-investimentos/src/Controller/InvestimentoController.php +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -57,13 +57,14 @@ public function investmentList(int $id, EntityManagerInterface $em): JsonRespons $result = []; foreach ($investments as $investment) { $valueWinnings = InvestmentCalculator::calculateInvestment($investment); + $valueWinningsOnly = InvestmentCalculator::calculateValueWinnings($investment); $result[] = [ 'id' => $investment->getId(), 'owner' => $investment->getOwner(), 'creationDate' => $investment->getCreationDate(), 'investmentValue' => $investment->getInvestmentValue(), - 'valueWithWinnings' => $valueWinnings, - + 'valueWithWinnings' => $valueWinnings, // valor total + 'winningsValueOnly' => $valueWinningsOnly, // valor somente do lucro, sem o investimento ]; } @@ -72,7 +73,7 @@ public function investmentList(int $id, EntityManagerInterface $em): JsonRespons ]); } - #[Route('/api/investments/draw/{id}', name: 'draw_investments', methods: ['PUT'])] + #[Route('/api/investments/draw/{id}', name: 'draw_investments', methods: ['PUT'])] public function drawInvestmentAccount(int $id, EntityManagerInterface $em): JsonResponse { $investment = $em->getRepository(Investment::class)->find($id); diff --git a/api-investimentos/src/Entity/InvestmentCalculator.php b/api-investimentos/src/Entity/InvestmentCalculator.php index 38f3a0d7f..5ddb49ba6 100644 --- a/api-investimentos/src/Entity/InvestmentCalculator.php +++ b/api-investimentos/src/Entity/InvestmentCalculator.php @@ -24,11 +24,26 @@ public static function calculateInvestment(Investment $investment): float } $currentDate = new \DateTime(); - $interval = $creationDate->diff($currentDate); - $months = ($interval->y * 12) + $interval->m; + $months = self::calculateMonthsBetween($creationDate, $currentDate); $finalValue = $initValue * pow(1.0052, $months); return round($finalValue, 2); } + + // Calculo de meses + public static function calculateMonthsBetween(\DateTime $start, \DateTime $end): int { + $interval = $start->diff($end); + return ($interval->y * 12) + $interval->m; + } + + public static function calculateValueWinnings(Investment $investment): float { + + $initValue = $investment->getInvestmentValue(); + $finalValue = InvestmentCalculator::calculateInvestment($investment); + + $valueWinnings = ($finalValue - $initValue); + + return round($valueWinnings, 2); + } } diff --git a/api-investimentos/src/Entity/Owner.php b/api-investimentos/src/Entity/Owner.php new file mode 100644 index 000000000..44fb0c50d --- /dev/null +++ b/api-investimentos/src/Entity/Owner.php @@ -0,0 +1,35 @@ +id; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } +} diff --git a/api-investimentos/src/Entity/TakeInvestmentOut.php b/api-investimentos/src/Entity/TakeInvestmentOut.php index a960a1bce..127b5dc87 100644 --- a/api-investimentos/src/Entity/TakeInvestmentOut.php +++ b/api-investimentos/src/Entity/TakeInvestmentOut.php @@ -3,18 +3,40 @@ namespace App\Utils; use App\Entity\Investment; +use App\Utils\InvestmentCalculator; class TakeInvestmentOut { public static function TakeOutInvestment(Investment $investment): float { $balance = $investment->getInvestmentValue(); $ownerName = $investment->getOwner(); + $creationDate = $investment->getCreationDate(); + $currentDate = new \DateTime(); + + $ageInvestment = InvestmentCalculator::calculateMonthsBetween($creationDate, $currentDate); + + $taxDiscount = 0; if ($balance == 0) { throw new \InvalidArgumentException('Não há saldo nesta conta'); } + switch (true) { + case $ageInvestment > 12: + $taxDiscount = 0.15; // 15% + break; + case $ageInvestment >= 12 && $ageInvestment <= 24: + $taxDiscount = 0.185; // 18,5% + break; + case $ageInvestment > 24: + $taxDiscount = 0.225; // 22,5% + break; + } + + $finalValue = $balance - ($balance * $taxDiscount); // Alterar: desc feito apenas no (invest - ganho) + $investment->setInvestmentValue(0); - return $balance; + return round($finalValue, 2); + } } diff --git a/api-investimentos/src/Repository/OwnerRepository.php b/api-investimentos/src/Repository/OwnerRepository.php new file mode 100644 index 000000000..5988c3524 --- /dev/null +++ b/api-investimentos/src/Repository/OwnerRepository.php @@ -0,0 +1,43 @@ + + */ +class OwnerRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Owner::class); + } + + // /** + // * @return Owner[] Returns an array of Owner objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('o') + // ->andWhere('o.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('o.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Owner + // { + // return $this->createQueryBuilder('o') + // ->andWhere('o.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} From 8a568556c76d55fcccd064a66c140a365238d3d2 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Fri, 5 Sep 2025 16:24:47 -0300 Subject: [PATCH 12/25] Deleted old migration and created the relationship between the tables: investment and owner --- .../migrations/Version20250903195059.php | 20 ++++++++++++++++--- api-investimentos/src/Entity/Investment.php | 12 ++++++----- api-investimentos/src/Entity/Owner.php | 14 ++++++++++++- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/api-investimentos/migrations/Version20250903195059.php b/api-investimentos/migrations/Version20250903195059.php index 96bcf2e69..e663ba17e 100644 --- a/api-investimentos/migrations/Version20250903195059.php +++ b/api-investimentos/migrations/Version20250903195059.php @@ -19,13 +19,27 @@ public function getDescription(): string public function up(Schema $schema): void { - // this up() migration is auto-generated, please modify it to your needs - $this->addSql('CREATE TABLE investment (id INT AUTO_INCREMENT NOT NULL, owner VARCHAR(100) NOT NULL, creation_date DATETIME NOT NULL, investment_value DOUBLE PRECISION NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + // Cria a tabela owner primeiro (se ainda não existir) + $this->addSql('CREATE TABLE owner ( + id INT AUTO_INCREMENT NOT NULL, + name VARCHAR(100) NOT NULL, + PRIMARY KEY(id) + ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + + // Cria a tabela investment com a relação ManyToOne para owner + $this->addSql('CREATE TABLE investment ( + id INT AUTO_INCREMENT NOT NULL, + owner_id INT NOT NULL, + creation_date DATETIME NOT NULL, + investment_value DOUBLE PRECISION NOT NULL, + PRIMARY KEY(id), + CONSTRAINT FK_INVESTMENT_OWNER FOREIGN KEY (owner_id) REFERENCES owner (id) + ) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); } public function down(Schema $schema): void { - // this down() migration is auto-generated, please modify it to your needs $this->addSql('DROP TABLE investment'); + $this->addSql('DROP TABLE owner'); } } diff --git a/api-investimentos/src/Entity/Investment.php b/api-investimentos/src/Entity/Investment.php index d83ece7d7..659ef930d 100644 --- a/api-investimentos/src/Entity/Investment.php +++ b/api-investimentos/src/Entity/Investment.php @@ -4,6 +4,7 @@ use App\Repository\InvestmentRepository; use Doctrine\ORM\Mapping as ORM; +use App\Entity\Owner; #[ORM\Entity(repositoryClass: InvestmentRepository::class)] class Investment @@ -13,26 +14,27 @@ class Investment #[ORM\Column] private ?int $id = null; - #[ORM\Column(length: 100)] - private ?string $owner = null; - #[ORM\Column] private ?\DateTime $creationDate = null; #[ORM\Column] private ?float $investmentValue = null; + #[ORM\ManyToOne(targetEntity: Owner::class, inversedBy: 'investments')] + #[ORM\JoinColumn(nullable: false)] + private ?Owner $owner = null; + public function getId(): ?int { return $this->id; } - public function getOwner(): ?string + public function getOwner(): ?Owner { return $this->owner; } - public function setOwner(string $owner): static + public function setOwner(Owner $owner): static { $this->owner = $owner; diff --git a/api-investimentos/src/Entity/Owner.php b/api-investimentos/src/Entity/Owner.php index 44fb0c50d..02db333be 100644 --- a/api-investimentos/src/Entity/Owner.php +++ b/api-investimentos/src/Entity/Owner.php @@ -4,7 +4,8 @@ use App\Repository\OwnerRepository; use Doctrine\ORM\Mapping as ORM; - +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; #[ORM\Entity(repositoryClass: OwnerRepository::class)] class Owner { @@ -16,6 +17,13 @@ class Owner #[ORM\Column(length: 100)] private ?string $name = null; + #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Investment::class)] + private Collection $investments; + + public function __construct() { + $this->investments = new ArrayCollection(); + } + public function getId(): ?int { return $this->id; @@ -32,4 +40,8 @@ public function setName(string $name): static return $this; } + + public function getInvestments(): Collection { + return $this->investments; + } } From bdc3cc4dac8141945f05179831b8bb04fa90eef6 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Sat, 6 Sep 2025 10:59:12 -0300 Subject: [PATCH 13/25] Modified files for listing investment projections --- .../src/Controller/InvestimentoController.php | 106 +++++++++++++++--- api-investimentos/src/Entity/Investment.php | 15 +++ .../src/Entity/InvestmentCalculator.php | 22 +++- .../src/Entity/TakeInvestmentOut.php | 45 ++++---- 4 files changed, 151 insertions(+), 37 deletions(-) diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php index ca4b3c627..411cc9879 100644 --- a/api-investimentos/src/Controller/InvestimentoController.php +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -12,20 +12,54 @@ use App\Utils\TakeInvestmentOut; class InvestimentoController extends AbstractController { - #[Route('/api/investments/create', name: 'create_investments', methods: ['POST'])] - public function create(Request $request, EntityManagerInterface $em): JsonResponse { + + #[Route('/api/clientOwner/create', name: 'create_owner', methods: ['POST'])] + public function createOwner(Request $request, EntityManagerInterface $em): JsonResponse { $data = json_decode($request->getContent(), true); - if (!$data || !isset($data['owner'], $data['creationDate'], $data['investmentValue'])) { + if (!$data || !isset($data['ownerName'])) { + return $this->json([ + 'error' => 'Parâmetros inválidos. Informe: o nome do proprietário.' + ], 400); + } + + $owner = new \App\Entity\Owner(); + $owner->setName($data['ownerName']); + + $em->persist($owner); + $em->flush(); + + return $this->json([ + 'message' => 'Proprietário criado com sucesso.', + 'owner' => [ + 'id' => $owner->getId(), + 'name' => $owner->getName() + ] + ], 201); + } + + #[Route('/api/investments/create', name: 'create_investments', methods: ['POST'])] + public function createInvest(Request $request, EntityManagerInterface $em): JsonResponse { + + $data = json_decode($request->getContent(), true); + if (!$data || !isset($data['ownerId'], $data['creationDate'], $data['investmentValue'])) { return $this->json([ - 'error' => 'Parâmetros inválidos. Informe: nome do proprietário (owner), a data da criação do investimento (creationDate) e o valor do investimento (investmentValue).' + 'error' => 'Parâmetros inválidos. Informe: ID do proprietário (ownerId), data da criação do investimento (creationDate) e valor do investimento (investmentValue).' ], 400); } + // Busca o Owner pelo ID + $owner = $em->getRepository(\App\Entity\Owner::class)->find($data['ownerId']); + if (!$owner) { + return $this->json([ + 'error' => 'Proprietário não encontrado.' + ], 404); + } + $investment = new Investment(); - $investment->setOwner($data['owner']); + $investment->setOwner($owner); $investment->setCreationDate(new \DateTime($data['creationDate'])); $investment->setInvestmentValue((float) $data['investmentValue']); @@ -36,21 +70,30 @@ public function create(Request $request, EntityManagerInterface $em): JsonRespon 'message' => 'Investimento criado com sucesso.', 'Investment' => [ 'id' => $investment->getId(), - 'ownerName' => $investment->getOwner(), + 'ownerId' => $owner->getId(), + 'ownerName' => $owner->getName(), 'creationDate' => $investment->getCreationDate()->format('Y-m-d H:i:s'), 'investmentValue' => $investment->getInvestmentValue(), ] ], 201); } - #[Route('/api/investments/list/{id}', name: 'list_investments', methods: ['GET'])] - public function investmentList(int $id, EntityManagerInterface $em): JsonResponse { + #[Route('/api/investments/list/{ownerId}', name: 'list_investments', methods: ['GET'])] + public function investmentList(int $ownerId, EntityManagerInterface $em): JsonResponse { + + $owner = $em->getRepository(\App\Entity\Owner::class)->find($ownerId); + + if (!$owner) { + return $this->json([ + 'message' => 'Proprietário não encontrado.' + ], 404); + } - $investments = $em->getRepository(Investment::class)->findBy(['id' => $id]); + $investments = $owner->getInvestments(); - if (!$investments) { + if (count($investments) === 0) { return $this->json([ - 'message' => 'Nenhum investimento encontrado para este propritário.' + 'message' => 'Nenhum investimento encontrado para este proprietário.' ], 404); } @@ -60,11 +103,12 @@ public function investmentList(int $id, EntityManagerInterface $em): JsonRespons $valueWinningsOnly = InvestmentCalculator::calculateValueWinnings($investment); $result[] = [ 'id' => $investment->getId(), - 'owner' => $investment->getOwner(), - 'creationDate' => $investment->getCreationDate(), + 'ownerId' => $owner->getId(), + 'ownerName' => $owner->getName(), + 'creationDate' => $investment->getCreationDate()->format('Y-m-d H:i:s'), 'investmentValue' => $investment->getInvestmentValue(), - 'valueWithWinnings' => $valueWinnings, // valor total - 'winningsValueOnly' => $valueWinningsOnly, // valor somente do lucro, sem o investimento + 'valueWithWinnings' => $valueWinnings, + 'winningsValueOnly' => $valueWinningsOnly, ]; } @@ -93,11 +137,41 @@ public function drawInvestmentAccount(int $id, EntityManagerInterface $em): Json ], 400); } + $owner = $investment->getOwner(); + return $this->json([ 'message' => 'Saque realizado com sucesso', 'withdrawValue' => $withdrawValue, 'investmentId' => $investment->getId(), - 'owner' => $investment->getOwner() + 'ownerId' => $owner ? $owner->getId() : null, + 'ownerName' => $owner ? $owner->getName() : null ]); } + + #[Route('/api/investments/withdrawn-gains/{ownerId}', name: 'list_withdrawn_gains', methods: ['GET'])] + public function listWithdrawnGains(int $ownerId, EntityManagerInterface $em): JsonResponse + { + $owner = $em->getRepository(\App\Entity\Owner::class)->find($ownerId); + if (!$owner) { + return $this->json(['error' => 'Proprietário não encontrado.'], 404); + } + + $investments = $em->getRepository(Investment::class)->findBy([ + 'owner' => $owner, + // Considera apenas investimentos já retirados + 'withdrawnAt' => ['not' => null] + ]); + + $result = []; + foreach ($investments as $investment) { + $gain = InvestmentCalculator::calculateValueWinnings($investment); + $result[] = [ + 'investmentId' => $investment->getId(), + 'withdrawnAt' => $investment->getWithdrawnAt()?->format('Y-m-d H:i:s'), + 'gain' => $gain + ]; + } + + return $this->json(['withdrawnGains' => $result]); + } } diff --git a/api-investimentos/src/Entity/Investment.php b/api-investimentos/src/Entity/Investment.php index 659ef930d..da6d54366 100644 --- a/api-investimentos/src/Entity/Investment.php +++ b/api-investimentos/src/Entity/Investment.php @@ -24,6 +24,9 @@ class Investment #[ORM\JoinColumn(nullable: false)] private ?Owner $owner = null; + #[ORM\Column(type: 'datetime', nullable: true)] + private ?\DateTime $withdrawnAt = null; + public function getId(): ?int { return $this->id; @@ -73,4 +76,16 @@ public function setInvestmentValue(float $investmentValue): static return $this; } + + public function getWithdrawnAt(): ?\DateTime + { + return $this->withdrawnAt; + } + + public function setWithdrawnAt(?\DateTime $date): static + { + $this->withdrawnAt = $date; + + return $this; + } } diff --git a/api-investimentos/src/Entity/InvestmentCalculator.php b/api-investimentos/src/Entity/InvestmentCalculator.php index 5ddb49ba6..11b9426b0 100644 --- a/api-investimentos/src/Entity/InvestmentCalculator.php +++ b/api-investimentos/src/Entity/InvestmentCalculator.php @@ -42,8 +42,28 @@ public static function calculateValueWinnings(Investment $investment): float { $initValue = $investment->getInvestmentValue(); $finalValue = InvestmentCalculator::calculateInvestment($investment); - $valueWinnings = ($finalValue - $initValue); + $valueWinnings = $finalValue - ($initValue); return round($valueWinnings, 2); } + + public static function projectFutureBalances(Investment $investment, int $years = 3): array{ + $initValue = $investment->getInvestmentValue(); + $creationDate = $investment->getCreationDate(); + $result = []; + + for ($i = 0; $i <= $years; $i++) { + $months = $i * 12; + $futureValue = $initValue * pow(1.0052, $months); + $futureDate = (clone $creationDate)->modify("+$months months")->format('Y-m-d'); + + $result[] = [ + 'year' => $i, + 'date' => $futureDate, + 'expectedBalance' => round($futureValue, 2) + ]; + } + + return $result; + } } diff --git a/api-investimentos/src/Entity/TakeInvestmentOut.php b/api-investimentos/src/Entity/TakeInvestmentOut.php index 127b5dc87..1035cf6f6 100644 --- a/api-investimentos/src/Entity/TakeInvestmentOut.php +++ b/api-investimentos/src/Entity/TakeInvestmentOut.php @@ -6,37 +6,42 @@ use App\Utils\InvestmentCalculator; class TakeInvestmentOut { - public static function TakeOutInvestment(Investment $investment): float { - $balance = $investment->getInvestmentValue(); - $ownerName = $investment->getOwner(); + + public static function calculateTaxDiscount(float $initValue, float $profit, Investment $investment): float { $creationDate = $investment->getCreationDate(); $currentDate = new \DateTime(); - $ageInvestment = InvestmentCalculator::calculateMonthsBetween($creationDate, $currentDate); - $taxDiscount = 0; - - if ($balance == 0) { - throw new \InvalidArgumentException('Não há saldo nesta conta'); + if ($ageInvestment < 12) { + $taxDiscount = 0.225; // 22,5% + } elseif ($ageInvestment >= 12 && $ageInvestment <= 24) { + $taxDiscount = 0.185; // 18,5% + } else { + $taxDiscount = 0.15; // 15% } - switch (true) { - case $ageInvestment > 12: - $taxDiscount = 0.15; // 15% - break; - case $ageInvestment >= 12 && $ageInvestment <= 24: - $taxDiscount = 0.185; // 18,5% - break; - case $ageInvestment > 24: - $taxDiscount = 0.225; // 22,5% - break; + // Aplica o desconto de imposto APENAS sobre o lucro + $tax = $profit * $taxDiscount; + $finalValue = $initValue + ($profit - $tax); + + return $finalValue; + } + + public static function TakeOutInvestment(Investment $investment): float { + + $initValue = $investment->getInvestmentValue(); + $updatedValue = InvestmentCalculator::calculateInvestment($investment); + $profit = $updatedValue - $initValue; + + if ($updatedValue <= 0) { + throw new \Exception('Não há saldo disponível para saque.'); } - $finalValue = $balance - ($balance * $taxDiscount); // Alterar: desc feito apenas no (invest - ganho) + $finalValue = self::calculateTaxDiscount($initValue, $profit, $investment); $investment->setInvestmentValue(0); + $investment->setWithdrawnAt(new \DateTime()); return round($finalValue, 2); - } } From ea7baa894c65e2e94435380e08a6081d08d463f9 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Sat, 6 Sep 2025 15:13:55 -0300 Subject: [PATCH 14/25] Add new features for pagination and improve code organization --- .../migrations/Version20250906140858.php | 33 ++++++++ .../src/Controller/InvestimentoController.php | 78 +++++++++++-------- .../src/Controller/OwnerController.php | 39 ++++++++++ .../src/Entity/InvestmentCalculator.php | 2 +- .../src/Repository/InvestmentRepository.php | 42 ++++++++++ 5 files changed, 161 insertions(+), 33 deletions(-) create mode 100644 api-investimentos/migrations/Version20250906140858.php create mode 100644 api-investimentos/src/Controller/OwnerController.php diff --git a/api-investimentos/migrations/Version20250906140858.php b/api-investimentos/migrations/Version20250906140858.php new file mode 100644 index 000000000..098ef3c64 --- /dev/null +++ b/api-investimentos/migrations/Version20250906140858.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE investment ADD withdrawn_at DATETIME DEFAULT NULL'); + $this->addSql('ALTER TABLE investment RENAME INDEX fk_investment_owner TO IDX_43CA0AD67E3C61F9'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE investment DROP withdrawn_at'); + $this->addSql('ALTER TABLE investment RENAME INDEX idx_43ca0ad67e3c61f9 TO FK_INVESTMENT_OWNER'); + } +} diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php index 411cc9879..f53470124 100644 --- a/api-investimentos/src/Controller/InvestimentoController.php +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -10,35 +10,10 @@ use Symfony\Component\HttpFoundation\Request; use App\Utils\InvestmentCalculator; use App\Utils\TakeInvestmentOut; +use App\Repository\InvestmentRepository; class InvestimentoController extends AbstractController { - #[Route('/api/clientOwner/create', name: 'create_owner', methods: ['POST'])] - public function createOwner(Request $request, EntityManagerInterface $em): JsonResponse { - - $data = json_decode($request->getContent(), true); - - if (!$data || !isset($data['ownerName'])) { - return $this->json([ - 'error' => 'Parâmetros inválidos. Informe: o nome do proprietário.' - ], 400); - } - - $owner = new \App\Entity\Owner(); - $owner->setName($data['ownerName']); - - $em->persist($owner); - $em->flush(); - - return $this->json([ - 'message' => 'Proprietário criado com sucesso.', - 'owner' => [ - 'id' => $owner->getId(), - 'name' => $owner->getName() - ] - ], 201); - } - #[Route('/api/investments/create', name: 'create_investments', methods: ['POST'])] public function createInvest(Request $request, EntityManagerInterface $em): JsonResponse { @@ -79,7 +54,7 @@ public function createInvest(Request $request, EntityManagerInterface $em): Json } #[Route('/api/investments/list/{ownerId}', name: 'list_investments', methods: ['GET'])] - public function investmentList(int $ownerId, EntityManagerInterface $em): JsonResponse { + public function investmentList(Request $request, int $ownerId, EntityManagerInterface $em, InvestmentRepository $investmentRepository): JsonResponse { $owner = $em->getRepository(\App\Entity\Owner::class)->find($ownerId); @@ -89,14 +64,20 @@ public function investmentList(int $ownerId, EntityManagerInterface $em): JsonRe ], 404); } - $investments = $owner->getInvestments(); - - if (count($investments) === 0) { + if (count($owner->getInvestments()) === 0) { return $this->json([ 'message' => 'Nenhum investimento encontrado para este proprietário.' ], 404); } + $page = max(1, (int) $request->query->get('page', 1)); + $limit = min(50, max(1, (int) $request->query->get('limit', 10))); // proteção + + $paginated = $investmentRepository->findPaginatedByOwner($owner, $page, $limit); + $investments = $paginated['items']; + $total = $paginated['total']; + $pages = $paginated['pages']; + $result = []; foreach ($investments as $investment) { $valueWinnings = InvestmentCalculator::calculateInvestment($investment); @@ -113,6 +94,10 @@ public function investmentList(int $ownerId, EntityManagerInterface $em): JsonRe } return $this->json([ + 'page' => $page, + 'limit' => $limit, + 'total' => $total, + 'pages' => $pages, 'investments' => $result ]); } @@ -147,21 +132,27 @@ public function drawInvestmentAccount(int $id, EntityManagerInterface $em): Json 'ownerName' => $owner ? $owner->getName() : null ]); } - + // Lista de ganhos de investimentos que foram retirados (por proprietário) #[Route('/api/investments/withdrawn-gains/{ownerId}', name: 'list_withdrawn_gains', methods: ['GET'])] public function listWithdrawnGains(int $ownerId, EntityManagerInterface $em): JsonResponse { $owner = $em->getRepository(\App\Entity\Owner::class)->find($ownerId); + if (!$owner) { return $this->json(['error' => 'Proprietário não encontrado.'], 404); } $investments = $em->getRepository(Investment::class)->findBy([ 'owner' => $owner, - // Considera apenas investimentos já retirados 'withdrawnAt' => ['not' => null] ]); + if (!$investments) { + return $this->json([ + 'error' => 'Nenhum investimento encontrado' + ], 404); + } + $result = []; foreach ($investments as $investment) { $gain = InvestmentCalculator::calculateValueWinnings($investment); @@ -174,4 +165,27 @@ public function listWithdrawnGains(int $ownerId, EntityManagerInterface $em): Js return $this->json(['withdrawnGains' => $result]); } + + // lista o saldo futuro de um investimento + #[Route('/api/investments/future-balances/{investmentId}', name: 'investment_future_balances', methods: ['GET'])] + public function projectFutureBalances(int $investmentId, Request $request, EntityManagerInterface $em): JsonResponse { + + $investment = $em->getRepository(Investment::class)->find($investmentId); + + if (!$investment) { + return $this->json(['error' => 'Investimento não encontrado.'], 404); + } + + $years = max(1, (int) $request->query->get('years', 3)); + + $projection = InvestmentCalculator::projectFutureBalances($investment, 3); + + return $this->json([ + 'investmentId' => $investment->getId(), + 'ownerId' => $investment->getOwner()->getId(), + 'ownerName' => $investment->getOwner()->getName(), + 'years' => $years, + 'projections' => $projection + ]); + } } diff --git a/api-investimentos/src/Controller/OwnerController.php b/api-investimentos/src/Controller/OwnerController.php new file mode 100644 index 000000000..1aa2b8a1d --- /dev/null +++ b/api-investimentos/src/Controller/OwnerController.php @@ -0,0 +1,39 @@ +getContent(), true); + + if (!$data || !isset($data['ownerName'])) { + return $this->json([ + 'error' => 'Parâmetros inválidos. Informe: o nome do proprietário.' + ], 400); + } + + $owner = new \App\Entity\Owner(); + $owner->setName($data['ownerName']); + + $em->persist($owner); + $em->flush(); + + return $this->json([ + 'message' => 'Proprietário criado com sucesso.', + 'owner' => [ + 'id' => $owner->getId(), + 'name' => $owner->getName() + ] + ], 201); + } +} diff --git a/api-investimentos/src/Entity/InvestmentCalculator.php b/api-investimentos/src/Entity/InvestmentCalculator.php index 11b9426b0..02e00f65e 100644 --- a/api-investimentos/src/Entity/InvestmentCalculator.php +++ b/api-investimentos/src/Entity/InvestmentCalculator.php @@ -52,7 +52,7 @@ public static function projectFutureBalances(Investment $investment, int $years $creationDate = $investment->getCreationDate(); $result = []; - for ($i = 0; $i <= $years; $i++) { + for ($i = 0; $i -1 <= $years; $i++) { $months = $i * 12; $futureValue = $initValue * pow(1.0052, $months); $futureDate = (clone $creationDate)->modify("+$months months")->format('Y-m-d'); diff --git a/api-investimentos/src/Repository/InvestmentRepository.php b/api-investimentos/src/Repository/InvestmentRepository.php index d0034c838..a3680c2f4 100644 --- a/api-investimentos/src/Repository/InvestmentRepository.php +++ b/api-investimentos/src/Repository/InvestmentRepository.php @@ -2,9 +2,11 @@ namespace App\Repository; +use App\Entity\Owner; use App\Entity\Investment; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\Tools\Pagination\Paginator; /** * @extends ServiceEntityRepository @@ -16,6 +18,46 @@ public function __construct(ManagerRegistry $registry) parent::__construct($registry, Investment::class); } + /** + * Retorna investimentos paginados para um owner + * + * @param Owner $owner + * @param int $page Página (1-based) + * @param int $limit Itens por página + * @return array ['items' => Investment[], 'total' => int, 'pages' => int, 'page' => int, 'limit' => int] + */ + + public function findPaginatedByOwner(Owner $owner, int $page = 1, int $limit = 10): array + { + $page = max(1, $page); + $limit = max(1, $limit); + $offset = ($page - 1) * $limit; + + $qb = $this->createQueryBuilder('i') + ->andWhere('i.owner = :owner') + ->setParameter('owner', $owner) + ->orderBy('i.creationDate', 'DESC') + ->setFirstResult($offset) + ->setMaxResults($limit); + + $query = $qb->getQuery(); + + $paginator = new Paginator($query, true); + $total = count($paginator); + + $items = iterator_to_array($paginator->getIterator()); + + $pages = ($limit > 0) ? (int) ceil($total / $limit) : 0; + + return [ + 'items' => $items, + 'total' => (int) $total, + 'pages' => $pages, + 'page' => $page, + 'limit' => $limit, + ]; + } + // /** // * @return Investment[] Returns an array of Investment objects // */ From 6afe16c5d0e0e8176c4a7672af7ef7063049ee9c Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Sat, 6 Sep 2025 15:20:30 -0300 Subject: [PATCH 15/25] Migration to change the investment entity --- .../migrations/Version20250906181821.php | 31 +++++++++++++++++++ api-investimentos/src/Entity/Investment.php | 14 +++++++++ 2 files changed, 45 insertions(+) create mode 100644 api-investimentos/migrations/Version20250906181821.php diff --git a/api-investimentos/migrations/Version20250906181821.php b/api-investimentos/migrations/Version20250906181821.php new file mode 100644 index 000000000..9c0f8c5cc --- /dev/null +++ b/api-investimentos/migrations/Version20250906181821.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE investment ADD withdrawn_gain DOUBLE PRECISION DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE investment DROP withdrawn_gain'); + } +} diff --git a/api-investimentos/src/Entity/Investment.php b/api-investimentos/src/Entity/Investment.php index da6d54366..b637e35c2 100644 --- a/api-investimentos/src/Entity/Investment.php +++ b/api-investimentos/src/Entity/Investment.php @@ -14,6 +14,9 @@ class Investment #[ORM\Column] private ?int $id = null; + #[ORM\Column(type: 'float', nullable: true)] + private ?float $withdrawnGain = null; + #[ORM\Column] private ?\DateTime $creationDate = null; @@ -88,4 +91,15 @@ public function setWithdrawnAt(?\DateTime $date): static return $this; } + + public function getWithdrawnGain(): ?float + { + return $this->withdrawnGain; + } + + public function setWithdrawnGain(?float $withdrawnGain): self + { + $this->withdrawnGain = $withdrawnGain; + return $this; + } } From 49f095acba442414ec11a84047249a6ddc69a7d3 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Sat, 6 Sep 2025 16:30:54 -0300 Subject: [PATCH 16/25] Files modified for new features and organization --- .../src/Controller/InvestimentoController.php | 27 ++++++++++--------- .../src/Entity/TakeInvestmentOut.php | 1 + .../src/Repository/InvestmentRepository.php | 11 ++++++++ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php index f53470124..ca811c0e4 100644 --- a/api-investimentos/src/Controller/InvestimentoController.php +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -104,6 +104,7 @@ public function investmentList(Request $request, int $ownerId, EntityManagerInte #[Route('/api/investments/draw/{id}', name: 'draw_investments', methods: ['PUT'])] public function drawInvestmentAccount(int $id, EntityManagerInterface $em): JsonResponse { + $investment = $em->getRepository(Investment::class)->find($id); if (!$investment) { @@ -132,9 +133,9 @@ public function drawInvestmentAccount(int $id, EntityManagerInterface $em): Json 'ownerName' => $owner ? $owner->getName() : null ]); } - // Lista de ganhos de investimentos que foram retirados (por proprietário) + #[Route('/api/investments/withdrawn-gains/{ownerId}', name: 'list_withdrawn_gains', methods: ['GET'])] - public function listWithdrawnGains(int $ownerId, EntityManagerInterface $em): JsonResponse + public function listWithdrawnGains(int $ownerId, EntityManagerInterface $em, InvestmentRepository $investmentRepository,): JsonResponse { $owner = $em->getRepository(\App\Entity\Owner::class)->find($ownerId); @@ -142,28 +143,30 @@ public function listWithdrawnGains(int $ownerId, EntityManagerInterface $em): Js return $this->json(['error' => 'Proprietário não encontrado.'], 404); } - $investments = $em->getRepository(Investment::class)->findBy([ - 'owner' => $owner, - 'withdrawnAt' => ['not' => null] - ]); + $investments = $investmentRepository->findWithdrawnByOwner($owner); - if (!$investments) { - return $this->json([ - 'error' => 'Nenhum investimento encontrado' + if (count($investments) === 0) { + return $this->json([ + 'error' => 'Nenhum investimento retirado encontrado' ], 404); } $result = []; foreach ($investments as $investment) { - $gain = InvestmentCalculator::calculateValueWinnings($investment); + $gain = $investment->getWithdrawnGain(); $result[] = [ 'investmentId' => $investment->getId(), 'withdrawnAt' => $investment->getWithdrawnAt()?->format('Y-m-d H:i:s'), - 'gain' => $gain + 'profit' => $investment->getWithdrawnGain(), + 'ownerId' => $owner->getId(), + 'ownerName' => $owner->getName() ]; } - return $this->json(['withdrawnGains' => $result]); + return $this->json([ + 'withdrawnGains' => $result, + 'total' => count($investments) + ]); } // lista o saldo futuro de um investimento diff --git a/api-investimentos/src/Entity/TakeInvestmentOut.php b/api-investimentos/src/Entity/TakeInvestmentOut.php index 1035cf6f6..f067b2e1c 100644 --- a/api-investimentos/src/Entity/TakeInvestmentOut.php +++ b/api-investimentos/src/Entity/TakeInvestmentOut.php @@ -41,6 +41,7 @@ public static function TakeOutInvestment(Investment $investment): float { $investment->setInvestmentValue(0); $investment->setWithdrawnAt(new \DateTime()); + $investment->setWithdrawnGain($profit); return round($finalValue, 2); } diff --git a/api-investimentos/src/Repository/InvestmentRepository.php b/api-investimentos/src/Repository/InvestmentRepository.php index a3680c2f4..42ea82905 100644 --- a/api-investimentos/src/Repository/InvestmentRepository.php +++ b/api-investimentos/src/Repository/InvestmentRepository.php @@ -58,6 +58,17 @@ public function findPaginatedByOwner(Owner $owner, int $page = 1, int $limit = 1 ]; } + public function findWithdrawnByOwner($owner): array { + + return $this->createQueryBuilder('i') + ->where('i.owner = :owner') + ->andWhere('i.withdrawnAt IS NOT NULL') + ->setParameter('owner', $owner) + ->getQuery() + ->getResult(); + + } + // /** // * @return Investment[] Returns an array of Investment objects // */ From 9311b374ec11d7ed3403a75bcc4b28da7a040dd8 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Sat, 6 Sep 2025 18:22:31 -0300 Subject: [PATCH 17/25] Required extensions added, such as php unit, twig bundle, and Nelmio. Controller files modified to generate documentation --- api-investimentos/.gitignore | 6 + api-investimentos/bin/phpunit | 23 + api-investimentos/composer.json | 8 +- api-investimentos/composer.lock | 3242 +++++++++++++++-- api-investimentos/config/bundles.php | 3 + .../config/packages/nelmio_api_doc.yaml | 9 + .../config/packages/property_info.yaml | 3 + api-investimentos/config/packages/twig.yaml | 6 + .../config/routes/nelmio_api_doc.yaml | 5 + api-investimentos/phpunit.dist.xml | 44 + .../src/Controller/InvestimentoController.php | 171 +- .../src/Controller/OwnerController.php | 61 +- api-investimentos/symfony.lock | 56 + api-investimentos/templates/base.html.twig | 16 + api-investimentos/tests/bootstrap.php | 13 + 15 files changed, 3392 insertions(+), 274 deletions(-) create mode 100644 api-investimentos/bin/phpunit create mode 100644 api-investimentos/config/packages/nelmio_api_doc.yaml create mode 100644 api-investimentos/config/packages/property_info.yaml create mode 100644 api-investimentos/config/packages/twig.yaml create mode 100644 api-investimentos/config/routes/nelmio_api_doc.yaml create mode 100644 api-investimentos/phpunit.dist.xml create mode 100644 api-investimentos/templates/base.html.twig create mode 100644 api-investimentos/tests/bootstrap.php diff --git a/api-investimentos/.gitignore b/api-investimentos/.gitignore index a586de575..915e52710 100644 --- a/api-investimentos/.gitignore +++ b/api-investimentos/.gitignore @@ -2,6 +2,7 @@ ###> symfony/framework-bundle ### .env .env.dev +.env.test /.env.local /.env.local.php /.env.*.local @@ -10,3 +11,8 @@ /var/ /vendor/ ###< symfony/framework-bundle ### + +###> phpunit/phpunit ### +/phpunit.xml +/.phpunit.cache/ +###< phpunit/phpunit ### diff --git a/api-investimentos/bin/phpunit b/api-investimentos/bin/phpunit new file mode 100644 index 000000000..692baccb6 --- /dev/null +++ b/api-investimentos/bin/phpunit @@ -0,0 +1,23 @@ +#!/usr/bin/env php += 80000) { + require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit'; + } else { + define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php'); + require PHPUNIT_COMPOSER_INSTALL; + PHPUnit\TextUI\Command::main(); + } +} else { + if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) { + echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n"; + exit(1); + } + + require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php'; +} diff --git a/api-investimentos/composer.json b/api-investimentos/composer.json index 13ca6010e..84d034392 100644 --- a/api-investimentos/composer.json +++ b/api-investimentos/composer.json @@ -13,6 +13,8 @@ "doctrine/doctrine-bundle": "^2.16", "doctrine/doctrine-migrations-bundle": "^3.4", "doctrine/orm": "^3.5", + "nelmio/api-doc-bundle": "^5.6", + "symfony/asset": "7.3.*", "symfony/console": "7.3.*", "symfony/dotenv": "7.3.*", "symfony/flex": "^2", @@ -20,7 +22,10 @@ "symfony/http-foundation": "7.3.*", "symfony/routing": "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": { @@ -73,6 +78,7 @@ } }, "require-dev": { + "phpunit/phpunit": "^12.3", "symfony/maker-bundle": "^1.64" } } diff --git a/api-investimentos/composer.lock b/api-investimentos/composer.lock index 4e92dcce1..c04b0f9bc 100644 --- a/api-investimentos/composer.lock +++ b/api-investimentos/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": "0aee3da215fdbcd8ad350f8110de3f42", + "content-hash": "3bc8fc5fc37bd95f68f81ba77040aff4", "packages": [ { "name": "doctrine/collections", @@ -1130,6 +1130,409 @@ }, "time": "2025-01-24T11:45:48+00:00" }, + { + "name": "nelmio/api-doc-bundle", + "version": "v5.6.0", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioApiDocBundle.git", + "reference": "7bf53803978503ed912e9025c98eaa180bf350c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/7bf53803978503ed912e9025c98eaa180bf350c6", + "reference": "7bf53803978503ed912e9025c98eaa180bf350c6", + "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.6.0" + }, + "funding": [ + { + "url": "https://github.com/DjordyKoert", + "type": "github" + } + ], + "time": "2025-09-03T11:03:27+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + }, + "time": "2025-08-13T20:13:15+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.3", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", + "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.3" + }, + "time": "2025-08-01T19:43:32+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.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", + "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.3.0" + }, + "time": "2025-08-30T15:50:23+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -1333,31 +1736,100 @@ "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-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" }, @@ -2839,6 +3311,77 @@ ], "time": "2025-08-29T08:23:45+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "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.3" + }, + "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-08-05T10:16:07+00:00" + }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.33.0", @@ -3251,6 +3794,92 @@ ], "time": "2025-06-24T13:30:11+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", @@ -3629,331 +4258,2476 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.3" + "source": "https://github.com/symfony/string/tree/v7.3.3" + }, + "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-08-25T06:35:40+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "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 translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-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-27T08:32:26+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "33558f013b7f6ed72805527c8405cae0062e47c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/33558f013b7f6ed72805527c8405cae0062e47c5", + "reference": "33558f013b7f6ed72805527c8405cae0062e47c5", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.21" + }, + "conflict": { + "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" + }, + "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\\Bridge\\Twig\\": "" + }, + "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 Twig with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bridge/tree/v7.3.3" + }, + "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-08-18T13:10:53+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "5d85220df4d8d79e6a9ca57eea6f70004de39657" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/5d85220df4d8d79e6a9ca57eea6f70004de39657", + "reference": "5d85220df4d8d79e6a9ca57eea6f70004de39657", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.2", + "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" + }, + "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\\Bundle\\TwigBundle\\": "" + }, + "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 of Twig into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-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-10T08:47:49+00:00" + }, + { + "name": "symfony/type-info", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "aa64b58ed04517d4d730202dd035895743c23273" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/aa64b58ed04517d4d730202dd035895743c23273", + "reference": "aa64b58ed04517d4d730202dd035895743c23273", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "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": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v7.3.3" + }, + "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-08-28T09:38:04+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", + "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.3" + }, + "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-08-13T11:49:31+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", + "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", + "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.3" + }, + "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-08-18T13:10:53+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", + "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", + "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.3" + }, + "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-08-27T11:34:33+00:00" + }, + { + "name": "twig/extra-bundle", + "version": "v3.21.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "62d1cf47a1aa009cbd07b21045b97d3d5cb79896" + }, + "dist": { + "type": "zip", + "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.3.2", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "d8fa9dc4c3b2fc8651ae780021bb9719b1e63d40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/d8fa9dc4c3b2fc8651ae780021bb9719b1e63d40", + "reference": "d8fa9dc4c3b2fc8651ae780021bb9719b1e63d40", + "shasum": "" + }, + "require": { + "ext-json": "*", + "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": { + "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/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "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": "Generate interactive documentation for your RESTful API using PHP attributes (preferred) or PHPDoc annotations", + "homepage": "https://github.com/zircote/swagger-php", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/5.3.2" + }, + "time": "2025-08-25T21:57:16+00:00" + } + ], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "12.3.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "da2cdaff87220fa641e7652364281b736e4347e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/da2cdaff87220fa641e7652364281b736e4347e0", + "reference": "da2cdaff87220fa641e7652364281b736e4347e0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.6.1", + "php": ">=8.3", + "phpunit/php-file-iterator": "^6.0", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.0.3", + "sebastian/lines-of-code": "^4.0", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.3.7" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-09-02T05:23:14+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "961bc913d42fe24a257bfff826a5068079ac7782" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782", + "reference": "961bc913d42fe24a257bfff826a5068079ac7782", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:37+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:58+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:16+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "12.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "9d68c1b41fc21aac106c71cde4669fe7b99fca10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9d68c1b41fc21aac106c71cde4669fe7b99fca10", + "reference": "9d68c1b41fc21aac106c71cde4669fe7b99fca10", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.3.6", + "phpunit/php-file-iterator": "^6.0.0", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.0.0", + "sebastian/comparator": "^7.1.3", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.0.3", + "sebastian/exporter": "^7.0.0", + "sebastian/global-state": "^8.0.2", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/type": "^6.0.3", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.3-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.8" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-09-03T06:25:17+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/6d584c727d9114bcdc14c86711cd1cad51778e7c", + "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:53:50+00:00" + }, + { + "name": "sebastian/comparator", + "version": "7.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.2" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2025-08-20T11:27:00+00:00" + }, + { + "name": "sebastian/complexity", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "8.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-08-12T14:11:56+00:00" + }, + { + "name": "sebastian/exporter", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "76432aafc58d50691a00d86d0632f1217a47b688" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/76432aafc58d50691a00d86d0632f1217a47b688", + "reference": "76432aafc58d50691a00d86d0632f1217a47b688", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:56:42+00:00" + }, + { + "name": "sebastian/global-state", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-29T11:29:25+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:28+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:48+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2025-08-25T06:35:40+00:00" + "time": "2025-02-07T04:58:17+00:00" }, { - "name": "symfony/var-dumper", - "version": "v7.3.3", + "name": "sebastian/recursion-context", + "version": "7.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", - "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/console": "<6.4" + "php": ">=8.3" }, "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" + "phpunit/phpunit": "^12.0" }, - "bin": [ - "Resources/bin/var-dump-server" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.3" + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", "type": "tidelift" } ], - "time": "2025-08-13T11:49:31+00:00" + "time": "2025-08-13T04:44:59+00:00" }, { - "name": "symfony/var-exporter", - "version": "v7.3.3", + "name": "sebastian/type", + "version": "6.0.3", "source": { "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", - "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.3" }, "require-dev": { - "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "phpunit/phpunit": "^12.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "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" - ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.3.3" + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" + "url": "https://github.com/sebastianbergmann", + "type": "github" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" }, { - "url": "https://github.com/nicolas-grekas", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", "type": "tidelift" } ], - "time": "2025-08-18T13:10:53+00:00" + "time": "2025-08-09T06:57:12+00:00" }, { - "name": "symfony/yaml", - "version": "v7.3.3", + "name": "sebastian/version", + "version": "6.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", - "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<6.4" + "php": ">=8.3" }, - "require-dev": { - "symfony/console": "^6.4|^7.0" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.3" + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2025-08-27T11:34:33+00:00" - } - ], - "packages-dev": [ + "time": "2025-02-07T05:00:38+00:00" + }, { - "name": "nikic/php-parser", - "version": "v5.6.1", + "name": "staabm/side-effects-detector", + "version": "1.0.5", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.4" + "php": "^7.4 || ^8.0" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" }, - "bin": [ - "bin/php-parse" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } + "classmap": [ + "lib/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } + "MIT" ], - "description": "A PHP parser written in PHP", + "description": "A static analysis tool to detect side effects in PHP code", "keywords": [ - "parser", - "php" + "static analysis" ], "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" }, - "time": "2025-08-13T20:13:15+00:00" + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" }, { "name": "symfony/maker-bundle", @@ -4112,11 +6886,61 @@ } ], "time": "2025-08-18T09:42:54+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -4124,6 +6948,6 @@ "ext-ctype": "*", "ext-iconv": "*" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/api-investimentos/config/bundles.php b/api-investimentos/config/bundles.php index de8898b23..008630c59 100644 --- a/api-investimentos/config/bundles.php +++ b/api-investimentos/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/api-investimentos/config/packages/nelmio_api_doc.yaml b/api-investimentos/config/packages/nelmio_api_doc.yaml new file mode 100644 index 000000000..1ced4adcb --- /dev/null +++ b/api-investimentos/config/packages/nelmio_api_doc.yaml @@ -0,0 +1,9 @@ +nelmio_api_doc: + documentation: + info: + title: API do Back-End Test + description: Documentação interativa da API + version: 1.0.0 + areas: + default: + path_patterns: [ ^/api ] diff --git a/api-investimentos/config/packages/property_info.yaml b/api-investimentos/config/packages/property_info.yaml new file mode 100644 index 000000000..dd31b9da2 --- /dev/null +++ b/api-investimentos/config/packages/property_info.yaml @@ -0,0 +1,3 @@ +framework: + property_info: + with_constructor_extractor: true diff --git a/api-investimentos/config/packages/twig.yaml b/api-investimentos/config/packages/twig.yaml new file mode 100644 index 000000000..3f795d921 --- /dev/null +++ b/api-investimentos/config/packages/twig.yaml @@ -0,0 +1,6 @@ +twig: + file_name_pattern: '*.twig' + +when@test: + twig: + strict_variables: true diff --git a/api-investimentos/config/routes/nelmio_api_doc.yaml b/api-investimentos/config/routes/nelmio_api_doc.yaml new file mode 100644 index 000000000..98c6bce63 --- /dev/null +++ b/api-investimentos/config/routes/nelmio_api_doc.yaml @@ -0,0 +1,5 @@ +app.swagger_ui: + path: /api/doc + methods: [GET] + defaults: + _controller: 'nelmio_api_doc.controller.swagger_ui' diff --git a/api-investimentos/phpunit.dist.xml b/api-investimentos/phpunit.dist.xml new file mode 100644 index 000000000..22bd8791e --- /dev/null +++ b/api-investimentos/phpunit.dist.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + tests + + + + + + src + + + + Doctrine\Deprecations\Deprecation::trigger + Doctrine\Deprecations\Deprecation::delegateTriggerToBackend + trigger_deprecation + + + + + + diff --git a/api-investimentos/src/Controller/InvestimentoController.php b/api-investimentos/src/Controller/InvestimentoController.php index ca811c0e4..d09ebff1c 100644 --- a/api-investimentos/src/Controller/InvestimentoController.php +++ b/api-investimentos/src/Controller/InvestimentoController.php @@ -4,39 +4,62 @@ use Doctrine\ORM\EntityManagerInterface; use App\Entity\Investment; +use App\Entity\Owner; +use App\Repository\InvestmentRepository; +use App\Utils\InvestmentCalculator; +use App\Utils\TakeInvestmentOut; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Request; -use App\Utils\InvestmentCalculator; -use App\Utils\TakeInvestmentOut; -use App\Repository\InvestmentRepository; - -class InvestimentoController extends AbstractController { +use OpenApi\Attributes as OA; +class InvestimentoController extends AbstractController +{ + // ========================= + // POST /api/investments/create + // ========================= #[Route('/api/investments/create', name: 'create_investments', methods: ['POST'])] - public function createInvest(Request $request, EntityManagerInterface $em): JsonResponse { - + #[OA\Post( + path: '/api/investments/create', + summary: 'Cria um novo investimento', + description: 'Cria um investimento para um proprietário existente com data e valor.', + tags: ['Investments'] + )] + #[OA\RequestBody( + required: true, + description: 'Dados do investimento', + content: new OA\JsonContent( + required: ['ownerId', 'creationDate', 'investmentValue'], + properties: [ + new OA\Property(property: 'ownerId', type: 'integer', example: 1, description: 'ID do proprietário'), + new OA\Property(property: 'creationDate', type: 'string', format: 'date-time', example: '2025-09-06T00:00:00', description: 'Data de criação do investimento'), + new OA\Property(property: 'investmentValue', type: 'number', format: 'float', example: 1000.50, description: 'Valor do investimento') + ] + ) + )] + #[OA\Response(response: 201, description: 'Investimento criado com sucesso')] + #[OA\Response(response: 400, description: 'Parâmetros inválidos')] + #[OA\Response(response: 404, description: 'Proprietário não encontrado')] + public function createInvest(Request $request, EntityManagerInterface $em): JsonResponse + { $data = json_decode($request->getContent(), true); if (!$data || !isset($data['ownerId'], $data['creationDate'], $data['investmentValue'])) { return $this->json([ - 'error' => 'Parâmetros inválidos. Informe: ID do proprietário (ownerId), data da criação do investimento (creationDate) e valor do investimento (investmentValue).' + 'error' => 'Parâmetros inválidos. Informe: ownerId, creationDate e investmentValue.' ], 400); } - // Busca o Owner pelo ID - $owner = $em->getRepository(\App\Entity\Owner::class)->find($data['ownerId']); + $owner = $em->getRepository(Owner::class)->find($data['ownerId']); if (!$owner) { - return $this->json([ - 'error' => 'Proprietário não encontrado.' - ], 404); + return $this->json(['error' => 'Proprietário não encontrado.'], 404); } $investment = new Investment(); $investment->setOwner($owner); $investment->setCreationDate(new \DateTime($data['creationDate'])); - $investment->setInvestmentValue((float) $data['investmentValue']); + $investment->setInvestmentValue((float)$data['investmentValue']); $em->persist($investment); $em->flush(); @@ -53,64 +76,78 @@ public function createInvest(Request $request, EntityManagerInterface $em): Json ], 201); } + // ========================= + // GET /api/investments/list/{ownerId} + // ========================= #[Route('/api/investments/list/{ownerId}', name: 'list_investments', methods: ['GET'])] - public function investmentList(Request $request, int $ownerId, EntityManagerInterface $em, InvestmentRepository $investmentRepository): JsonResponse { - - $owner = $em->getRepository(\App\Entity\Owner::class)->find($ownerId); - + #[OA\Get( + path: '/api/investments/list/{ownerId}', + summary: 'Lista investimentos de um proprietário', + description: 'Retorna a lista de investimentos de um proprietário com paginação.', + tags: ['Investments'] + )] + #[OA\Parameter(name: 'ownerId', in: 'path', required: true, description: 'ID do proprietário', schema: new OA\Schema(type: 'integer'))] + #[OA\Parameter(name: 'page', in: 'query', required: false, description: 'Número da página', schema: new OA\Schema(type: 'integer', default: 1))] + #[OA\Parameter(name: 'limit', in: 'query', required: false, description: 'Itens por página', schema: new OA\Schema(type: 'integer', default: 10))] + #[OA\Response(response: 200, description: 'Lista de investimentos retornada com sucesso')] + #[OA\Response(response: 404, description: 'Proprietário não encontrado ou sem investimentos')] + public function investmentList(Request $request, int $ownerId, EntityManagerInterface $em, InvestmentRepository $investmentRepository): JsonResponse + { + $owner = $em->getRepository(Owner::class)->find($ownerId); if (!$owner) { - return $this->json([ - 'message' => 'Proprietário não encontrado.' - ], 404); + return $this->json(['message' => 'Proprietário não encontrado.'], 404); } if (count($owner->getInvestments()) === 0) { - return $this->json([ - 'message' => 'Nenhum investimento encontrado para este proprietário.' - ], 404); + return $this->json(['message' => 'Nenhum investimento encontrado.'], 404); } - $page = max(1, (int) $request->query->get('page', 1)); - $limit = min(50, max(1, (int) $request->query->get('limit', 10))); // proteção + $page = max(1, (int)$request->query->get('page', 1)); + $limit = min(50, max(1, (int)$request->query->get('limit', 10))); $paginated = $investmentRepository->findPaginatedByOwner($owner, $page, $limit); $investments = $paginated['items']; - $total = $paginated['total']; - $pages = $paginated['pages']; - $result = []; + foreach ($investments as $investment) { - $valueWinnings = InvestmentCalculator::calculateInvestment($investment); - $valueWinningsOnly = InvestmentCalculator::calculateValueWinnings($investment); $result[] = [ 'id' => $investment->getId(), 'ownerId' => $owner->getId(), 'ownerName' => $owner->getName(), 'creationDate' => $investment->getCreationDate()->format('Y-m-d H:i:s'), 'investmentValue' => $investment->getInvestmentValue(), - 'valueWithWinnings' => $valueWinnings, - 'winningsValueOnly' => $valueWinningsOnly, + 'valueWithWinnings' => InvestmentCalculator::calculateInvestment($investment), + 'winningsValueOnly' => InvestmentCalculator::calculateValueWinnings($investment), ]; } return $this->json([ 'page' => $page, 'limit' => $limit, - 'total' => $total, - 'pages' => $pages, + 'total' => $paginated['total'], + 'pages' => $paginated['pages'], 'investments' => $result ]); } + // ========================= + // PUT /api/investments/draw/{id} + // ========================= #[Route('/api/investments/draw/{id}', name: 'draw_investments', methods: ['PUT'])] - public function drawInvestmentAccount(int $id, EntityManagerInterface $em): JsonResponse { - + #[OA\Put( + path: '/api/investments/draw/{id}', + summary: 'Saca um investimento', + description: 'Realiza o saque de um investimento específico.', + tags: ['Investments'] + )] + #[OA\Parameter(name: 'id', in: 'path', required: true, description: 'ID do investimento', schema: new OA\Schema(type: 'integer'))] + #[OA\Response(response: 200, description: 'Saque realizado com sucesso')] + #[OA\Response(response: 400, description: 'Investimento não encontrado ou erro no saque')] + public function drawInvestmentAccount(int $id, EntityManagerInterface $em): JsonResponse + { $investment = $em->getRepository(Investment::class)->find($id); - if (!$investment) { - return $this->json([ - 'error' => 'Investimento não encontrado' - ], 400); + return $this->json(['error' => 'Investimento não encontrado'], 400); } try { @@ -118,9 +155,7 @@ public function drawInvestmentAccount(int $id, EntityManagerInterface $em): Json $em->persist($investment); $em->flush(); } catch (\InvalidArgumentException $e) { - return $this->json([ - 'error' => $e->getMessage() - ], 400); + return $this->json(['error' => $e->getMessage()], 400); } $owner = $investment->getOwner(); @@ -134,26 +169,33 @@ public function drawInvestmentAccount(int $id, EntityManagerInterface $em): Json ]); } + // ========================= + // GET /api/investments/withdrawn-gains/{ownerId} + // ========================= #[Route('/api/investments/withdrawn-gains/{ownerId}', name: 'list_withdrawn_gains', methods: ['GET'])] - public function listWithdrawnGains(int $ownerId, EntityManagerInterface $em, InvestmentRepository $investmentRepository,): JsonResponse + #[OA\Get( + path: '/api/investments/withdrawn-gains/{ownerId}', + summary: 'Lista ganhos retirados de um proprietário', + description: 'Retorna todos os ganhos que já foram retirados por um proprietário.', + tags: ['Investments'] + )] + #[OA\Parameter(name: 'ownerId', in: 'path', required: true, description: 'ID do proprietário', schema: new OA\Schema(type: 'integer'))] + #[OA\Response(response: 200, description: 'Lista de ganhos retirados retornada com sucesso')] + #[OA\Response(response: 404, description: 'Proprietário ou ganhos não encontrados')] + public function listWithdrawnGains(int $ownerId, EntityManagerInterface $em, InvestmentRepository $investmentRepository): JsonResponse { - $owner = $em->getRepository(\App\Entity\Owner::class)->find($ownerId); - + $owner = $em->getRepository(Owner::class)->find($ownerId); if (!$owner) { return $this->json(['error' => 'Proprietário não encontrado.'], 404); } $investments = $investmentRepository->findWithdrawnByOwner($owner); - if (count($investments) === 0) { - return $this->json([ - 'error' => 'Nenhum investimento retirado encontrado' - ], 404); + return $this->json(['error' => 'Nenhum investimento retirado encontrado'], 404); } $result = []; foreach ($investments as $investment) { - $gain = $investment->getWithdrawnGain(); $result[] = [ 'investmentId' => $investment->getId(), 'withdrawnAt' => $investment->getWithdrawnAt()?->format('Y-m-d H:i:s'), @@ -169,19 +211,29 @@ public function listWithdrawnGains(int $ownerId, EntityManagerInterface $em, Inv ]); } - // lista o saldo futuro de um investimento + // ========================= + // GET /api/investments/future-balances/{investmentId} + // ========================= #[Route('/api/investments/future-balances/{investmentId}', name: 'investment_future_balances', methods: ['GET'])] - public function projectFutureBalances(int $investmentId, Request $request, EntityManagerInterface $em): JsonResponse { - + #[OA\Get( + path: '/api/investments/future-balances/{investmentId}', + summary: 'Projeta saldos futuros de um investimento', + description: 'Calcula o saldo futuro do investimento ao longo de anos especificados.', + tags: ['Investments'] + )] + #[OA\Parameter(name: 'investmentId', in: 'path', required: true, description: 'ID do investimento', schema: new OA\Schema(type: 'integer'))] + #[OA\Parameter(name: 'years', in: 'query', required: false, description: 'Número de anos para projeção', schema: new OA\Schema(type: 'integer', default: 3))] + #[OA\Response(response: 200, description: 'Projeção retornada com sucesso')] + #[OA\Response(response: 404, description: 'Investimento não encontrado')] + public function projectFutureBalances(int $investmentId, Request $request, EntityManagerInterface $em): JsonResponse + { $investment = $em->getRepository(Investment::class)->find($investmentId); - if (!$investment) { return $this->json(['error' => 'Investimento não encontrado.'], 404); } - $years = max(1, (int) $request->query->get('years', 3)); - - $projection = InvestmentCalculator::projectFutureBalances($investment, 3); + $years = max(1, (int)$request->query->get('years', 3)); + $projection = InvestmentCalculator::projectFutureBalances($investment, $years); return $this->json([ 'investmentId' => $investment->getId(), @@ -192,3 +244,4 @@ public function projectFutureBalances(int $investmentId, Request $request, Entit ]); } } + diff --git a/api-investimentos/src/Controller/OwnerController.php b/api-investimentos/src/Controller/OwnerController.php index 1aa2b8a1d..14955dcaf 100644 --- a/api-investimentos/src/Controller/OwnerController.php +++ b/api-investimentos/src/Controller/OwnerController.php @@ -6,14 +6,64 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Request; - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use OpenApi\Attributes as OA; -class OwnerController extends AbstractController { - +class OwnerController extends AbstractController +{ + // ========================= + // POST /api/clientowner/create + // ========================= #[Route('/api/clientowner/create', name: 'create_owner', methods: ['POST'])] - public function createOwner(Request $request, EntityManagerInterface $em): JsonResponse { - + #[OA\Post( + path: '/api/clientowner/create', + summary: 'Cria um novo proprietário', + description: 'Cria um proprietário (Owner) com um nome informado.', + tags: ['Owners'] + )] + #[OA\RequestBody( + required: true, + description: 'Dados do proprietário', + content: new OA\JsonContent( + required: ['ownerName'], + properties: [ + new OA\Property( + property: 'ownerName', + type: 'string', + example: 'João Silva', + description: 'Nome do proprietário a ser criado' + ) + ] + ) + )] + #[OA\Response( + response: 201, + description: 'Proprietário criado com sucesso', + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'message', type: 'string', example: 'Proprietário criado com sucesso.'), + new OA\Property( + property: 'owner', + type: 'object', + properties: [ + new OA\Property(property: 'id', type: 'integer', example: 1), + new OA\Property(property: 'name', type: 'string', example: 'João Silva') + ] + ) + ] + ) + )] + #[OA\Response( + response: 400, + description: 'Parâmetros inválidos', + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'error', type: 'string', example: 'Parâmetros inválidos. Informe: o nome do proprietário.') + ] + ) + )] + public function createOwner(Request $request, EntityManagerInterface $em): JsonResponse + { $data = json_decode($request->getContent(), true); if (!$data || !isset($data['ownerName'])) { @@ -37,3 +87,4 @@ public function createOwner(Request $request, EntityManagerInterface $em): JsonR ], 201); } } + diff --git a/api-investimentos/symfony.lock b/api-investimentos/symfony.lock index fdc6ce55c..fa19c8a19 100644 --- a/api-investimentos/symfony.lock +++ b/api-investimentos/symfony.lock @@ -35,6 +35,34 @@ "./migrations/.gitignore" ] }, + "nelmio/api-doc-bundle": { + "version": "5.6", + "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" + ] + }, + "phpunit/phpunit": { + "version": "12.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "11.1", + "ref": "c6658a60fc9d594805370eacdf542c3d6b5c0869" + }, + "files": [ + "./.env.test", + "./phpunit.dist.xml", + "./tests/bootstrap.php", + "./bin/phpunit" + ] + }, "symfony/console": { "version": "7.3", "recipe": { @@ -89,6 +117,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 +141,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/api-investimentos/templates/base.html.twig b/api-investimentos/templates/base.html.twig new file mode 100644 index 000000000..1069c1476 --- /dev/null +++ b/api-investimentos/templates/base.html.twig @@ -0,0 +1,16 @@ + + + + + {% block title %}Welcome!{% endblock %} + + {% block stylesheets %} + {% endblock %} + + {% block javascripts %} + {% endblock %} + + + {% block body %}{% endblock %} + + diff --git a/api-investimentos/tests/bootstrap.php b/api-investimentos/tests/bootstrap.php new file mode 100644 index 000000000..47a58557d --- /dev/null +++ b/api-investimentos/tests/bootstrap.php @@ -0,0 +1,13 @@ +bootEnv(dirname(__DIR__).'/.env'); +} + +if ($_SERVER['APP_DEBUG']) { + umask(0000); +} From d6e6a11682fc683c2eb5db6770d40f82483418a6 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Sun, 7 Sep 2025 01:52:56 -0300 Subject: [PATCH 18/25] Better organized files, unit tests created to cover InvestmentCalculator --- api-investimentos/src/Entity/Investment.php | 13 +-- .../InvestmentCalculator.php | 2 +- .../{Entity => Utils}/TakeInvestmentOut.php | 0 .../tests/Utils/InvestmentCalculatorTest.php | 98 +++++++++++++++++++ 4 files changed, 104 insertions(+), 9 deletions(-) rename api-investimentos/src/{Entity => Utils}/InvestmentCalculator.php (97%) rename api-investimentos/src/{Entity => Utils}/TakeInvestmentOut.php (100%) create mode 100644 api-investimentos/tests/Utils/InvestmentCalculatorTest.php diff --git a/api-investimentos/src/Entity/Investment.php b/api-investimentos/src/Entity/Investment.php index b637e35c2..ab5705d47 100644 --- a/api-investimentos/src/Entity/Investment.php +++ b/api-investimentos/src/Entity/Investment.php @@ -18,7 +18,7 @@ class Investment private ?float $withdrawnGain = null; #[ORM\Column] - private ?\DateTime $creationDate = null; + private $creationDate = null; #[ORM\Column] private ?float $investmentValue = null; @@ -47,21 +47,18 @@ public function setOwner(Owner $owner): static return $this; } - public function getCreationDate(): ?\DateTime + public function getCreationDate() { return $this->creationDate; } - public function setCreationDate(\DateTime $creationDate): static + public function setCreationDate($creationDate): void { - $now = new \DateTime(); - if ($creationDate > $now) { - throw new \InvalidArgumentException('A data de criação não pode ser futura'); + if (!$creationDate instanceof \DateTime) { + throw new \InvalidArgumentException("Creation date must be a DateTime object."); } $this->creationDate = $creationDate; - - return $this; } public function getInvestmentValue(): ?float diff --git a/api-investimentos/src/Entity/InvestmentCalculator.php b/api-investimentos/src/Utils/InvestmentCalculator.php similarity index 97% rename from api-investimentos/src/Entity/InvestmentCalculator.php rename to api-investimentos/src/Utils/InvestmentCalculator.php index 02e00f65e..11b9426b0 100644 --- a/api-investimentos/src/Entity/InvestmentCalculator.php +++ b/api-investimentos/src/Utils/InvestmentCalculator.php @@ -52,7 +52,7 @@ public static function projectFutureBalances(Investment $investment, int $years $creationDate = $investment->getCreationDate(); $result = []; - for ($i = 0; $i -1 <= $years; $i++) { + for ($i = 0; $i <= $years; $i++) { $months = $i * 12; $futureValue = $initValue * pow(1.0052, $months); $futureDate = (clone $creationDate)->modify("+$months months")->format('Y-m-d'); diff --git a/api-investimentos/src/Entity/TakeInvestmentOut.php b/api-investimentos/src/Utils/TakeInvestmentOut.php similarity index 100% rename from api-investimentos/src/Entity/TakeInvestmentOut.php rename to api-investimentos/src/Utils/TakeInvestmentOut.php diff --git a/api-investimentos/tests/Utils/InvestmentCalculatorTest.php b/api-investimentos/tests/Utils/InvestmentCalculatorTest.php new file mode 100644 index 000000000..4564b36f5 --- /dev/null +++ b/api-investimentos/tests/Utils/InvestmentCalculatorTest.php @@ -0,0 +1,98 @@ +setInvestmentValue($value); + $investment->setCreationDate(new \DateTime($date)); + + return $investment; + } + + public function testCalculateInvestmentValid() { + + $investment = $this->createInvestment(1000, '2024-01-01'); + $result = InvestmentCalculator::calculateInvestment($investment); + + // valor esperado apos x meses + $months = InvestmentCalculator::calculateMonthsBetween(new \DateTime('2024-01-01'), new \DateTime()); + $expected = 1000 * pow(1.0052, $months); + $expected = round($expected, 2); + + $this->assertEquals($expected, $result); + } + + public function testCalculateInvestmentThrowExceptionOnNullDate() { + + $this->expectException(\InvalidArgumentException::class); + + $investment = $this->createInvestment(1000); + + // força o null com reflection + $reflection = new \ReflectionClass($investment); + $property = $reflection->getProperty('creationDate'); + $property->setAccessible(true); + $property->setValue($investment, null); + + InvestmentCalculator::calculateInvestment($investment); + + } + + public function testCalculateInvestmentThrowsExceptionOnInvalidDateFormat() { + + $this->expectException(\InvalidArgumentException::class); + + $investment = $this->createInvestment(1000); + + // aqui tbm + $reflection = new \ReflectionClass($investment); + $property = $reflection->getProperty('creationDate'); + $property->setAccessible(true); + $property->setValue($investment, null); + + InvestmentCalculator::calculateInvestment($investment); + + } + + public function testCalculateMonthsBetween() { + + $start = new \DateTime('2023-01-01'); + $end = new \DateTime('2023-06-01'); + + $months = InvestmentCalculator::calculateMonthsBetween($start, $end); + + $this->assertEquals(5, $months); // 5 meses + } + + public function testProjectFutureBalancesDefaultYears() { + + $investment = $this->createInvestment(1000, '2024-01-01'); + $result = InvestmentCalculator::projectFutureBalances($investment); + + $this->assertIsArray($result); + $this->assertCount(4, $result); // 0, 1, 2, 3 anos + + $this->assertArrayHasKey('year', $result[0]); + $this->assertArrayHasKey('date', $result[0]); + $this->assertArrayHasKey('expectedBalance', $result[0]); + } + + public function testProjectFutureBalancesCustomYears() { + + $investment = $this->createInvestment(1000, '2024-01-01'); + $result = InvestmentCalculator::projectFutureBalances($investment, 5); + + $this->assertCount(6, $result); // 0 até 5 anos + $this->assertEquals(5, $result[5]['year']); + } +} From 06f372f111c9642793c21d6dd0c692845d44da34 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Sun, 7 Sep 2025 15:38:30 -0300 Subject: [PATCH 19/25] Bug fix in DateTime type --- api-investimentos/src/Entity/Investment.php | 13 +++++-------- .../tests/Utils/InvestmentCalculatorTest.php | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/api-investimentos/src/Entity/Investment.php b/api-investimentos/src/Entity/Investment.php index ab5705d47..9d41e2326 100644 --- a/api-investimentos/src/Entity/Investment.php +++ b/api-investimentos/src/Entity/Investment.php @@ -17,8 +17,8 @@ class Investment #[ORM\Column(type: 'float', nullable: true)] private ?float $withdrawnGain = null; - #[ORM\Column] - private $creationDate = null; + #[ORM\Column(type: 'datetime')] + private ?\DateTime $creationDate = null; #[ORM\Column] private ?float $investmentValue = null; @@ -47,18 +47,15 @@ public function setOwner(Owner $owner): static return $this; } - public function getCreationDate() + public function getCreationDate(): ?\DateTime { return $this->creationDate; } - public function setCreationDate($creationDate): void + public function setCreationDate(\DateTime $creationDate): self { - if (!$creationDate instanceof \DateTime) { - throw new \InvalidArgumentException("Creation date must be a DateTime object."); - } - $this->creationDate = $creationDate; + return $this; } public function getInvestmentValue(): ?float diff --git a/api-investimentos/tests/Utils/InvestmentCalculatorTest.php b/api-investimentos/tests/Utils/InvestmentCalculatorTest.php index 4564b36f5..551e3da31 100644 --- a/api-investimentos/tests/Utils/InvestmentCalculatorTest.php +++ b/api-investimentos/tests/Utils/InvestmentCalculatorTest.php @@ -45,7 +45,6 @@ public function testCalculateInvestmentThrowExceptionOnNullDate() { $property->setValue($investment, null); InvestmentCalculator::calculateInvestment($investment); - } public function testCalculateInvestmentThrowsExceptionOnInvalidDateFormat() { From 6a6197520f0d1e1d30c7ee6cf5c8c6a1cb5640f4 Mon Sep 17 00:00:00 2001 From: Eduardo Granemann <167165542+DevGranemann@users.noreply.github.com> Date: Sun, 7 Sep 2025 20:41:33 -0300 Subject: [PATCH 20/25] Update README.md --- README.md | 252 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 166 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 664da6305..4d1ae53ad 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,168 @@ -# Back End Test Project +# API de Investimentos +API desenvolvida para agerenciar investimentos. + +## Estrutura do Repositório + +``` +src/ +│ +├── Migrations/ +│ ├── VersionXXXXX.php -> Migrações que foram criadas e executadas +├── Controller/ +│ ├── InvestmentController.php -> Controlador do Investment +│ └── OwnerController.php -> Controlador do Owner +├── Entity/ +│ └── Investment.php -> Objeto que representa Investment no BD +│ └── Owner.php -> Objeto que representa Owner no BD +├── Utils/ +│ └── InvestmentCalculator.php -> Possui os calculos do investimento +│ └── TakeInvestmentOut.php -> Possui a lógica para saque dos investimentos +├── Tests/ +│ ├── Utils/ +│ └── InvestmentCalculatorTest.php -> Documento onde está os testes dos calculos do investimento "InvestmentCalculator.php" +``` + + + + +## Funcionalidades +- **Cadastro de Proprietários:** funcionalidade que permite cadastrar um usuário que será, futuramente, proprietário de investimentos; + +- **Cadastro de Investimentos:** esta funcionalidade podemos criar um novo investimento para um determinado proprietário já cadastrado; + +- **Listagem de Investimentos:** lista todos os investimentos feitos por um usuário e também o saldo atual de cada investimento. Possui paginação; + +- **Saque:** esta funcionalidade permite o usuário retirar apenas o valor total de seu investimento, ou seja, o valor que investiu mais o lucro obtido. Lembrando que as taxas de impostos são aplicadas apenas no valor de lucros que aquele investimento gerou; + +- **Listagem de Ganhos Retirados:** aqui obtemos uma lista de todos os investimentos que foram sacados pelo proprietário. Esta lista contém informações as informações de valor que aquele investimento gerou e a data do saque, por exemplo; + +- **Projeção de Saldos Futuros:** com essa funcionalidades podemos observar melhor e estudar as projeções dos investimentos. É possível ``setar`` um valor em anos para melhor simulação. + +## Decisão das Tecnologias + +- **Symfony**: decidi utilizar o Symfony pela sua robustez, pela organização por ser MVC e também pela sua modularidade; +- **MySQL**: pela praticidade e rapidez: por linha de comando tem acesso total ao banco, comandos simples. Portabilidade: qualquer servidor com MySQL terá o cliente CLI disponível — garante que você consegue administrar em qualquer ambiente; +- **Abstração do SQL**: em vez de escrever queries manualmente, podemos trabalhar com objetos PHP e deixa o Doctrine gerar as queries. Produtividade: criação automática de tabelas/mapeamento via migrations. Compatibilidade: caso seja necessário trocar MySQL por PostgreSQL, por exemplo, o Doctrine adapta as queries; + +## Exemplos dos Endpoints + +### Owner Create +``` +{ + "message": "Proprietário criado com sucesso.", + "owner": { + "id": 4, + "name": "Maria da Silva" + } +} +``` +### Investment Create +``` +{ + "message": "Investimento criado com sucesso.", + "Investment": { + "id": 26, + "ownerId": 2, + "ownerName": "João da Silva", + "creationDate": "2025-07-03 00:00:00", + "investmentValue": 16000 + } +} +``` +### Invesment List +``` +{ + "page": 1, + "limit": 5, + "total": 25, + "pages": 5, + "investments": [ + { + "id": 1, + "ownerId": 1, + "ownerName": "Eduardo da Silva", + "creationDate": "2025-08-03 00:00:00", + "investmentValue": 0, + "valueWithWinnings": 0, + "winningsValueOnly": 0 + }, + { + "id": 2, + "ownerId": 1, + "ownerName": "Eduardo da Silva", + "creationDate": "2025-07-03 00:00:00", + "investmentValue": 0, + "valueWithWinnings": 0, + "winningsValueOnly": 0 + }, +``` +### Investment Draw +``` +{ + "message": "Saque realizado com sucesso", + "withdrawValue": 127018.2, + "investmentId": 28, + "ownerId": 1, + "ownerName": "Eduardo da Silva" +} +``` +### Withdraw Gains +``` +{ + "withdrawnGains": [ + { + "investmentId": 1, + "withdrawnAt": "2025-09-06 14:33:50", + "profit": null, + "ownerId": 1, + "ownerName": "Eduardo da Silva" + }, + { + "investmentId": 2, + "withdrawnAt": "2025-09-06 18:44:41", + "profit": 20.85, + "ownerId": 1, + "ownerName": "Eduardo da Silva" + }, + { + "investmentId": 3, + "withdrawnAt": "2025-09-06 19:15:12", + "profit": 41.71, + "ownerId": 1, + "ownerName": "Eduardo da Silva" + }, +``` +### Future Balances +``` +{ + "investmentId": 5, + "ownerId": 1, + "ownerName": "Eduardo da Silva", + "years": 5, + "projections": [ + { + "year": 0, + "date": "2025-07-03", + "expectedBalance": 6000 + }, + { + "year": 1, + "date": "2026-07-03", + "expectedBalance": 6385.3 + }, + { + "year": 2, + "date": "2027-07-03", + "expectedBalance": 6795.33 + }, +``` + +## Ferramentas e Tecnologias Utilizadas: +- PHP 8.4.12; +- Symfony 5.12.0; +- BD MySQL (por linha de comando); +- Doctrine ORM; +- Insomnia (para testar as rotas); +- NelmioApiDocBundle (Documentação da API); -You should see this challenge as an opportunity to create an application following modern development best practices (given the stack of your choice), but also feel free to use your own architecture preferences (coding standards, code organization, third-party libraries, etc). It’s perfectly fine to use vanilla code or any framework or libraries. -## Scope - -In this challenge you should build an **API** for an application that stores and manages investments, it should have the following features: - -1. __Creation__ of an investment with an owner, a creation date and an amount. - 1. The creation date of an investment can be today or a date in the past. - 2. An investment should not be or become negative. -2. __View__ of an investment with its initial amount and expected balance. - 1. Expected balance should be the sum of the invested amount and the [gains][]. - 2. If an investment was already withdrawn then the balance must reflect the gains of that investment -3. __Withdrawal__ of a investment. - 1. The withdraw will always be the sum of the initial amount and its gains, - partial withdrawn is not supported. - 2. The withdrawal date must be informmed by the user, and it can be a date in the past or today, but can't happen before the investment creation or the future. - 3. [Taxes][taxes] need to be applied to the withdrawals before showing the final value. -4. __List__ of a person's investments - 1. This list should have pagination. - -__NOTE:__ the implementation of an interface will not be evaluated, and if implemented the endpoints still need to be usable/readable for a machine. - -### Gain Calculation - -The investment will pay 0.52% every month in the same day of the investment creation. - -Given that the gain is paid every month, it should be treated as [compound gain][], which means that every new period (month) the amount gained will become part of the investment balance for the next payment. - -### Taxation - -When money is withdrawn, tax is triggered. Taxes apply only to the gain portion of the money withdrawn. For example, if the initial investment was 1000.00, the current balance is 1200.00, then the taxes will be applied to the 200.00. - -The tax percentage changes according to the age of the investment: -* If it is less than one year old, the percentage will be 22.5% (tax = 45.00). -* If it is between one and two years old, the percentage will be 18.5% (tax = 37.00). -* If older than two years, the percentage will be 15% (tax = 30.00). - -## Requirements -1. Create project using any technology of your preference. It’s perfectly OK to use vanilla code or any framework or libraries; -2. Although you can use as many dependencies as you want, you should manage them wisely; -3. It is not necessary to send the notification emails, however, the code required for that would be welcome; -4. The API must be documented in some way. - -## Deliverables -The project source code and dependencies should be made available in GitHub. Here are the steps you should follow: -1. Fork this repository to your GitHub account (create an account if you don't have one, you will need it working with us). -2. Create a "development" branch and commit the code to it. Do not push the code to the main branch. -3. Include a README file that describes: - - Special build instructions, if any - - List of third-party libraries used and short description of why/how they were used - - A link to the API documentation. -4. Once the work is complete, create a pull request from "development" into "main" and send us the link. -5. Avoid using huge commits hiding your progress. Feel free to work on a branch and use `git rebase` to adjust your commits before submitting the final version. - -## Coding Standards -When working on the project be as clean and consistent as possible. - -## Project Deadline -Ideally you'd finish the test project in 5 days. It shouldn't take you longer than a entire week. - -## Quality Assurance -Use the following checklist to ensure high quality of the project. - -### General -- First of all, the application should run without errors. -- Are all requirements set above met? -- Is coding style consistent? -- The API is well documented? -- The API has unit tests? - -## Submission -1. A link to the Github repository. -2. Briefly describe how you decided on the tools that you used. - -## Have Fun Coding 🤘 -- This challenge description is intentionally vague in some aspects, but if you need assistance feel free to ask for help. -- If any of the seems out of your current level, you may skip it, but remember to tell us about it in the pull request. - -## Credits - -This coding challenge was inspired on [kinvoapp/kinvo-back-end-test](https://github.com/kinvoapp/kinvo-back-end-test/blob/2f17d713de739e309d17a1a74a82c3fd0e66d128/README.md) - -[gains]: #gain-calculation -[taxes]: #taxation -[interest]: #interest-calculation -[compound gain]: https://www.investopedia.com/terms/g/gain.asp From 98ea9c3e6cf039aa8fbff62675bc116ab5f9941d Mon Sep 17 00:00:00 2001 From: Eduardo Granemann <167165542+DevGranemann@users.noreply.github.com> Date: Sun, 7 Sep 2025 22:57:09 -0300 Subject: [PATCH 21/25] Update README.md --- README.md | 119 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4d1ae53ad..44c7e5d8d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ # API de Investimentos API desenvolvida para agerenciar investimentos. +## Funcionalidades +- **Cadastro de Proprietários:** funcionalidade que permite cadastrar um usuário que será, futuramente, proprietário de investimentos; + +- **Cadastro de Investimentos:** esta funcionalidade podemos criar um novo investimento para um determinado proprietário já cadastrado; + +- **Listagem de Investimentos:** lista todos os investimentos feitos por um usuário e também o saldo atual de cada investimento. Possui paginação; + +- **Saque:** esta funcionalidade permite o usuário retirar apenas o valor total de seu investimento, ou seja, o valor que investiu mais o lucro obtido. Lembrando que as taxas de impostos são aplicadas apenas no valor de lucros que aquele investimento gerou; + +- **Listagem de Ganhos Retirados:** aqui obtemos uma lista de todos os investimentos que foram sacados pelo proprietário. Esta lista contém informações as informações de valor que aquele investimento gerou e a data do saque, por exemplo; + +- **Projeção de Saldos Futuros:** com essa funcionalidades podemos observar melhor e estudar as projeções dos investimentos. É possível ``setar`` um valor em anos para melhor simulação. + ## Estrutura do Repositório ``` @@ -22,30 +35,8 @@ src/ │ └── InvestmentCalculatorTest.php -> Documento onde está os testes dos calculos do investimento "InvestmentCalculator.php" ``` - - - -## Funcionalidades -- **Cadastro de Proprietários:** funcionalidade que permite cadastrar um usuário que será, futuramente, proprietário de investimentos; - -- **Cadastro de Investimentos:** esta funcionalidade podemos criar um novo investimento para um determinado proprietário já cadastrado; - -- **Listagem de Investimentos:** lista todos os investimentos feitos por um usuário e também o saldo atual de cada investimento. Possui paginação; - -- **Saque:** esta funcionalidade permite o usuário retirar apenas o valor total de seu investimento, ou seja, o valor que investiu mais o lucro obtido. Lembrando que as taxas de impostos são aplicadas apenas no valor de lucros que aquele investimento gerou; - -- **Listagem de Ganhos Retirados:** aqui obtemos uma lista de todos os investimentos que foram sacados pelo proprietário. Esta lista contém informações as informações de valor que aquele investimento gerou e a data do saque, por exemplo; - -- **Projeção de Saldos Futuros:** com essa funcionalidades podemos observar melhor e estudar as projeções dos investimentos. É possível ``setar`` um valor em anos para melhor simulação. - -## Decisão das Tecnologias - -- **Symfony**: decidi utilizar o Symfony pela sua robustez, pela organização por ser MVC e também pela sua modularidade; -- **MySQL**: pela praticidade e rapidez: por linha de comando tem acesso total ao banco, comandos simples. Portabilidade: qualquer servidor com MySQL terá o cliente CLI disponível — garante que você consegue administrar em qualquer ambiente; -- **Abstração do SQL**: em vez de escrever queries manualmente, podemos trabalhar com objetos PHP e deixa o Doctrine gerar as queries. Produtividade: criação automática de tabelas/mapeamento via migrations. Compatibilidade: caso seja necessário trocar MySQL por PostgreSQL, por exemplo, o Doctrine adapta as queries; - ## Exemplos dos Endpoints - +A documentação está disponivél no ambiente local em http://localhost:8000/api/doc ### Owner Create ``` { @@ -164,5 +155,87 @@ src/ - Doctrine ORM; - Insomnia (para testar as rotas); - NelmioApiDocBundle (Documentação da API); +- PHPUnit (testes unitários); + +## Decisão das Tecnologias + +- **Symfony**: decidi utilizar o Symfony pela sua robustez, pela organização por ser MVC e também pela sua modularidade; +- **MySQL**: pela praticidade e rapidez: por linha de comando tem acesso total ao banco, comandos simples. Portabilidade: qualquer servidor com MySQL terá o cliente CLI disponível — garante que você consegue administrar em qualquer ambiente; +- **Abstração do SQL**: em vez de escrever queries manualmente, podemos trabalhar com objetos PHP e deixa o Doctrine gerar as queries. Produtividade: criação automática de tabelas/mapeamento via migrations. Compatibilidade: caso seja necessário trocar MySQL por PostgreSQL, por exemplo, o Doctrine adapta as queries; + +## Quer Testar Localmente Este Projeto? + +Este guia descreve os passos necessários para rodar a API em um ambiente LOCAL: + +### Pré-requisitos + +Antes de começar, verifique se você possui as seguintes ferramentas instaladas no seu sistema: + +- [PHP 8+](https://www.php.net/downloads.php) +- [Composer](https://getcomposer.org/download/) +- [MySQL](https://dev.mysql.com/downloads/) +- [Symfony CLI](https://symfony.com/download) (opcional, mas recomendado) +- [Insomnia](https://insomnia.rest/download) (para testar as rotas) + + +### Passos para rodar a API localmente + +### 1. Clonar o repositório +```bash +git clone https://github.com/seu-usuario/api-investimentos.git +cd api-investimentos +``` +### 2. Instalar as dependências +```bash + composer install +``` + +### 3. Configurar as variáveis de ambiente +Crie o arquivo .env.local na raiz do projeto e configure a conexão com o banco de dados MySQL: +```bash + DATABASE_URL="mysql://usuario:senha@127.0.0.1:3306/nome_do_banco" +``` + +### 4. Criar o BD e rodar as migrations + +```bash + php bin/console doctrine:database:create + php bin/console doctrine:migrations:migrate +``` + +### 5. Rodar o servidor local +Com Symfony CLI: +```bash +symfony server:start +``` +Ou +```bash +symfony serve +``` +### 6. Testar a API com o Insomnia +Abra o Insomnia e configure as rotas da API. Exemplo +```bash +GET http://127.0.0.1:8000/api/investments +``` +## Cobertura dos Testes Unitários + +A API conta com uma suíte de testes unitários implementada com **PHPUnit**, garantindo a confiabilidade dos cálculos e projeções de investimento. +Os testes cobrem os seguintes cenários: + +### InvestmentCalculatorTest +- **Cálculo de investimento válido** + - Verifica se o saldo final do investimento é calculado corretamente com base nos meses decorridos e na taxa de rendimento. + +- **Tratamento de exceções** + - Lança exceção caso a data de criação do investimento seja `null`. + - Lança exceção caso a data de criação esteja em formato inválido. + +- **Cálculo de meses entre datas** + - Valida o cálculo da diferença em meses entre duas datas distintas. + +- **Projeção de saldos futuros** + - Geração da projeção de crescimento do investimento para os próximos anos (padrão: 3 anos além do ano inicial). + - Valida a estrutura do array retornado (`year`, `date`, `expectedBalance`). + - Suporta número customizado de anos, verificando se a projeção é calculada corretamente até o ano especificado. From 8caa535ae540ecd029cc5e34ebe37896c0cda4c1 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Tue, 9 Sep 2025 02:56:55 -0300 Subject: [PATCH 22/25] API key stored in .env and download securiry bundle of composer --- api-investimentos/composer.json | 1 + api-investimentos/composer.lock | 643 +++++++++++++++++- api-investimentos/config/bundles.php | 1 + .../config/packages/security.yaml | 40 ++ api-investimentos/config/routes/security.yaml | 3 + .../src/Security/ApiKeyAuthenticator.php | 44 ++ api-investimentos/symfony.lock | 13 + 7 files changed, 742 insertions(+), 3 deletions(-) create mode 100644 api-investimentos/config/packages/security.yaml create mode 100644 api-investimentos/config/routes/security.yaml create mode 100644 api-investimentos/src/Security/ApiKeyAuthenticator.php diff --git a/api-investimentos/composer.json b/api-investimentos/composer.json index 84d034392..2097651c9 100644 --- a/api-investimentos/composer.json +++ b/api-investimentos/composer.json @@ -22,6 +22,7 @@ "symfony/http-foundation": "7.3.*", "symfony/routing": "7.3.*", "symfony/runtime": "7.3.*", + "symfony/security-bundle": "7.3.*", "symfony/twig-bundle": "7.3.*", "symfony/yaml": "7.3.*", "twig/extra-bundle": "^2.12|^3.0", diff --git a/api-investimentos/composer.lock b/api-investimentos/composer.lock index c04b0f9bc..759eedcb5 100644 --- a/api-investimentos/composer.lock +++ b/api-investimentos/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": "3bc8fc5fc37bd95f68f81ba77040aff4", + "content-hash": "62a4f7a43cf4b7de1d58d0800a6e8e1e", "packages": [ { "name": "doctrine/collections", @@ -1582,6 +1582,54 @@ }, "time": "2021-02-03T23:26:27+00:00" }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -1982,6 +2030,80 @@ ], "time": "2025-03-13T15:25:07+00:00" }, + { + "name": "symfony/clock", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "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": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/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": "2024-09-25T14:21:43+00:00" + }, { "name": "symfony/config", "version": "v7.3.2", @@ -3382,6 +3504,78 @@ ], "time": "2025-08-05T10:16:07+00:00" }, + { + "name": "symfony/password-hasher", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/password-hasher.git", + "reference": "31fbe66af859582a20b803f38be96be8accdf2c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/31fbe66af859582a20b803f38be96be8accdf2c3", + "reference": "31fbe66af859582a20b803f38be96be8accdf2c3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/security-core": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PasswordHasher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides password hashing utilities", + "homepage": "https://symfony.com", + "keywords": [ + "hashing", + "password" + ], + "support": { + "source": "https://github.com/symfony/password-hasher/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-04T08:22:58+00:00" + }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.33.0", @@ -3794,6 +3988,86 @@ ], "time": "2025-06-24T13:30:11+00:00" }, + { + "name": "symfony/property-access", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", + "reference": "4a4389e5c8bd1d0320d80a23caa6a1ac71cb81a7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/property-info": "^6.4|^7.0" + }, + "require-dev": { + "symfony/cache": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "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 functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v7.3.3" + }, + "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-08-04T15:15:28+00:00" + }, { "name": "symfony/property-info", "version": "v7.3.1", @@ -4044,6 +4318,369 @@ ], "time": "2025-06-13T07:48:40+00:00" }, + { + "name": "symfony/security-bundle", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-bundle.git", + "reference": "fbecca9a10af8d886e116f74e860e19b7583689c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/fbecca9a10af8d886e116f74e860e19b7583689c", + "reference": "fbecca9a10af8d886e116f74e860e19b7583689c", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "ext-xml": "*", + "php": ">=8.2", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^7.3", + "symfony/dependency-injection": "^6.4.11|^7.1.4", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/password-hasher": "^6.4|^7.0", + "symfony/security-core": "^7.3", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^7.3", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/console": "<6.4", + "symfony/framework-bundle": "<6.4", + "symfony/http-client": "<6.4", + "symfony/ldap": "<6.4", + "symfony/serializer": "<6.4", + "symfony/twig-bundle": "<6.4", + "symfony/validator": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/browser-kit": "^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/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "symfony/twig-bundle": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/twig": "^3.12", + "web-token/jwt-library": "^3.3.2|^4.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\SecurityBundle\\": "" + }, + "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 of the Security component into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-bundle/tree/v7.3.3" + }, + "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-08-06T08:34:58+00:00" + }, + { + "name": "symfony/security-core", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-core.git", + "reference": "4465a3b9cefbaebaeeeb98c2becfdb4b59d22488" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-core/zipball/4465a3b9cefbaebaeeeb98c2becfdb4b59d22488", + "reference": "4465a3b9cefbaebaeeeb98c2becfdb4b59d22488", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/password-hasher": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/ldap": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/validator": "<6.4" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "psr/container": "^1.1|^2.0", + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", + "symfony/validator": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Core\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - Core Library", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-core/tree/v7.3.3" + }, + "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-08-25T06:35:40+00:00" + }, + { + "name": "symfony/security-csrf", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-csrf.git", + "reference": "2b4b0c46c901729e4e90719eacd980381f53e0a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/2b4b0c46c901729e4e90719eacd980381f53e0a3", + "reference": "2b4b0c46c901729e4e90719eacd980381f53e0a3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/security-core": "^6.4|^7.0" + }, + "conflict": { + "symfony/http-foundation": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Csrf\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - CSRF Library", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-csrf/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-01-02T18:42:10+00:00" + }, + { + "name": "symfony/security-http", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-http.git", + "reference": "1bf0dc10f27d4776c47f18f98236c619793a9260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-http/zipball/1bf0dc10f27d4776c47f18f98236c619793a9260", + "reference": "1bf0dc10f27d4776c47f18f98236c619793a9260", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/security-core": "^7.3", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/clock": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-client-contracts": "<3.0", + "symfony/security-bundle": "<6.4", + "symfony/security-csrf": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/http-client-contracts": "^3.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "web-token/jwt-library": "^3.3.2|^4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Http\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - HTTP Integration", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-http/tree/v7.3.3" + }, + "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-08-25T06:35:40+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.6.0", @@ -6940,7 +7577,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -6948,6 +7585,6 @@ "ext-ctype": "*", "ext-iconv": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/api-investimentos/config/bundles.php b/api-investimentos/config/bundles.php index 008630c59..f84b3f30b 100644 --- a/api-investimentos/config/bundles.php +++ b/api-investimentos/config/bundles.php @@ -8,4 +8,5 @@ Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true], Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], + Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], ]; diff --git a/api-investimentos/config/packages/security.yaml b/api-investimentos/config/packages/security.yaml new file mode 100644 index 000000000..fd2a05456 --- /dev/null +++ b/api-investimentos/config/packages/security.yaml @@ -0,0 +1,40 @@ +security: + # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords + password_hashers: + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' + # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider + providers: + users_in_memory: { memory: null } + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + main: + lazy: true + provider: users_in_memory + custom_authenticator: App\Security\ApiKeyAuthenticator + + # activate different ways to authenticate + # https://symfony.com/doc/current/security.html#the-firewall + + # https://symfony.com/doc/current/security/impersonating_user.html + # switch_user: true + + # Easy way to control access for large sections of your site + # Note: Only the *first* access control that matches will be used + access_control: + # - { path: ^/admin, roles: ROLE_ADMIN } + # - { path: ^/profile, roles: ROLE_USER } + +when@test: + security: + password_hashers: + # By default, password hashers are resource intensive and take time. This is + # important to generate secure password hashes. In tests however, secure hashes + # are not important, waste resources and increase test times. The following + # reduces the work factor to the lowest possible values. + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: + algorithm: auto + cost: 4 # Lowest possible value for bcrypt + time_cost: 3 # Lowest possible value for argon + memory_cost: 10 # Lowest possible value for argon diff --git a/api-investimentos/config/routes/security.yaml b/api-investimentos/config/routes/security.yaml new file mode 100644 index 000000000..f853be15c --- /dev/null +++ b/api-investimentos/config/routes/security.yaml @@ -0,0 +1,3 @@ +_security_logout: + resource: security.route_loader.logout + type: service diff --git a/api-investimentos/src/Security/ApiKeyAuthenticator.php b/api-investimentos/src/Security/ApiKeyAuthenticator.php new file mode 100644 index 000000000..556a315e6 --- /dev/null +++ b/api-investimentos/src/Security/ApiKeyAuthenticator.php @@ -0,0 +1,44 @@ + Date: Tue, 9 Sep 2025 03:37:31 -0300 Subject: [PATCH 23/25] Configured default file for us API --- .../src/Security/ApiKeyAuthenticator.php | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/api-investimentos/src/Security/ApiKeyAuthenticator.php b/api-investimentos/src/Security/ApiKeyAuthenticator.php index 556a315e6..1466ee4b3 100644 --- a/api-investimentos/src/Security/ApiKeyAuthenticator.php +++ b/api-investimentos/src/Security/ApiKeyAuthenticator.php @@ -2,43 +2,52 @@ namespace App\Security; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials; use Symfony\Component\Security\Http\Authenticator\Passport\Passport; class ApiKeyAuthenticator extends AbstractAuthenticator { public function supports(Request $request): ?bool { - // TODO: Implement supports() method. + return $request->headers->has('X-API-KEY'); } public function authenticate(Request $request): Passport { - // TODO: Implement authenticate() method. + $apiKey = $request->headers->get('X-API-KEY'); + + if ($apiKey !== $_ENV['API_KEY']) { + throw new AuthenticationException('Chave da API inválida'); + } + + return new Passport( + new UserBadge('api-user'), + new CustomCredentials( + function ($credentials, $user) { + return $credentials === $_ENV['API_KEY']; + }, + $apiKey + ) + ); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { - // TODO: Implement onAuthenticationSuccess() method. + return null; } public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { - // TODO: Implement onAuthenticationFailure() method. + return new JsonResponse([ + 'error' => 'Acesso negado: chave inválida' + ], 401); } - // public function start(Request $request, ?AuthenticationException $authException = null): Response - // { - // /* - // * If you would like this class to control what happens when an anonymous user accesses a - // * protected page (e.g. redirect to /login), uncomment this method and make this class - // * implement Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface. - // * - // * For more details, see https://symfony.com/doc/current/security/experimental_authenticators.html#configuring-the-authentication-entry-point - // */ - // } } From 3d60393d8662a2bef8b63fecaf558fb9714cbee8 Mon Sep 17 00:00:00 2001 From: DevGranemann Date: Tue, 9 Sep 2025 05:46:06 -0300 Subject: [PATCH 24/25] API key authentication temporarily disabled --- .../config/packages/security.yaml | 39 +++++-------------- api-investimentos/config/services.yaml | 30 +++++++------- .../src/Security/ApiKeyAuthenticator.php | 18 ++++----- 3 files changed, 34 insertions(+), 53 deletions(-) diff --git a/api-investimentos/config/packages/security.yaml b/api-investimentos/config/packages/security.yaml index fd2a05456..13389e4cf 100644 --- a/api-investimentos/config/packages/security.yaml +++ b/api-investimentos/config/packages/security.yaml @@ -1,40 +1,21 @@ security: - # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords password_hashers: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' - # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider + providers: users_in_memory: { memory: null } + firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false - main: - lazy: true - provider: users_in_memory - custom_authenticator: App\Security\ApiKeyAuthenticator - # activate different ways to authenticate - # https://symfony.com/doc/current/security.html#the-firewall - - # https://symfony.com/doc/current/security/impersonating_user.html - # switch_user: true - - # Easy way to control access for large sections of your site - # Note: Only the *first* access control that matches will be used - access_control: - # - { path: ^/admin, roles: ROLE_ADMIN } - # - { path: ^/profile, roles: ROLE_USER } + api: + pattern: ^/api + stateless: true + provider: users_in_memory + # custom_authenticators: + # - App\Security\ApiKeyAuthenticator -when@test: - security: - password_hashers: - # By default, password hashers are resource intensive and take time. This is - # important to generate secure password hashes. In tests however, secure hashes - # are not important, waste resources and increase test times. The following - # reduces the work factor to the lowest possible values. - Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: - algorithm: auto - cost: 4 # Lowest possible value for bcrypt - time_cost: 3 # Lowest possible value for argon - memory_cost: 10 # Lowest possible value for argon + # access_control: + # - { path: ^/api, roles: IS_AUTHENTICATED_FULLY } diff --git a/api-investimentos/config/services.yaml b/api-investimentos/config/services.yaml index 6bbad873a..63d35a717 100644 --- a/api-investimentos/config/services.yaml +++ b/api-investimentos/config/services.yaml @@ -1,20 +1,20 @@ -# This file is the entry point to configure your own services. -# Files in the packages/ subdirectory configure your dependencies. - -# Put parameters here that don't need to change on each machine where the app is deployed -# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration -parameters: - services: - # default configuration for services in *this* file + # Serviço específico para o Authenticator + App\Security\ApiKeyAuthenticator: + arguments: + $apiKey: '%env(API_KEY)%' + + # Configuração padrão _defaults: - autowire: true # Automatically injects dependencies in your services. - autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. + autowire: true + autoconfigure: true + bind: + $projectDir: '%kernel.project_dir%' - # makes classes in src/ available to be used as services - # this creates a service per class whose id is the fully-qualified class name + # Registro automático de classes do src/ App\: resource: '../src/' - - # add more service definitions when explicit configuration is needed - # please note that last definitions always *replace* previous ones + exclude: + - '../src/DependencyInjection/' + - '../src/Entity/' + - '../src/Kernel.php' diff --git a/api-investimentos/src/Security/ApiKeyAuthenticator.php b/api-investimentos/src/Security/ApiKeyAuthenticator.php index 1466ee4b3..d89cad59b 100644 --- a/api-investimentos/src/Security/ApiKeyAuthenticator.php +++ b/api-investimentos/src/Security/ApiKeyAuthenticator.php @@ -14,6 +14,13 @@ class ApiKeyAuthenticator extends AbstractAuthenticator { + private string $apiKey; + + public function __construct(string $apiKey) + { + $this->apiKey = $apiKey; + } + public function supports(Request $request): ?bool { return $request->headers->has('X-API-KEY'); @@ -21,18 +28,12 @@ public function supports(Request $request): ?bool public function authenticate(Request $request): Passport { - $apiKey = $request->headers->get('X-API-KEY'); - - if ($apiKey !== $_ENV['API_KEY']) { - throw new AuthenticationException('Chave da API inválida'); - } + $apiKey = trim($request->headers->get('X-API-KEY')); return new Passport( new UserBadge('api-user'), new CustomCredentials( - function ($credentials, $user) { - return $credentials === $_ENV['API_KEY']; - }, + fn($credentials, $user) => $credentials === $this->apiKey, $apiKey ) ); @@ -49,5 +50,4 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio 'error' => 'Acesso negado: chave inválida' ], 401); } - } From 30cbd29ecfbf6e76de476af889bb5ee354de93be Mon Sep 17 00:00:00 2001 From: Eduardo Granemann <167165542+DevGranemann@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:36:20 -0300 Subject: [PATCH 25/25] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adicionado descrição sobre autenticação. --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 44c7e5d8d..cf35cecb7 100644 --- a/README.md +++ b/README.md @@ -238,4 +238,20 @@ Os testes cobrem os seguintes cenários: - Valida a estrutura do array retornado (`year`, `date`, `expectedBalance`). - Suporta número customizado de anos, verificando se a projeção é calculada corretamente até o ano especificado. +## Autenticação +A API utiliza **API Key Authenticator** para proteger as rotas + +- O cliente deve enviar a chave de API em cada requisição, através do header HTTP: + +```http +GET/ api/ +X_API_KEY: senha +``` + +- Caso a chave seja inválido ou ausente, a API retornará: + ```bash + { + "error": "Chave inválida" + } + ```