From dbbc1a723231b6bd740c46fa29c9c197f3e5dc5f Mon Sep 17 00:00:00 2001 From: InnobyteNo8651 Date: Sun, 7 Jun 2026 11:25:44 +0200 Subject: [PATCH 1/6] feat(migration): add date column and fix currency to varchar(3) in invoices table --- migrations/Version20260607091233.php | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 migrations/Version20260607091233.php diff --git a/migrations/Version20260607091233.php b/migrations/Version20260607091233.php new file mode 100644 index 0000000..329a781 --- /dev/null +++ b/migrations/Version20260607091233.php @@ -0,0 +1,30 @@ +addSql('ALTER TABLE invoice ALTER COLUMN currency TYPE VARCHAR(3)'); + $this->addSql('ALTER TABLE invoice ADD date DATE NOT NULL DEFAULT CURRENT_DATE'); + $this->addSql('ALTER TABLE invoice ALTER COLUMN date DROP DEFAULT'); + $this->addSql("COMMENT ON COLUMN invoice.date IS '(DC2Type:date_immutable)'"); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE invoice DROP COLUMN date'); + $this->addSql('ALTER TABLE invoice ALTER COLUMN currency TYPE VARCHAR(255)'); + } +} From 42d9226205aeefc60734f876f2a8035054681752 Mon Sep 17 00:00:00 2001 From: InnobyteNo8651 Date: Sun, 7 Jun 2026 11:47:53 +0200 Subject: [PATCH 2/6] chore(composer): add symfony/validator and update dependencies --- composer.json | 1 + composer.lock | 1354 ++++++++++++++++++++------------ config/packages/validator.yaml | 13 + symfony.lock | 21 + 4 files changed, 884 insertions(+), 505 deletions(-) create mode 100644 config/packages/validator.yaml diff --git a/composer.json b/composer.json index cdbf320..f2fe6be 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "symfony/flex": "^2", "symfony/framework-bundle": "6.4.*", "symfony/runtime": "6.4.*", + "symfony/validator": "6.4.*", "symfony/yaml": "6.4.*" }, "config": { diff --git a/composer.lock b/composer.lock index 85fb085..0506917 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e0cd7b1cf6a4a219bcb7f9ea6f987d0b", + "content-hash": "477db8e9d98c679e4f6aeb5bb07b536a", "packages": [ { "name": "doctrine/annotations", @@ -80,126 +80,34 @@ "issues": "https://github.com/doctrine/annotations/issues", "source": "https://github.com/doctrine/annotations/tree/2.0.2" }, + "abandoned": true, "time": "2024-09-05T10:17:24+00:00" }, - { - "name": "doctrine/cache", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "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 Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.2.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%2Fcache", - "type": "tidelift" - } - ], - "time": "2022-05-20T20:07:39+00:00" - }, { "name": "doctrine/collections", - "version": "2.2.2", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "d8af7f248c74f195f7347424600fd9e17b57af59" + "reference": "7713da39d8e237f28411d6a616a3dce5e20d5de2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/d8af7f248c74f195f7347424600fd9e17b57af59", - "reference": "d8af7f248c74f195f7347424600fd9e17b57af59", + "url": "https://api.github.com/repos/doctrine/collections/zipball/7713da39d8e237f28411d6a616a3dce5e20d5de2", + "reference": "7713da39d8e237f28411d6a616a3dce5e20d5de2", "shasum": "" }, "require": { "doctrine/deprecations": "^1", - "php": "^8.1" + "php": "^8.1", + "symfony/polyfill-php84": "^1.30" }, "require-dev": { - "doctrine/coding-standard": "^12", + "doctrine/coding-standard": "^14", "ext-json": "*", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.11" + "phpstan/phpstan": "^2.1.30", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpunit/phpunit": "^10.5.58 || ^11.5.42 || ^12.4" }, "type": "library", "autoload": { @@ -243,7 +151,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.2.2" + "source": "https://github.com/doctrine/collections/tree/2.6.0" }, "funding": [ { @@ -259,42 +167,45 @@ "type": "tidelift" } ], - "time": "2024-04-18T06:56:21+00:00" + "time": "2026-01-15T10:01:58+00:00" }, { "name": "doctrine/dbal", - "version": "3.9.4", + "version": "3.10.5", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "ec16c82f20be1a7224e65ac67144a29199f87959" + "reference": "95d84866bf3c04b2ddca1df7c049714660959aef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/ec16c82f20be1a7224e65ac67144a29199f87959", - "reference": "ec16c82f20be1a7224e65ac67144a29199f87959", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/95d84866bf3c04b2ddca1df7c049714660959aef", + "reference": "95d84866bf3c04b2ddca1df7c049714660959aef", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", "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/coding-standard": "12.0.0", + "doctrine/cache": "^1.11|^2.0", + "doctrine/coding-standard": "14.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "2.1.1", + "phpstan/phpstan": "2.1.30", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "9.6.22", - "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.10.2", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0" + "phpunit/phpunit": "9.6.34", + "slevomat/coding-standard": "8.27.1", + "squizlabs/php_codesniffer": "4.0.1", + "symfony/cache": "^5.4|^6.0|^7.0|^8.0", + "symfony/console": "^4.4|^5.4|^6.0|^7.0|^8.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -354,7 +265,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.9.4" + "source": "https://github.com/doctrine/dbal/tree/3.10.5" }, "funding": [ { @@ -370,30 +281,33 @@ "type": "tidelift" } ], - "time": "2025-01-16T08:28:55+00:00" + "time": "2026-02-24T08:03:57+00:00" }, { "name": "doctrine/deprecations", - "version": "1.1.4", + "version": "1.1.6", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", - "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "1.4.10 || 2.0.3", + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -413,68 +327,69 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" }, - "time": "2024-12-07T21:18:45+00:00" + "time": "2026-02-07T07:09:04+00:00" }, { "name": "doctrine/doctrine-bundle", - "version": "2.13.2", + "version": "2.18.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "2363c43d9815a11657e452625cd64172d5587486" + "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/2363c43d9815a11657e452625cd64172d5587486", - "reference": "2363c43d9815a11657e452625cd64172d5587486", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/0ff098b29b8b3c68307c8987dcaed7fd829c6546", + "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546", "shasum": "" }, "require": { - "doctrine/cache": "^1.11 || ^2.0", "doctrine/dbal": "^3.7.0 || ^4.0", - "doctrine/persistence": "^2.2 || ^3", + "doctrine/deprecations": "^1.0", + "doctrine/persistence": "^3.1 || ^4", "doctrine/sql-formatter": "^1.0.1", - "php": "^7.4 || ^8.0", - "symfony/cache": "^5.4 || ^6.0 || ^7.0", - "symfony/config": "^5.4 || ^6.0 || ^7.0", - "symfony/console": "^5.4 || ^6.0 || ^7.0", - "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", - "symfony/deprecation-contracts": "^2.1 || ^3", - "symfony/doctrine-bridge": "^5.4.46 || ~6.3.12 || ^6.4.3 || ^7.0.3", - "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1.1 || ^2.0 || ^3" + "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/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", - "twig/twig": "<1.34 || >=2.0 <2.4" + "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/coding-standard": "^12", - "doctrine/deprecations": "^1.0", - "doctrine/orm": "^2.17 || ^3.0", + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^14", + "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.5.26", + "phpunit/phpunit": "^10.5.53 || ^12.3.10", "psr/log": "^1.1.4 || ^2.0 || ^3.0", - "symfony/phpunit-bridge": "^6.1 || ^7.0", - "symfony/property-info": "^5.4 || ^6.0 || ^7.0", - "symfony/proxy-manager-bridge": "^5.4 || ^6.0", - "symfony/security-bundle": "^5.4 || ^6.0 || ^7.0", - "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", - "symfony/string": "^5.4 || ^6.0 || ^7.0", - "symfony/twig-bridge": "^5.4 || ^6.0 || ^7.0", - "symfony/validator": "^5.4 || ^6.0 || ^7.0", - "symfony/var-exporter": "^5.4 || ^6.2 || ^7.0", - "symfony/web-profiler-bundle": "^5.4 || ^6.0 || ^7.0", - "symfony/yaml": "^5.4 || ^6.0 || ^7.0", - "twig/twig": "^1.34 || ^2.12 || ^3.0" + "symfony/doctrine-messenger": "^6.4 || ^7.0", + "symfony/expression-language": "^6.4 || ^7.0", + "symfony/messenger": "^6.4 || ^7.0", + "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.14.7 || ^3.0.4" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -519,7 +434,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.13.2" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.2" }, "funding": [ { @@ -535,42 +450,41 @@ "type": "tidelift" } ], - "time": "2025-01-15T11:12:38+00:00" + "time": "2025-12-20T21:35:32+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "3.4.1", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "e858ce0f5c12b266dce7dce24834448355155da7" + "reference": "1e380c6dd8ac8488217f39cff6b77e367f1a644b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/e858ce0f5c12b266dce7dce24834448355155da7", - "reference": "e858ce0f5c12b266dce7dce24834448355155da7", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/1e380c6dd8ac8488217f39cff6b77e367f1a644b", + "reference": "1e380c6dd8ac8488217f39cff6b77e367f1a644b", "shasum": "" }, "require": { - "doctrine/doctrine-bundle": "^2.4", + "doctrine/doctrine-bundle": "^2.4 || ^3.0", "doctrine/migrations": "^3.2", "php": "^7.2 || ^8.0", "symfony/deprecation-contracts": "^2.1 || ^3", - "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { "composer/semver": "^3.0", - "doctrine/coding-standard": "^12", + "doctrine/coding-standard": "^12 || ^14", "doctrine/orm": "^2.6 || ^3", - "doctrine/persistence": "^2.0 || ^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" + "symfony/phpunit-bridge": "^6.3 || ^7 || ^8", + "symfony/var-exporter": "^5.4 || ^6 || ^7 || ^8" }, "type": "symfony-bundle", "autoload": { @@ -605,7 +519,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", - "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.4.1" + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.7.0" }, "funding": [ { @@ -621,20 +535,20 @@ "type": "tidelift" } ], - "time": "2025-01-27T22:48:22+00:00" + "time": "2025-11-15T19:02:59+00:00" }, { "name": "doctrine/event-manager", - "version": "2.0.1", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" + "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/dda33921b198841ca8dbad2eaa5d4d34769d18cf", + "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf", "shasum": "" }, "require": { @@ -644,10 +558,10 @@ "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.24" + "doctrine/coding-standard": "^14", + "phpdocumentor/guides-cli": "^1.4", + "phpstan/phpstan": "^2.1.32", + "phpunit/phpunit": "^10.5.58" }, "type": "library", "autoload": { @@ -696,7 +610,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.1" + "source": "https://github.com/doctrine/event-manager/tree/2.1.1" }, "funding": [ { @@ -712,37 +626,36 @@ "type": "tidelift" } ], - "time": "2024-05-22T20:47:39+00:00" + "time": "2026-01-29T07:11:08+00:00" }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "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\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -787,7 +700,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -803,7 +716,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/instantiator", @@ -954,16 +867,16 @@ }, { "name": "doctrine/migrations", - "version": "3.8.2", + "version": "3.9.7", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "5007eb1168691225ac305fe16856755c20860842" + "reference": "96cb2a89b56c9efb0bac38e606dc0b0f13e650ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/5007eb1168691225ac305fe16856755c20860842", - "reference": "5007eb1168691225ac305fe16856755c20860842", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/96cb2a89b56c9efb0bac38e606dc0b0f13e650ec", + "reference": "96cb2a89b56c9efb0bac38e606dc0b0f13e650ec", "shasum": "" }, "require": { @@ -973,29 +886,29 @@ "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" + "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/var-exporter": "^6.2 || ^7.0 || ^8.0" }, "conflict": { "doctrine/orm": "<2.12 || >=4" }, "require-dev": { - "doctrine/coding-standard": "^12", + "doctrine/coding-standard": "^14", "doctrine/orm": "^2.13 || ^3", - "doctrine/persistence": "^2 || ^3", + "doctrine/persistence": "^2 || ^3 || ^4", "doctrine/sql-formatter": "^1.0", "ext-pdo_sqlite": "*", "fig/log-test": "^1", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpstan/phpstan-strict-rules": "^1.4", - "phpstan/phpstan-symfony": "^1.3", - "phpunit/phpunit": "^10.3", - "symfony/cache": "^5.4 || ^6.0 || ^7.0", - "symfony/process": "^5.4 || ^6.0 || ^7.0", - "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + "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 || ^8.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "suggest": { "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", @@ -1037,7 +950,7 @@ ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/3.8.2" + "source": "https://github.com/doctrine/migrations/tree/3.9.7" }, "funding": [ { @@ -1053,20 +966,20 @@ "type": "tidelift" } ], - "time": "2024-10-10T21:35:27+00:00" + "time": "2026-04-23T19:33:20+00:00" }, { "name": "doctrine/orm", - "version": "3.3.1", + "version": "3.6.7", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "b1f8253105aa5382c495e5f9f8ef34e297775428" + "reference": "bc217c0e19c3a9eadfa67697143b87c9ba01272c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/b1f8253105aa5382c495e5f9f8ef34e297775428", - "reference": "b1f8253105aa5382c495e5f9f8ef34e297775428", + "url": "https://api.github.com/repos/doctrine/orm/zipball/bc217c0e19c3a9eadfa67697143b87c9ba01272c", + "reference": "bc217c0e19c3a9eadfa67697143b87c9ba01272c", "shasum": "" }, "require": { @@ -1082,20 +995,18 @@ "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" + "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^12.0", + "doctrine/coding-standard": "^14.0", "phpbench/phpbench": "^1.0", - "phpdocumentor/guides-cli": "^1.4", "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "2.0.3", + "phpstan/phpstan": "2.1.23", "phpstan/phpstan-deprecation-rules": "^2", - "phpunit/phpunit": "^10.4.0", + "phpunit/phpunit": "^10.5.0 || ^11.5", "psr/log": "^1 || ^2 || ^3", - "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^5.4 || ^6.2 || ^7.0" + "symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", @@ -1141,45 +1052,43 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.3.1" + "source": "https://github.com/doctrine/orm/tree/3.6.7" }, - "time": "2024-12-19T07:08:14+00:00" + "time": "2026-05-25T16:45:47+00:00" }, { "name": "doctrine/persistence", - "version": "3.4.0", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "0ea965320cec355dba75031c1b23d4c78362e3ff" + "reference": "49ab73e0d3e2ac8d1f5ecda3dd8acd5503781e8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/0ea965320cec355dba75031c1b23d4c78362e3ff", - "reference": "0ea965320cec355dba75031c1b23d4c78362e3ff", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/49ab73e0d3e2ac8d1f5ecda3dd8acd5503781e8b", + "reference": "49ab73e0d3e2ac8d1f5ecda3dd8acd5503781e8b", "shasum": "" }, "require": { + "doctrine/deprecations": "^1", "doctrine/event-manager": "^1 || ^2", - "php": "^7.2 || ^8.0", + "php": "^8.1", "psr/cache": "^1.0 || ^2.0 || ^3.0" }, - "conflict": { - "doctrine/common": "<2.10" - }, "require-dev": { - "doctrine/coding-standard": "^12", - "doctrine/common": "^3.0", - "phpstan/phpstan": "1.12.7", - "phpstan/phpstan-phpunit": "^1", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5.38 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0" + "doctrine/coding-standard": "^14", + "phpstan/phpstan": "2.1.30", + "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.58 || ^12", + "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/finder": "^4.4 || ^5.4 || ^6.0 || ^7.0 || ^8.0" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Persistence\\": "src/Persistence" + "Doctrine\\Persistence\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1223,7 +1132,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/3.4.0" + "source": "https://github.com/doctrine/persistence/tree/4.2.0" }, "funding": [ { @@ -1239,30 +1148,30 @@ "type": "tidelift" } ], - "time": "2024-10-30T19:48:12+00:00" + "time": "2026-04-26T12:12:52+00:00" }, { "name": "doctrine/sql-formatter", - "version": "1.5.2", + "version": "1.5.4", "source": { "type": "git", "url": "https://github.com/doctrine/sql-formatter.git", - "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8" + "reference": "9563949f5cd3bd12a17d12fb980528bc141c5806" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/d6d00aba6fd2957fe5216fe2b7673e9985db20c8", - "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/9563949f5cd3bd12a17d12fb980528bc141c5806", + "reference": "9563949f5cd3bd12a17d12fb980528bc141c5806", "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" + "doctrine/coding-standard": "^14", + "ergebnis/phpunit-slow-test-detector": "^2.20", + "phpstan/phpstan": "^2.1.31", + "phpunit/phpunit": "^10.5.58" }, "bin": [ "bin/sql-formatter" @@ -1292,9 +1201,9 @@ ], "support": { "issues": "https://github.com/doctrine/sql-formatter/issues", - "source": "https://github.com/doctrine/sql-formatter/tree/1.5.2" + "source": "https://github.com/doctrine/sql-formatter/tree/1.5.4" }, - "time": "2025-01-24T11:45:48+00:00" + "time": "2026-02-08T16:21:46+00:00" }, { "name": "psr/cache", @@ -1500,16 +1409,16 @@ }, { "name": "symfony/cache", - "version": "v6.4.18", + "version": "v6.4.41", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "b209751ed25f735ea90ca4c9c969d9413a17dfee" + "reference": "5490a577195422c3c9cda09c64823580858af854" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/b209751ed25f735ea90ca4c9c969d9413a17dfee", - "reference": "b209751ed25f735ea90ca4c9c969d9413a17dfee", + "url": "https://api.github.com/repos/symfony/cache/zipball/5490a577195422c3c9cda09c64823580858af854", + "reference": "5490a577195422c3c9cda09c64823580858af854", "shasum": "" }, "require": { @@ -1576,7 +1485,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.18" + "source": "https://github.com/symfony/cache/tree/v6.4.41" }, "funding": [ { @@ -1587,25 +1496,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-22T14:13:52+00:00" + "time": "2026-05-24T08:42:40+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.5.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + "reference": "225e8a254166bd3442e370c6f50145465db63831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/225e8a254166bd3442e370c6f50145465db63831", + "reference": "225e8a254166bd3442e370c6f50145465db63831", "shasum": "" }, "require": { @@ -1619,7 +1532,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -1652,7 +1565,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/cache-contracts/tree/v3.7.0" }, "funding": [ { @@ -1663,25 +1576,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2026-05-05T15:33:14+00:00" }, { "name": "symfony/config", - "version": "v6.4.14", + "version": "v6.4.37", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" + "reference": "ee615e8352db9c5f0b7b149154a3f654dc72042b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", + "url": "https://api.github.com/repos/symfony/config/zipball/ee615e8352db9c5f0b7b149154a3f654dc72042b", + "reference": "ee615e8352db9c5f0b7b149154a3f654dc72042b", "shasum": "" }, "require": { @@ -1727,7 +1644,7 @@ "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/v6.4.14" + "source": "https://github.com/symfony/config/tree/v6.4.37" }, "funding": [ { @@ -1738,25 +1655,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-04T11:33:53+00:00" + "time": "2026-04-29T10:19:30+00:00" }, { "name": "symfony/console", - "version": "v6.4.17", + "version": "v6.4.41", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "799445db3f15768ecc382ac5699e6da0520a0a04" + "reference": "d21b17ed158e79180fac3895ff751707970eeb57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/799445db3f15768ecc382ac5699e6da0520a0a04", - "reference": "799445db3f15768ecc382ac5699e6da0520a0a04", + "url": "https://api.github.com/repos/symfony/console/zipball/d21b17ed158e79180fac3895ff751707970eeb57", + "reference": "d21b17ed158e79180fac3895ff751707970eeb57", "shasum": "" }, "require": { @@ -1821,7 +1742,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.17" + "source": "https://github.com/symfony/console/tree/v6.4.41" }, "funding": [ { @@ -1832,25 +1753,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-07T12:07:30+00:00" + "time": "2026-05-24T08:48:41+00:00" }, { "name": "symfony/dependency-injection", - "version": "v6.4.16", + "version": "v6.4.38", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "7a379d8871f6a36f01559c14e11141cc02eb8dc8" + "reference": "f0990df92ee67721886a2a8b6e19a1bafbf3d7a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/7a379d8871f6a36f01559c14e11141cc02eb8dc8", - "reference": "7a379d8871f6a36f01559c14e11141cc02eb8dc8", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f0990df92ee67721886a2a8b6e19a1bafbf3d7a4", + "reference": "f0990df92ee67721886a2a8b6e19a1bafbf3d7a4", "shasum": "" }, "require": { @@ -1858,7 +1783,7 @@ "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/service-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.2.10|^7.0" + "symfony/var-exporter": "^6.4.20|^7.2.5" }, "conflict": { "ext-psr": "<1.1|>=2", @@ -1902,7 +1827,7 @@ "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/v6.4.16" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.38" }, "funding": [ { @@ -1913,25 +1838,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-25T14:52:46+00:00" + "time": "2026-05-04T13:00:01+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { @@ -1944,7 +1873,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -1969,7 +1898,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" }, "funding": [ { @@ -1980,25 +1909,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v6.4.18", + "version": "v6.4.34", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "fd0094d4648bf6bbdafcd1f0c8aafb6e93d735e6" + "reference": "9e82991eda36e85b640644e1d8d34d89eff498a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/fd0094d4648bf6bbdafcd1f0c8aafb6e93d735e6", - "reference": "fd0094d4648bf6bbdafcd1f0c8aafb6e93d735e6", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/9e82991eda36e85b640644e1d8d34d89eff498a6", + "reference": "9e82991eda36e85b640644e1d8d34d89eff498a6", "shasum": "" }, "require": { @@ -2077,7 +2010,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.18" + "source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.34" }, "funding": [ { @@ -2088,25 +2021,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-25T08:04:58+00:00" + "time": "2026-02-06T08:53:22+00:00" }, { "name": "symfony/dotenv", - "version": "v6.4.16", + "version": "v6.4.39", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "1ac5e7e7e862d4d574258daf08bd569ba926e4a5" + "reference": "e25b00497fe75e318c01a72ea24c0a9c2837cd62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/1ac5e7e7e862d4d574258daf08bd569ba926e4a5", - "reference": "1ac5e7e7e862d4d574258daf08bd569ba926e4a5", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/e25b00497fe75e318c01a72ea24c0a9c2837cd62", + "reference": "e25b00497fe75e318c01a72ea24c0a9c2837cd62", "shasum": "" }, "require": { @@ -2151,7 +2088,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v6.4.16" + "source": "https://github.com/symfony/dotenv/tree/v6.4.39" }, "funding": [ { @@ -2162,25 +2099,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-27T11:08:19+00:00" + "time": "2026-05-06T14:07:03+00:00" }, { "name": "symfony/error-handler", - "version": "v6.4.18", + "version": "v6.4.36", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "e8d3b5b1975e67812a54388b1ba8e9ec28eb770e" + "reference": "2ea68f0e1835ad6a126f93bbc14cd236c10ab361" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/e8d3b5b1975e67812a54388b1ba8e9ec28eb770e", - "reference": "e8d3b5b1975e67812a54388b1ba8e9ec28eb770e", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/2ea68f0e1835ad6a126f93bbc14cd236c10ab361", + "reference": "2ea68f0e1835ad6a126f93bbc14cd236c10ab361", "shasum": "" }, "require": { @@ -2226,7 +2167,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.18" + "source": "https://github.com/symfony/error-handler/tree/v6.4.36" }, "funding": [ { @@ -2237,25 +2178,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-06T09:38:16+00:00" + "time": "2026-03-10T15:56:14+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.13", + "version": "v6.4.37", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" + "reference": "2e3bf817ba9347341ab15926700fb6320367c0e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", - "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2e3bf817ba9347341ab15926700fb6320367c0e1", + "reference": "2e3bf817ba9347341ab15926700fb6320367c0e1", "shasum": "" }, "require": { @@ -2306,7 +2251,7 @@ "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/v6.4.13" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.37" }, "funding": [ { @@ -2317,25 +2262,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2026-04-13T14:11:12+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", "shasum": "" }, "require": { @@ -2349,7 +2298,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -2382,7 +2331,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" }, "funding": [ { @@ -2393,25 +2342,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.13", + "version": "v6.4.39", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" + "reference": "c507b077756b4e3e09adbbe7975fac81cd3722ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", - "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/c507b077756b4e3e09adbbe7975fac81cd3722ca", + "reference": "c507b077756b4e3e09adbbe7975fac81cd3722ca", "shasum": "" }, "require": { @@ -2448,7 +2401,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.13" + "source": "https://github.com/symfony/filesystem/tree/v6.4.39" }, "funding": [ { @@ -2459,25 +2412,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-25T15:07:50+00:00" + "time": "2026-05-07T13:11:42+00:00" }, { "name": "symfony/finder", - "version": "v6.4.17", + "version": "v6.4.34", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" + "reference": "9590e86be1d1c57bfbb16d0dd040345378c20896" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", - "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "url": "https://api.github.com/repos/symfony/finder/zipball/9590e86be1d1c57bfbb16d0dd040345378c20896", + "reference": "9590e86be1d1c57bfbb16d0dd040345378c20896", "shasum": "" }, "require": { @@ -2512,7 +2469,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.17" + "source": "https://github.com/symfony/finder/tree/v6.4.34" }, "funding": [ { @@ -2523,40 +2480,45 @@ "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-29T13:51:37+00:00" + "time": "2026-01-28T15:16:37+00:00" }, { "name": "symfony/flex", - "version": "v2.4.7", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402" + "reference": "4a6d98eea3ebc7f68d82810cb682eedca2649e99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/92f4fba342161ff36072bd3b8e0b3c6c23160402", - "reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402", + "url": "https://api.github.com/repos/symfony/flex/zipball/4a6d98eea3ebc7f68d82810cb682eedca2649e99", + "reference": "4a6d98eea3ebc7f68d82810cb682eedca2649e99", "shasum": "" }, "require": { "composer-plugin-api": "^2.1", - "php": ">=8.0" + "php": ">=8.1" }, "conflict": { - "composer/semver": "<1.7.2" + "composer/semver": "<1.7.2", + "symfony/dotenv": "<5.4" }, "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" + "phpunit/phpunit": "^12.4", + "symfony/dotenv": "^6.4.41|^7.4.13|^8.0.13", + "symfony/filesystem": "^6.4|^7.4|^8.0", + "symfony/process": "^6.4|^7.4|^8.0" }, "type": "composer-plugin", "extra": { @@ -2580,7 +2542,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v2.4.7" + "source": "https://github.com/symfony/flex/tree/v2.11.0" }, "funding": [ { @@ -2591,25 +2553,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-07T08:51:54+00:00" + "time": "2026-05-29T17:25:22+00:00" }, { "name": "symfony/framework-bundle", - "version": "v6.4.18", + "version": "v6.4.41", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "91df8ee37543ebc01756c9e5eaf94d1878ff1ccd" + "reference": "53dd12518c5c8d1f9b92f077fed4f521bccfbc0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/91df8ee37543ebc01756c9e5eaf94d1878ff1ccd", - "reference": "91df8ee37543ebc01756c9e5eaf94d1878ff1ccd", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/53dd12518c5c8d1f9b92f077fed4f521bccfbc0a", + "reference": "53dd12518c5c8d1f9b92f077fed4f521bccfbc0a", "shasum": "" }, "require": { @@ -2645,7 +2611,7 @@ "symfony/lock": "<5.4", "symfony/mailer": "<5.4", "symfony/messenger": "<6.3", - "symfony/mime": "<6.4", + "symfony/mime": "<6.4.37|>=7.0,<7.4.9", "symfony/property-access": "<5.4", "symfony/property-info": "<5.4", "symfony/runtime": "<5.4.45|>=6.0,<6.4.13|>=7.0,<7.1.6", @@ -2682,7 +2648,7 @@ "symfony/lock": "^5.4|^6.0|^7.0", "symfony/mailer": "^5.4|^6.0|^7.0", "symfony/messenger": "^6.3|^7.0", - "symfony/mime": "^6.4|^7.0", + "symfony/mime": "^6.4.37|^7.4.9", "symfony/notifier": "^5.4|^6.0|^7.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/process": "^5.4|^6.0|^7.0", @@ -2729,7 +2695,7 @@ "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/v6.4.18" + "source": "https://github.com/symfony/framework-bundle/tree/v6.4.41" }, "funding": [ { @@ -2740,25 +2706,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-28T18:47:02+00:00" + "time": "2026-05-23T15:52:25+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.18", + "version": "v6.4.41", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "d0492d6217e5ab48f51fca76f64cf8e78919d0db" + "reference": "48d76c29a67a301e0f7779a512bf76417395ffef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0492d6217e5ab48f51fca76f64cf8e78919d0db", - "reference": "d0492d6217e5ab48f51fca76f64cf8e78919d0db", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/48d76c29a67a301e0f7779a512bf76417395ffef", + "reference": "48d76c29a67a301e0f7779a512bf76417395ffef", "shasum": "" }, "require": { @@ -2806,7 +2776,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.18" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.41" }, "funding": [ { @@ -2817,25 +2787,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-09T15:48:56+00:00" + "time": "2026-05-24T10:54:17+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.18", + "version": "v6.4.41", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "fca7197bfe9e99dfae7fb1ad3f7f5bd9ef80e1b7" + "reference": "155f6c1fa29720e8c947591da3be4014951fba6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/fca7197bfe9e99dfae7fb1ad3f7f5bd9ef80e1b7", - "reference": "fca7197bfe9e99dfae7fb1ad3f7f5bd9ef80e1b7", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/155f6c1fa29720e8c947591da3be4014951fba6f", + "reference": "155f6c1fa29720e8c947591da3be4014951fba6f", "shasum": "" }, "require": { @@ -2876,7 +2850,7 @@ "symfony/config": "^6.1|^7.0", "symfony/console": "^5.4|^6.0|^7.0", "symfony/css-selector": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1", "symfony/dom-crawler": "^5.4|^6.0|^7.0", "symfony/expression-language": "^5.4|^6.0|^7.0", "symfony/finder": "^5.4|^6.0|^7.0", @@ -2920,7 +2894,7 @@ "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/v6.4.18" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.41" }, "funding": [ { @@ -2931,25 +2905,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-29T07:25:58+00:00" + "time": "2026-05-27T08:22:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", "shasum": "" }, "require": { @@ -2998,7 +2976,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.38.1" }, "funding": [ { @@ -3009,25 +2987,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-26T05:58:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { @@ -3079,7 +3061,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -3090,28 +3072,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -3159,7 +3146,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" }, "funding": [ { @@ -3170,25 +3157,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.31.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + "reference": "8339098cae28673c15cce00d80734af0453054e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2", + "reference": "8339098cae28673c15cce00d80734af0453054e2", "shasum": "" }, "require": { @@ -3235,7 +3226,87 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:51:13+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "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.38.1" }, "funding": [ { @@ -3246,25 +3317,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { "name": "symfony/routing", - "version": "v6.4.18", + "version": "v6.4.41", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "e9bfc94953019089acdfb9be51c1b9142c4afa68" + "reference": "af04c79671fd8df0805a44c83fa2b0ba56c8329e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/e9bfc94953019089acdfb9be51c1b9142c4afa68", - "reference": "e9bfc94953019089acdfb9be51c1b9142c4afa68", + "url": "https://api.github.com/repos/symfony/routing/zipball/af04c79671fd8df0805a44c83fa2b0ba56c8329e", + "reference": "af04c79671fd8df0805a44c83fa2b0ba56c8329e", "shasum": "" }, "require": { @@ -3318,7 +3393,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.18" + "source": "https://github.com/symfony/routing/tree/v6.4.41" }, "funding": [ { @@ -3329,25 +3404,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-09T08:51:02+00:00" + "time": "2026-05-24T11:18:16+00:00" }, { "name": "symfony/runtime", - "version": "v6.4.14", + "version": "v6.4.41", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "4facd4174f45cd37c65860403412b67c7381136a" + "reference": "4a354e15532ed4f99c6d0fdc5618bfbd3ea05a8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/4facd4174f45cd37c65860403412b67c7381136a", - "reference": "4facd4174f45cd37c65860403412b67c7381136a", + "url": "https://api.github.com/repos/symfony/runtime/zipball/4a354e15532ed4f99c6d0fdc5618bfbd3ea05a8a", + "reference": "4a354e15532ed4f99c6d0fdc5618bfbd3ea05a8a", "shasum": "" }, "require": { @@ -3397,7 +3476,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v6.4.14" + "source": "https://github.com/symfony/runtime/tree/v6.4.41" }, "funding": [ { @@ -3408,25 +3487,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-05T16:39:55+00:00" + "time": "2026-05-23T14:01:00+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { @@ -3444,7 +3527,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -3480,7 +3563,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" }, "funding": [ { @@ -3491,25 +3574,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "symfony/stopwatch", - "version": "v6.4.13", + "version": "v6.4.24", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "2cae0a6f8d04937d02f6d19806251e2104d54f92" + "reference": "b67e94e06a05d9572c2fa354483b3e13e3cb1898" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/2cae0a6f8d04937d02f6d19806251e2104d54f92", - "reference": "2cae0a6f8d04937d02f6d19806251e2104d54f92", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b67e94e06a05d9572c2fa354483b3e13e3cb1898", + "reference": "b67e94e06a05d9572c2fa354483b3e13e3cb1898", "shasum": "" }, "require": { @@ -3542,7 +3629,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.4.13" + "source": "https://github.com/symfony/stopwatch/tree/v6.4.24" }, "funding": [ { @@ -3553,25 +3640,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-07-10T08:14:14+00:00" }, { "name": "symfony/string", - "version": "v6.4.15", + "version": "v6.4.39", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f" + "reference": "62e3c927de664edadb5bef260987eb047a17a113" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", - "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", + "url": "https://api.github.com/repos/symfony/string/zipball/62e3c927de664edadb5bef260987eb047a17a113", + "reference": "62e3c927de664edadb5bef260987eb047a17a113", "shasum": "" }, "require": { @@ -3585,7 +3676,6 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", "symfony/http-client": "^5.4|^6.0|^7.0", "symfony/intl": "^6.2|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -3628,7 +3718,190 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.15" + "source": "https://github.com/symfony/string/tree/v6.4.39" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-12T11:44:19+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0ab302977a952b42fd51475c4ebac81f8da0a95d", + "reference": "0ab302977a952b42fd51475c4ebac81f8da0a95d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\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.7.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-05T13:30:16+00:00" + }, + { + "name": "symfony/validator", + "version": "v6.4.37", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "72cfcf7925746d9950bbdab1362f6bda3b4046cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/72cfcf7925746d9950bbdab1362f6bda3b4046cf", + "reference": "72cfcf7925746d9950bbdab1362f6bda3b4046cf", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php83": "^1.27", + "symfony/translation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/annotations": "<1.13", + "doctrine/lexer": "<1.1", + "symfony/dependency-injection": "<5.4", + "symfony/expression-language": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/intl": "<5.4", + "symfony/property-info": "<5.4", + "symfony/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.13|^2", + "egulias/email-validator": "^2.1.10|^3|^4", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4.35|~6.3.12|^6.4.3|^7.0.3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Validator\\": "" + }, + "exclude-from-classmap": [ + "/Tests/", + "/Resources/bin/" + ] + }, + "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 validate values", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/validator/tree/v6.4.37" }, "funding": [ { @@ -3639,25 +3912,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-13T13:31:12+00:00" + "time": "2026-04-29T06:33:37+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.18", + "version": "v6.4.36", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "4ad10cf8b020e77ba665305bb7804389884b4837" + "reference": "7c8ad9ce4faf6c8a99948e70ce02b601a0439782" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/4ad10cf8b020e77ba665305bb7804389884b4837", - "reference": "4ad10cf8b020e77ba665305bb7804389884b4837", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7c8ad9ce4faf6c8a99948e70ce02b601a0439782", + "reference": "7c8ad9ce4faf6c8a99948e70ce02b601a0439782", "shasum": "" }, "require": { @@ -3669,7 +3946,6 @@ "symfony/console": "<5.4" }, "require-dev": { - "ext-iconv": "*", "symfony/console": "^5.4|^6.0|^7.0", "symfony/error-handler": "^6.3|^7.0", "symfony/http-kernel": "^5.4|^6.0|^7.0", @@ -3713,7 +3989,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.18" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.36" }, "funding": [ { @@ -3724,25 +4000,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-01-17T11:26:11+00:00" + "time": "2026-03-30T15:36:00+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.13", + "version": "v6.4.37", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "0f605f72a363f8743001038a176eeb2a11223b51" + "reference": "34f6957deffacabd1b1c579a312daa481e3e99ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51", - "reference": "0f605f72a363f8743001038a176eeb2a11223b51", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/34f6957deffacabd1b1c579a312daa481e3e99ca", + "reference": "34f6957deffacabd1b1c579a312daa481e3e99ca", "shasum": "" }, "require": { @@ -3790,7 +4070,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.13" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.37" }, "funding": [ { @@ -3801,25 +4081,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2026-04-14T12:12:40+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.18", + "version": "v6.4.41", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5" + "reference": "e8fdf3408c85806198d5826e604ffc6830d33152" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5", - "reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e8fdf3408c85806198d5826e604ffc6830d33152", + "reference": "e8fdf3408c85806198d5826e604ffc6830d33152", "shasum": "" }, "require": { @@ -3862,7 +4146,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.18" + "source": "https://github.com/symfony/yaml/tree/v6.4.41" }, "funding": [ { @@ -3873,27 +4157,31 @@ "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-01-07T09:44:41+00:00" + "time": "2026-05-25T06:03:23+00:00" } ], "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -3932,7 +4220,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -3940,20 +4228,20 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -3972,7 +4260,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -3996,9 +4284,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", @@ -4439,16 +4727,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.22", + "version": "9.6.34", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" + "reference": "b36f02317466907a230d3aa1d34467041271ef4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a", + "reference": "b36f02317466907a230d3aa1d34467041271ef4a", "shasum": "" }, "require": { @@ -4459,7 +4747,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", @@ -4470,11 +4758,11 @@ "phpunit/php-timer": "^5.0.3", "sebastian/cli-parser": "^1.0.2", "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.8", + "sebastian/comparator": "^4.0.10", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", - "sebastian/global-state": "^5.0.7", + "sebastian/exporter": "^4.0.8", + "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", "sebastian/type": "^3.2.1", @@ -4522,7 +4810,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34" }, "funding": [ { @@ -4533,12 +4821,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2024-12-05T13:48:26+00:00" + "time": "2026-01-27T05:45:00+00:00" }, { "name": "sebastian/cli-parser", @@ -4709,16 +5005,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "4.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", "shasum": "" }, "require": { @@ -4771,15 +5067,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2026-01-24T09:22:56+00:00" }, { "name": "sebastian/complexity", @@ -4969,16 +5277,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", "shasum": "" }, "require": { @@ -5034,28 +5342,40 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-09-24T06:03:27+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.7", + "version": "5.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", "shasum": "" }, "require": { @@ -5098,15 +5418,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2024-03-02T06:35:11+00:00" + "time": "2025-08-10T07:10:35+00:00" }, { "name": "sebastian/lines-of-code", @@ -5279,16 +5611,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", "shasum": "" }, "require": { @@ -5330,15 +5662,27 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2025-08-10T06:57:39+00:00" }, { "name": "sebastian/resource-operations", @@ -5505,16 +5849,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -5543,7 +5887,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -5551,7 +5895,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], @@ -5565,5 +5909,5 @@ "ext-iconv": "*" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/config/packages/validator.yaml b/config/packages/validator.yaml new file mode 100644 index 0000000..0201281 --- /dev/null +++ b/config/packages/validator.yaml @@ -0,0 +1,13 @@ +framework: + validation: + email_validation_mode: html5 + + # Enables validator auto-mapping support. + # For instance, basic validation constraints will be inferred from Doctrine's metadata. + #auto_mapping: + # App\Entity\: [] + +when@test: + framework: + validation: + not_compromised_password: false diff --git a/symfony.lock b/symfony.lock index ec68ba0..e5d49a7 100644 --- a/symfony.lock +++ b/symfony.lock @@ -8,6 +8,15 @@ "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05" } }, + "doctrine/deprecations": { + "version": "1.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "fdd756167454623e21f1d769c5b814b243782a67" + } + }, "doctrine/doctrine-bundle": { "version": "2.13", "recipe": { @@ -105,5 +114,17 @@ "config/packages/routing.yaml", "config/routes.yaml" ] + }, + "symfony/validator": { + "version": "6.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.3", + "ref": "c32cfd98f714894c4f128bb99aa2530c1227603c" + }, + "files": [ + "config/packages/validator.yaml" + ] } } From 556aa348707a3ada857242cf4a3d8ea157f1e91a Mon Sep 17 00:00:00 2001 From: InnobyteNo8651 Date: Sun, 7 Jun 2026 17:35:56 +0200 Subject: [PATCH 3/6] refactor(DDD): improve code quality and project structure --- Makefile | 29 ++++++ config/packages/doctrine.yaml | 6 +- config/services.yaml | 12 ++- ....csv => invoices_20260607120101_cid_1.csv} | 0 ...son => invoices_20260607120201_cid_1.json} | 0 data/invoices_20260607120301_cid_4.json | 20 ++++ data/invoices_20260607120401_cid_2.csv | 3 + migrations/.gitignore | 0 .../ParseInvoiceFileCommand.php | 26 +++++ .../ParseInvoiceFileHandler.php | 98 +++++++++++++++++++ .../ParseInvoiceFile/ParseInvoiceResult.php | 69 +++++++++++++ src/Command/ParseInvoicesCommand.php | 31 ------ src/Domain/Entity/Invoice.php | 78 +++++++++++++++ .../InvoiceExtensionStrategyInterface.php | 13 +++ src/Domain/Parser/InvoiceParserInterface.php | 16 +++ .../Parser/InvoiceParserResolverInterface.php | 12 +++ .../Repository/InvoiceRepositoryInterface.php | 16 +++ src/Entity/.gitignore | 0 src/Entity/Invoice.php | 23 ----- .../Console/ParseInvoicesCommand.php | 93 ++++++++++++++++++ src/Infrastructure/Logger/InvoiceLogger.php | 47 +++++++++ .../Parser/Csv/AbstractCsvInvoiceParser.php | 56 +++++++++++ .../Parser/Csv/Cid1CsvInvoiceParser.php | 33 +++++++ .../Parser/Csv/Cid2CsvInvoiceParser.php | 33 +++++++ .../Parser/Csv/CsvInvoiceParserStrategy.php | 32 ++++++ .../Parser/InvoiceParserResolver.php | 43 ++++++++ .../Parser/Json/AbstractJsonInvoiceParser.php | 56 +++++++++++ .../Parser/Json/Cid1JsonInvoiceParser.php | 23 +++++ .../Parser/Json/Cid4JsonInvoiceParser.php | 23 +++++ .../Parser/Json/JsonInvoiceParserStrategy.php | 32 ++++++ .../Repository/InvoiceRepository.php | 40 ++++++++ src/Repository/.gitignore | 0 src/Repository/InvoiceRepository.php | 18 ---- src/Service/InvoiceParser.php | 68 ------------- 34 files changed, 902 insertions(+), 147 deletions(-) create mode 100644 Makefile rename data/{invoices.csv => invoices_20260607120101_cid_1.csv} (100%) rename data/{invoices.json => invoices_20260607120201_cid_1.json} (100%) create mode 100644 data/invoices_20260607120301_cid_4.json create mode 100644 data/invoices_20260607120401_cid_2.csv delete mode 100644 migrations/.gitignore create mode 100644 src/Application/Command/ParseInvoiceFile/ParseInvoiceFileCommand.php create mode 100644 src/Application/Command/ParseInvoiceFile/ParseInvoiceFileHandler.php create mode 100644 src/Application/Command/ParseInvoiceFile/ParseInvoiceResult.php delete mode 100644 src/Command/ParseInvoicesCommand.php create mode 100644 src/Domain/Entity/Invoice.php create mode 100644 src/Domain/Parser/InvoiceExtensionStrategyInterface.php create mode 100644 src/Domain/Parser/InvoiceParserInterface.php create mode 100644 src/Domain/Parser/InvoiceParserResolverInterface.php create mode 100644 src/Domain/Repository/InvoiceRepositoryInterface.php delete mode 100644 src/Entity/.gitignore delete mode 100644 src/Entity/Invoice.php create mode 100644 src/Infrastructure/Console/ParseInvoicesCommand.php create mode 100644 src/Infrastructure/Logger/InvoiceLogger.php create mode 100644 src/Infrastructure/Parser/Csv/AbstractCsvInvoiceParser.php create mode 100644 src/Infrastructure/Parser/Csv/Cid1CsvInvoiceParser.php create mode 100644 src/Infrastructure/Parser/Csv/Cid2CsvInvoiceParser.php create mode 100644 src/Infrastructure/Parser/Csv/CsvInvoiceParserStrategy.php create mode 100644 src/Infrastructure/Parser/InvoiceParserResolver.php create mode 100644 src/Infrastructure/Parser/Json/AbstractJsonInvoiceParser.php create mode 100644 src/Infrastructure/Parser/Json/Cid1JsonInvoiceParser.php create mode 100644 src/Infrastructure/Parser/Json/Cid4JsonInvoiceParser.php create mode 100644 src/Infrastructure/Parser/Json/JsonInvoiceParserStrategy.php create mode 100644 src/Infrastructure/Repository/InvoiceRepository.php delete mode 100644 src/Repository/.gitignore delete mode 100644 src/Repository/InvoiceRepository.php delete mode 100644 src/Service/InvoiceParser.php diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..810ac53 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +DOCKER_COMPOSE := $(shell command -v docker-compose 2>/dev/null && echo "docker-compose" || echo "docker compose") +APP = $(DOCKER_COMPOSE) exec -t app + +up: + $(DOCKER_COMPOSE) up -d + +start: + $(DOCKER_COMPOSE) up --build -d + +down: + $(DOCKER_COMPOSE) down + +app-bash: + $(DOCKER_COMPOSE) exec -t app bash + +cc: + $(APP) php bin/console cache:clear + +migrate: + $(APP) php bin/console doctrine:migrations:migrate --no-interaction + +parse: + $(APP) bash -c "php bin/console app:parse-invoices data/invoices_*" + +install: + $(APP) composer install + +update: + $(APP) composer update diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 315e82c..859bccf 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -10,7 +10,7 @@ doctrine: naming_strategy: doctrine.orm.naming_strategy.underscore auto_mapping: true mappings: - App\Entity: - dir: '%kernel.project_dir%/src/Entity' - prefix: 'App\Entity' + App\Domain\Entity: + dir: '%kernel.project_dir%/src/Domain/Entity' + prefix: 'App\Domain\Entity' is_bundle: false diff --git a/config/services.yaml b/config/services.yaml index 31ed25d..9f101d6 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -17,9 +17,13 @@ services: resource: '../src/' exclude: - '../src/DependencyInjection/' - - '../src/Entity/' + - '../src/Domain/Entity/' - '../src/Kernel.php' - App\Command\: - resource: '../src/Command' - tags: ['console.command'] + _instanceof: + App\Domain\Parser\InvoiceExtensionStrategyInterface: + tags: ['app.extension_registry'] + App\Infrastructure\Parser\Csv\AbstractCsvInvoiceParser: + tags: ['app.csv_invoice_parser'] + App\Infrastructure\Parser\Json\AbstractJsonInvoiceParser: + tags: ['app.json_invoice_parser'] diff --git a/data/invoices.csv b/data/invoices_20260607120101_cid_1.csv similarity index 100% rename from data/invoices.csv rename to data/invoices_20260607120101_cid_1.csv diff --git a/data/invoices.json b/data/invoices_20260607120201_cid_1.json similarity index 100% rename from data/invoices.json rename to data/invoices_20260607120201_cid_1.json diff --git a/data/invoices_20260607120301_cid_4.json b/data/invoices_20260607120301_cid_4.json new file mode 100644 index 0000000..0d7c70a --- /dev/null +++ b/data/invoices_20260607120301_cid_4.json @@ -0,0 +1,20 @@ +[ + { + "nom_prenom": "Gaël Dupont", + "price": "not-a-number", + "devise": "EUR", + "date": "not-a-date" + }, + { + "nom_prenom": "John Doe", + "price": 999.38, + "devise": "EUR", + "date": "2025-02-06" + }, + { + "nom_prenom": "Raphaël Guérin", + "price": 135.87, + "devise": "USD", + "date": "2025-02-06" + } +] diff --git a/data/invoices_20260607120401_cid_2.csv b/data/invoices_20260607120401_cid_2.csv new file mode 100644 index 0000000..687d294 --- /dev/null +++ b/data/invoices_20260607120401_cid_2.csv @@ -0,0 +1,3 @@ +name,amount,currency,civility,date +John Doe,670.43,EUR,Mr,2025-02-06 +Raphaël Guérin,852.38,EURR,Mr,2025-02-06 \ No newline at end of file diff --git a/migrations/.gitignore b/migrations/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/src/Application/Command/ParseInvoiceFile/ParseInvoiceFileCommand.php b/src/Application/Command/ParseInvoiceFile/ParseInvoiceFileCommand.php new file mode 100644 index 0000000..c5757f7 --- /dev/null +++ b/src/Application/Command/ParseInvoiceFile/ParseInvoiceFileCommand.php @@ -0,0 +1,26 @@ +filePath; + $parser = $this->parserResolver->getParser($file); + + if ($parser === null) { + $message = sprintf('No parser found for file: "%s".', basename($file)); + $result->addError(0, 'parser', $message, true); + $this->logger->error($file, 0, 'parser', $message); + return $result; + } + + try { + $rows = $parser->parse($file); + } catch (Throwable $e) { + $result->addError(0, 'parse', $e->getMessage(), true); + $this->logger->error($file, 0, 'parse', $e->getMessage()); + return $result; + } + + /** @var array */ + $invoiceCache = []; + + foreach ($rows as $index => $row) { + $line = $index + 1; + + if ($row instanceof Throwable) { + $result->addError($line, 'parse', $row->getMessage()); + $result->incrementSkipped(); + $this->logger->warning($file, $line, 'parse', $row->getMessage()); + continue; + } + + $violations = $this->validator->validate($row); + if (count($violations) > 0) { + foreach ($violations as $violation) { + $field = $violation->getPropertyPath(); + $message = (string) $violation->getMessage(); + $result->addError($line, $field, $message); + $this->logger->warning($file, $line, $field, $message); + } + $result->incrementSkipped(); + continue; + } + + try { + $key = $row->getName() . '|' . $row->getDate()->format('Y-m-d'); + $existing = $invoiceCache[$key] ?? $this->invoiceRepository->findByNameAndDate($row->getName(), $row->getDate()); + + if ($existing !== null) { + $existing->updateAmountAndCurrency($row->getAmount(), $row->getCurrency()); + $this->invoiceRepository->update($existing); + $result->incrementUpdated(); + } else { + $this->invoiceRepository->create($row); + $result->incrementInserted(); + $invoiceCache[$key] = $row; + } + } catch (Throwable $e) { + $result->addError($line, 'database', $e->getMessage()); + $result->incrementSkipped(); + $this->logger->error($file, $line, 'database', $e->getMessage()); + } + } + + try { + $this->invoiceRepository->flush(); + } catch (Throwable $e) { + $this->logger->error($file, 0, 'flush', $e->getMessage()); + throw $e; + } + + return $result; + } +} diff --git a/src/Application/Command/ParseInvoiceFile/ParseInvoiceResult.php b/src/Application/Command/ParseInvoiceFile/ParseInvoiceResult.php new file mode 100644 index 0000000..e8812ec --- /dev/null +++ b/src/Application/Command/ParseInvoiceFile/ParseInvoiceResult.php @@ -0,0 +1,69 @@ + */ + private array $errors = []; + + public function incrementInserted(): void + { + $this->inserted++; + } + + public function incrementUpdated(): void + { + $this->updated++; + } + + public function addError(int $line, string $field, string $message, bool $critical = false): void + { + $this->errors[] = ['line' => $line, 'field' => $field, 'message' => $message, 'critical' => $critical]; + } + + public function incrementSkipped(): void + { + $this->skipped++; + } + + public function merge(self $other): void + { + $this->inserted += $other->getInserted(); + $this->updated += $other->getUpdated(); + $this->skipped += $other->getSkipped(); + $this->errors = array_merge($this->errors, $other->getErrors()); + } + + public function hasErrors(): bool + { + return count($this->errors) > 0; + } + + public function getInserted(): int + { + return $this->inserted; + } + + public function getUpdated(): int + { + return $this->updated; + } + + public function getSkipped(): int + { + return $this->skipped; + } + + /** @return array */ + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/src/Command/ParseInvoicesCommand.php b/src/Command/ParseInvoicesCommand.php deleted file mode 100644 index 5dd24eb..0000000 --- a/src/Command/ParseInvoicesCommand.php +++ /dev/null @@ -1,31 +0,0 @@ -parser = $parser; - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $this->parser->parse('data/invoices.json'); - $this->parser->parse('data/invoices.csv'); - return Command::SUCCESS; - } -} diff --git a/src/Domain/Entity/Invoice.php b/src/Domain/Entity/Invoice.php new file mode 100644 index 0000000..d35a098 --- /dev/null +++ b/src/Domain/Entity/Invoice.php @@ -0,0 +1,78 @@ +name = $name; + $this->amount = $amount; + $this->currency = $currency; + $this->date = $date; + } + + public function getId(): int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function getAmount(): float + { + return $this->amount; + } + + public function getCurrency(): string + { + return $this->currency; + } + + public function getDate(): DateTimeImmutable + { + return $this->date; + } + + public function updateAmountAndCurrency(float $amount, string $currency): void + { + $this->amount = $amount; + $this->currency = $currency; + } +} diff --git a/src/Domain/Parser/InvoiceExtensionStrategyInterface.php b/src/Domain/Parser/InvoiceExtensionStrategyInterface.php new file mode 100644 index 0000000..8565e34 --- /dev/null +++ b/src/Domain/Parser/InvoiceExtensionStrategyInterface.php @@ -0,0 +1,13 @@ + */ + public function parse(string $filePath): array; +} diff --git a/src/Domain/Parser/InvoiceParserResolverInterface.php b/src/Domain/Parser/InvoiceParserResolverInterface.php new file mode 100644 index 0000000..7dac738 --- /dev/null +++ b/src/Domain/Parser/InvoiceParserResolverInterface.php @@ -0,0 +1,12 @@ +addArgument( + 'files', + InputArgument::IS_ARRAY | InputArgument::REQUIRED, + 'One or more invoice files to parse (CSV or JSON), named with a client ID: invoices_{datetime}_cid_{id}.{ext}', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $files = $input->getArgument('files'); + $total = new ParseInvoiceResult(); + + foreach ($files as $file) { + $io->section(sprintf('Processing: %s', $file)); + + try { + $result = $this->handler->handle(new ParseInvoiceFileCommand($file)); + } catch (InvalidArgumentException $e) { + $io->error($e->getMessage()); + continue; + } + + $io->text(sprintf( + 'Inserted: %d, Updated: %d, Skipped: %d', + $result->getInserted(), + $result->getUpdated(), + $result->getSkipped(), + )); + + if ($result->hasErrors()) { + foreach ($result->getErrors() as $error) { + $message = sprintf( + '[%s] Line %d | field: %s | %s', + basename($file), + $error['line'], + $error['field'], + $error['message'], + ); + + if ($error['critical']) { + $io->error($message); + } else { + $io->warning($message); + } + } + $io->note('See var/log/invoice.log for details.'); + } + + $total->merge($result); + } + + $io->success(sprintf( + 'Done — inserted: %d, updated: %d, skipped: %d.', + $total->getInserted(), + $total->getUpdated(), + $total->getSkipped(), + )); + + return Command::SUCCESS; + } +} diff --git a/src/Infrastructure/Logger/InvoiceLogger.php b/src/Infrastructure/Logger/InvoiceLogger.php new file mode 100644 index 0000000..c7fa059 --- /dev/null +++ b/src/Infrastructure/Logger/InvoiceLogger.php @@ -0,0 +1,47 @@ +path = $logDir . '/invoice.log'; + } + + public function warning(string $file, int $line, string $field, string $message): void + { + $this->write('WARNING', $file, $line, $field, $message); + } + + public function error(string $file, int $line, string $field, string $message): void + { + $this->write('ERROR', $file, $line, $field, $message); + } + + private function write(string $level, string $file, int $line, string $field, string $message): void + { + file_put_contents( + $this->path, + sprintf( + "[%s] [%-7s] [%s] Line %d | field: %s | %s\n", + (new DateTimeImmutable())->format('Y-m-d H:i:s'), + $level, + basename($file), + $line, + $field, + $message, + ), + FILE_APPEND, + ); + } +} diff --git a/src/Infrastructure/Parser/Csv/AbstractCsvInvoiceParser.php b/src/Infrastructure/Parser/Csv/AbstractCsvInvoiceParser.php new file mode 100644 index 0000000..0ee55de --- /dev/null +++ b/src/Infrastructure/Parser/Csv/AbstractCsvInvoiceParser.php @@ -0,0 +1,56 @@ +hasHeader()) { + array_shift($lines); + } + + $map = array_combine($this->getColumns(), $this->getPositions()); + $result = []; + + foreach ($lines as $line) { + try { + $row = str_getcsv($line, $this->getSeparator()); + $result[] = new Invoice( + name: $row[$map['name']], + amount: (float) $row[$map['amount']], + currency: $row[$map['currency']], + date: new DateTimeImmutable($row[$map['date']]), + ); + } catch (Throwable $e) { + $result[] = $e; + } + } + + return $result; + } +} diff --git a/src/Infrastructure/Parser/Csv/Cid1CsvInvoiceParser.php b/src/Infrastructure/Parser/Csv/Cid1CsvInvoiceParser.php new file mode 100644 index 0000000..fd97900 --- /dev/null +++ b/src/Infrastructure/Parser/Csv/Cid1CsvInvoiceParser.php @@ -0,0 +1,33 @@ + */ + private array $parsers; + + public function __construct( + #[AutowireIterator('app.csv_invoice_parser', defaultIndexMethod: 'getClientId')] + iterable $parsers, + ) { + $this->parsers = iterator_to_array($parsers); + } + + public static function getExtension(): string + { + return 'csv'; + } + + public function findParserForClient(string $clientId): ?InvoiceParserInterface + { + return $this->parsers[$clientId] ?? null; + } +} diff --git a/src/Infrastructure/Parser/InvoiceParserResolver.php b/src/Infrastructure/Parser/InvoiceParserResolver.php new file mode 100644 index 0000000..d8e3897 --- /dev/null +++ b/src/Infrastructure/Parser/InvoiceParserResolver.php @@ -0,0 +1,43 @@ + */ + private array $extensionStrategies; + + public function __construct( + #[AutowireIterator('app.extension_registry', defaultIndexMethod: 'getExtension')] + iterable $extensionStrategies, + ) { + $this->extensionStrategies = iterator_to_array($extensionStrategies); + } + + public function getParser(string $filePath): ?InvoiceParserInterface + { + $extension = pathinfo($filePath, PATHINFO_EXTENSION); + $clientId = $this->extractClientId($filePath); + + if ($clientId === null) { + return null; + } + + return ($this->extensionStrategies[$extension] ?? null)?->findParserForClient($clientId); + } + + private function extractClientId(string $filePath): ?string + { + if (preg_match(InvoiceParserResolverInterface::FILENAME_CLIENT_ID_PATTERN, basename($filePath), $matches)) { + return $matches[1]; + } + return null; + } +} diff --git a/src/Infrastructure/Parser/Json/AbstractJsonInvoiceParser.php b/src/Infrastructure/Parser/Json/AbstractJsonInvoiceParser.php new file mode 100644 index 0000000..593c115 --- /dev/null +++ b/src/Infrastructure/Parser/Json/AbstractJsonInvoiceParser.php @@ -0,0 +1,56 @@ + 'nom', 'amount' => 'montant', 'currency' => 'devise', 'date' => 'date'] + * + * @return array + */ + abstract public function getFieldMap(): array; + + public function parse(string $filePath): array + { + $contents = @file_get_contents($filePath); + + if ($contents === false) { + throw new RuntimeException(sprintf('Cannot read file "%s".', $filePath)); + } + + $data = json_decode( + $contents, + associative: true, + depth: 512, + flags: JSON_THROW_ON_ERROR, + ); + + $map = $this->getFieldMap(); + $result = []; + + foreach ($data as $row) { + try { + $result[] = new Invoice( + name: $row[$map['name']], + amount: (float) $row[$map['amount']], + currency: $row[$map['currency']], + date: new DateTimeImmutable($row[$map['date']]), + ); + } catch (Throwable $e) { + $result[] = $e; + } + } + + return $result; + } +} diff --git a/src/Infrastructure/Parser/Json/Cid1JsonInvoiceParser.php b/src/Infrastructure/Parser/Json/Cid1JsonInvoiceParser.php new file mode 100644 index 0000000..b9213ef --- /dev/null +++ b/src/Infrastructure/Parser/Json/Cid1JsonInvoiceParser.php @@ -0,0 +1,23 @@ + 'nom', + 'amount' => 'montant', + 'currency' => 'devise', + 'date' => 'date', + ]; + } +} diff --git a/src/Infrastructure/Parser/Json/Cid4JsonInvoiceParser.php b/src/Infrastructure/Parser/Json/Cid4JsonInvoiceParser.php new file mode 100644 index 0000000..3f58ddb --- /dev/null +++ b/src/Infrastructure/Parser/Json/Cid4JsonInvoiceParser.php @@ -0,0 +1,23 @@ + 'nom_prenom', + 'amount' => 'price', + 'currency' => 'devise', + 'date' => 'date', + ]; + } +} diff --git a/src/Infrastructure/Parser/Json/JsonInvoiceParserStrategy.php b/src/Infrastructure/Parser/Json/JsonInvoiceParserStrategy.php new file mode 100644 index 0000000..f85af5b --- /dev/null +++ b/src/Infrastructure/Parser/Json/JsonInvoiceParserStrategy.php @@ -0,0 +1,32 @@ + */ + private array $parsers; + + public function __construct( + #[AutowireIterator('app.json_invoice_parser', defaultIndexMethod: 'getClientId')] + iterable $parsers, + ) { + $this->parsers = iterator_to_array($parsers); + } + + public static function getExtension(): string + { + return 'json'; + } + + public function findParserForClient(string $clientId): ?InvoiceParserInterface + { + return $this->parsers[$clientId] ?? null; + } +} diff --git a/src/Infrastructure/Repository/InvoiceRepository.php b/src/Infrastructure/Repository/InvoiceRepository.php new file mode 100644 index 0000000..0eff9c7 --- /dev/null +++ b/src/Infrastructure/Repository/InvoiceRepository.php @@ -0,0 +1,40 @@ +getEntityManager()->persist($invoice); + } + + public function update(Invoice $invoice): void + { + // Entity is already tracked by Doctrine UnitOfWork. + // Changes will be flushed on flush() call. + } + + public function flush(): void + { + $this->getEntityManager()->flush(); + } + + public function findByNameAndDate(string $name, DateTimeImmutable $date): ?Invoice + { + return $this->findOneBy(['name' => $name, 'date' => $date]); + } +} diff --git a/src/Repository/.gitignore b/src/Repository/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/src/Repository/InvoiceRepository.php b/src/Repository/InvoiceRepository.php deleted file mode 100644 index 92d7dc2..0000000 --- a/src/Repository/InvoiceRepository.php +++ /dev/null @@ -1,18 +0,0 @@ -em = $em; - } - - public function parse(string $fp): void - { - if (str_contains($fp, 'json')) { //Pour les json - $f = file_get_contents($fp); - $d = preg_split("/\r\n|\n|\r/", $f); - $c = 0; - $m = ""; - $n = ""; - /** Tant qu'il y a une ligne */ - while(true){ - if(isset($d[$c])){ - if(str_contains($d[$c], "montant")){ - $m = explode(": ", $d[$c])[1]; - $m = substr($m, 0, strlen($m) - 1); - } - if(str_contains($d[$c], "nom")){ - $n = explode(": ", $d[$c])[1]; - $n = substr($n, 0, strlen($n) - 1); - } - if(str_contains($d[$c], "}")){ - $this->em->getConnection()->executeStatement( - "UPDATE invoice SET amount = {$m} WHERE name = '{$n}'" - ); - } - $c++; - }else{ - break; - } - } - } elseif (str_contains($fp, 'csv')) { //Pour les json - - - $d = array_map(function($r) { - return str_getcsv($r, "\t"); - }, file($fp)); - $c = 0; - while(true){ - if(isset($d[$c])){ - $this->em->getConnection()->executeStatement( - "UPDATE invoice SET amount = {$d[$c][0]} WHERE name = '{$d[$c][2]}'" - ); - $c++; - }else{ - break; - } - } - } - } -} From 63b4a0a49c030b403cfa4762e07c8f5f5ae1e5c0 Mon Sep 17 00:00:00 2001 From: InnobyteNo8651 Date: Sun, 7 Jun 2026 17:36:54 +0200 Subject: [PATCH 4/6] chore(TU): add unit tests and make test command --- Makefile | 3 ++ tests/InvoiceParserTest.php | 45 ------------------------ tests/Unit/Domain/Entity/InvoiceTest.php | 44 +++++++++++++++++++++++ 3 files changed, 47 insertions(+), 45 deletions(-) delete mode 100644 tests/InvoiceParserTest.php create mode 100644 tests/Unit/Domain/Entity/InvoiceTest.php diff --git a/Makefile b/Makefile index 810ac53..3115834 100644 --- a/Makefile +++ b/Makefile @@ -27,3 +27,6 @@ install: update: $(APP) composer update + +test: + $(APP) php vendor/bin/phpunit tests/Unit diff --git a/tests/InvoiceParserTest.php b/tests/InvoiceParserTest.php deleted file mode 100644 index 0058eeb..0000000 --- a/tests/InvoiceParserTest.php +++ /dev/null @@ -1,45 +0,0 @@ -entityManager = $this->createMock(EntityManagerInterface::class); - - $connection = $this->createMock(Connection::class); - $this->entityManager->method('getConnection')->willReturn($connection); - - $connection->expects($this->exactly(10))->method('executeStatement'); - - $invoiceParser = new InvoiceParser($this->entityManager); - - $invoiceParser->parse('data/invoices.json'); - } - - public function testParseCsv(): void - { - $this->entityManager = $this->createMock(EntityManagerInterface::class); - - $connection = $this->createMock(Connection::class); - $this->entityManager->method('getConnection')->willReturn($connection); - - $connection->expects($this->exactly(10))->method('executeStatement'); - - $invoiceParser = new InvoiceParser($this->entityManager); - - $invoiceParser->parse('data/invoices.csv'); - } - -} - diff --git a/tests/Unit/Domain/Entity/InvoiceTest.php b/tests/Unit/Domain/Entity/InvoiceTest.php new file mode 100644 index 0000000..9b727f8 --- /dev/null +++ b/tests/Unit/Domain/Entity/InvoiceTest.php @@ -0,0 +1,44 @@ +getName()); + self::assertSame(100.50, $invoice->getAmount()); + self::assertSame('EUR', $invoice->getCurrency()); + self::assertSame($date, $invoice->getDate()); + } + + public function testUpdateAmountAndCurrencyDoesNotChangeName(): void + { + $invoice = new Invoice('John Doe', 100.50, 'EUR', new DateTimeImmutable('2025-02-03')); + + $invoice->updateAmountAndCurrency(200.00, 'USD'); + + self::assertSame(200.00, $invoice->getAmount()); + self::assertSame('USD', $invoice->getCurrency()); + self::assertSame('John Doe', $invoice->getName()); + } + + public function testUpdateAmountAndCurrencyDoesNotChangeDate(): void + { + $date = new DateTimeImmutable('2025-02-03'); + $invoice = new Invoice('John Doe', 100.50, 'EUR', $date); + + $invoice->updateAmountAndCurrency(200.00, 'USD'); + + self::assertSame($date, $invoice->getDate()); + } +} From 646a840254ce728a000926b0110966fc5141ee6b Mon Sep 17 00:00:00 2001 From: InnobyteNo8651 Date: Sun, 7 Jun 2026 17:37:09 +0200 Subject: [PATCH 5/6] docs(README): update README with make commands and setup instructions --- README.md | 385 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 347 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 84a9961..2cc92fa 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,373 @@ - # Invoice Parser Ce projet est une application Symfony qui permet de parser des fichiers de type JSON et CSV contenant des informations sur des factures. Les données extraites sont ensuite mises à jour dans la base de données via des requêtes SQL. -## Prérequis +--- + +## Stack + +- PHP 8.2 + Symfony 6.4 +- Doctrine ORM 3.x + PostgreSQL 15 +- PHPUnit 9.6 +- Docker (PHP-FPM + PostgreSQL) + +--- + +## Architecture (DDD) + +``` +src/ +├── Application/ +│ └── Command/ParseInvoiceFile/ +│ ├── ParseInvoiceFileCommand.php # Commande CQRS — porte le chemin du fichier +│ ├── ParseInvoiceFileHandler.php # Orchestration : parse → valide → upsert +│ └── ParseInvoiceResult.php # Compteurs inserted / updated / skipped + erreurs +│ +├── Domain/ +│ ├── Entity/ +│ │ └── Invoice.php # Entité Doctrine (name, amount, currency, date) +│ ├── Parser/ +│ │ ├── InvoiceParserInterface.php # getClientId() + parse() +│ │ ├── InvoiceExtensionStrategyInterface.php # getExtension() + findParserForClient() +│ │ └── InvoiceParserResolverInterface.php # getParser(filePath) +│ └── Repository/ +│ └── InvoiceRepositoryInterface.php # create / update / flush / findByNameAndDate +│ +└── Infrastructure/ + ├── Console/ + │ └── ParseInvoicesCommand.php # Commande CLI app:parse-invoices + ├── Logger/ + │ └── InvoiceLogger.php # var/log/invoice.log + ├── Parser/ + │ ├── InvoiceParserResolver.php # Routing extension → clientId → parser + │ ├── Csv/ + │ │ ├── AbstractCsvInvoiceParser.php # Logique CSV générique + │ │ ├── CsvInvoiceParserStrategy.php # Registry des parsers CSV par client + │ │ ├── Cid1CsvInvoiceParser.php # Client 1 — TSV, sans header + │ │ └── Cid2CsvInvoiceParser.php # Client 2 — CSV, avec header + │ └── Json/ + │ ├── AbstractJsonInvoiceParser.php # Logique JSON générique + │ ├── JsonInvoiceParserStrategy.php # Registry des parsers JSON par client + │ ├── Cid1JsonInvoiceParser.php # Client 1 — champs FR + │ └── Cid4JsonInvoiceParser.php # Client 4 — champs mixtes + └── Repository/ + └── InvoiceRepository.php # Implémentation Doctrine +``` + +--- + +## Pattern Strategy + Resolver + +`InvoiceParserResolver` analyse le nom de fichier et sélectionne le bon parser en deux niveaux : + +| Niveau | Interface | Clé de sélection | +|--------|-----------|------------------| +| 1 | `InvoiceExtensionStrategyInterface` | extension du fichier (`csv`, `json`…) | +| 2 | `InvoiceParserInterface` | identifiant client dans le nom (`cid_1`, `cid_2`…) | + +``` +invoices_20260607100102_cid_1.csv + └─cid─┘└─ext─┘ + │ + ┌─────────────┘ Niveau 1 + ▼ + CsvInvoiceParserStrategy + │ + cid_1│ Niveau 2 + ▼ + Cid1CsvInvoiceParser +``` + +**Extraction du client ID** — regex `/(cid_\d+)[_.]/` appliquée sur le nom de fichier. + +**Upsert** — clé unique `name + date`. Les lignes existantes sont mises à jour (`amount`, `currency`). Les doublons dans le même fichier sont détectés en mémoire via `$invoiceCache` avant le `flush()`, pour éviter les faux inserts. + +--- + +## Format des fichiers + +``` +invoices_{datetime}_cid_{id}.{ext} +``` + +Exemples : +``` +data/invoices_20260607120101_cid_1.csv +data/invoices_20260607120201_cid_1.json +data/invoices_20260607120301_cid_4.json +data/invoices_20260607120401_cid_2.csv +``` + +--- -- Docker -- Docker Compose +## Commandes Make + +| Commande | Description | +|----------|-------------| +| `make up` | Démarrer les containers | +| `make down` | Arrêter les containers | +| `make app-bash` | Ouvrir un shell dans le container | +| `make cc` | Vider le cache Symfony | +| `make migrate` | Appliquer les migrations | +| `make parse` | Parser tous les fichiers `data/invoices_*` | +| `make install` | Installer les dépendances Composer | +| `make update` | Mettre à jour les dépendances Composer | +| `make test` | Lancer les tests unitaires | + +--- ## Installation -1. Clone le projet dans ton répertoire local. +```bash +# 1. Clone le projet dans ton répertoire local +git clone https://github.com/InnobyteNo8651/invoice-parser.git +cd invoice-parser + +# 2. Aller sur ta branche +git switch refactor/code-quality-improvements + +# 3. Démarrer les containers +make start + +# 4. Installer les dépendances +make install + +# 5. Appliquer toutes les migrations +make migrate +``` + +--- + +## Migrations + +### `Version20250203171333` — Table initiale + +Crée la table `invoice` avec les colonnes de base : + +| Colonne | Type | +|------------|--------------------| +| `id` | INT (séquence) | +| `name` | VARCHAR(255) | +| `amount` | DOUBLE PRECISION | +| `currency` | VARCHAR(255) | + +### `Version20260607091233` — Ajout de `date` + correction `currency` + +- Réduit `currency` de VARCHAR(255) à **VARCHAR(3)** (format ISO 4217 : EUR, USD…) +- Ajoute la colonne **`date DATE NOT NULL`** (mappée en `DateTimeImmutable` via Doctrine) + +> **Note :** si la table contient déjà des données au moment d'appliquer cette migration, +> les lignes existantes reçoivent `CURRENT_DATE` comme valeur par défaut temporaire. +> Il faut vider la table et réimporter les fichiers pour avoir les bonnes dates. + +--- + +## Utilisation + +```bash +# Tous les fichiers du dossier data/ +make parse + +# Un ou plusieurs fichiers spécifiques +make app-bash +php bin/console app:parse-invoices data/invoices_20260607120101_cid_1.csv data/invoices_20260607120201_cid_1.json +``` + +Sortie exemple : +``` +Processing: data/invoices_20260607120101_cid_1.csv + Inserted: 8, Updated: 2, Skipped: 0 + + [OK] Done — inserted: 8, updated: 2, skipped: 0. +``` + +Les erreurs sont affichées en console et loggées dans `var/log/invoice.log`. + +--- + +## Ajouter un nouveau client (extension existante) + +### Client CSV (ex: cid_5) + +Créer `src/Infrastructure/Parser/Csv/Cid5CsvInvoiceParser.php` : + +```php +final class Cid5CsvInvoiceParser extends AbstractCsvInvoiceParser +{ + public static function getClientId(): string { return 'cid_5'; } + public function getSeparator(): string { return ';'; } + public function getColumns(): array { return ['name', 'amount', 'currency', 'date']; } + public function getPositions(): array { return [0, 1, 2, 3]; } + public function hasHeader(): bool { return true; } +} +``` + +Symfony le détecte automatiquement via `_instanceof` dans `services.yaml` — aucune autre configuration nécessaire. + +### Client JSON (ex: cid_6) + +Créer `src/Infrastructure/Parser/Json/Cid6JsonInvoiceParser.php` : + +```php +final class Cid6JsonInvoiceParser extends AbstractJsonInvoiceParser +{ + public static function getClientId(): string { return 'cid_6'; } + + public function getFieldMap(): array + { + return ['name' => 'full_name', 'amount' => 'total', 'currency' => 'cur', 'date' => 'invoice_date']; + } +} +``` + +Même principe — aucune configuration supplémentaire dans `services.yaml`. + +--- + +## Ajouter une nouvelle extension (ex : XML) + +Pour ajouter le support d'un nouveau format de fichier, il faut créer **4 éléments** et ajouter **1 ligne** dans `services.yaml`. + +### Étape 1 — Classe abstraite + +Créer `src/Infrastructure/Parser/Xml/AbstractXmlInvoiceParser.php` : + +```php +namespace App\Infrastructure\Parser\Xml; + +use App\Domain\Entity\Invoice; +use App\Domain\Parser\InvoiceParserInterface; +use DateTimeImmutable; +use Throwable; + +abstract class AbstractXmlInvoiceParser implements InvoiceParserInterface +{ + abstract public function getFieldMap(): array; + + public function parse(string $filePath): array + { + // lire le fichier, instancier SimpleXMLElement, + // itérer les nœuds enfants, retourner Invoice[]|Throwable[] + } +} +``` + +### Étape 2 — Strategy (registry des parsers XML) + +Créer `src/Infrastructure/Parser/Xml/XmlInvoiceParserStrategy.php` : + +```php +namespace App\Infrastructure\Parser\Xml; + +use App\Domain\Parser\InvoiceExtensionStrategyInterface; +use App\Domain\Parser\InvoiceParserInterface; +use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; + +final class XmlInvoiceParserStrategy implements InvoiceExtensionStrategyInterface +{ + /** @var array */ + private array $parsers; + + public function __construct( + #[AutowireIterator('app.xml_invoice_parser', defaultIndexMethod: 'getClientId')] + iterable $parsers, + ) { + $this->parsers = iterator_to_array($parsers); + } + + public static function getExtension(): string { return 'xml'; } + + public function findParserForClient(string $clientId): ?InvoiceParserInterface + { + return $this->parsers[$clientId] ?? null; + } +} +``` + +### Étape 3 — Parser concret pour chaque client XML + +Créer `src/Infrastructure/Parser/Xml/Cid7XmlInvoiceParser.php` : + +```php +final class Cid7XmlInvoiceParser extends AbstractXmlInvoiceParser +{ + public static function getClientId(): string { return 'cid_7'; } + + public function getFieldMap(): array + { + return ['name' => 'ClientName', 'amount' => 'Total', 'currency' => 'Currency', 'date' => 'InvoiceDate']; + } +} +``` + +### Étape 4 — Déclarer le tag dans `services.yaml` - ```bash - git clone https://github.com/ton-repository/invoice-parser.git - cd invoice-parser - ``` +```yaml +_instanceof: + # entrées existantes... + App\Infrastructure\Parser\Xml\AbstractXmlInvoiceParser: + tags: ['app.xml_invoice_parser'] +``` -2. Construis et lance les containers Docker. +`XmlInvoiceParserStrategy` est auto-détecté via `_instanceof: InvoiceExtensionStrategyInterface` (déjà configuré) — aucune autre modification nécessaire. - ```bash - docker-compose up --build -d - ``` +### Vérification - Cela créera les containers pour l'application Symfony et la base de données PostgreSQL. +```bash +make app-bash +php bin/console app:parse-invoices data/invoices_[DATETIME]_cid_7.xml +``` -3. Installe les dépendances PHP via Composer. +--- - ```bash - docker-compose exec app composer install - ``` +## Tests -4. Créer la base de données et exécute les migrations. +```bash +make test +``` - ```bash - docker-compose exec app php bin/console doctrine:migrations:migrate - ``` +``` +OK (3 tests, 8 assertions) +``` -## Lancer l'application +Seule la couche **Domain** est couverte — c'est la priorité : logique métier pure, sans dépendances externes. -### 1. Exécuter la commande de parsing +| Suite | Fichier | Ce qui est testé | +|-------|---------|-----------------| +| `InvoiceTest` | `tests/Unit/Domain/Entity/InvoiceTest.php` | Constructeur, getters, `updateAmountAndCurrency` | -Pour parser les fichiers de factures (`json` ou `csv`), exécute la commande suivante : +--- - ```bash - docker-compose run --rm app php bin/console app:parse - ``` +## Améliorations possibles -Cette commande va charger et parser les fichiers, puis mettre à jour les enregistrements dans la base de données. +### Fonctionnel +- [ ] **Type monétaire** — remplacer `float $amount` par un Value Object `Money` (stockage en centimes `int`) pour éviter les erreurs d'arrondi des flottants +- [ ] **Unicité** — ajouter une contrainte d'unicité en base sur `name + date` pour garantir l'intégrité +- [ ] **Format date** — détecter automatiquement le format de date (FR/EN) et insérer correctement dans la colonne date -### 2. Lancer les tests unitaires +### Fichiers volumineux +- [ ] **Streaming** — lire les fichiers ligne par ligne au lieu de tout charger en mémoire (`SplFileObject` pour CSV, JSONL pour JSON) +- [ ] **Batch flush** — appeler `flush()` + `clear()` toutes les N lignes pour libérer la mémoire Doctrine +- [ ] **Traitement async** — intégrer Symfony Messenger pour traiter les fichiers en arrière-plan -Pour vérifier le bon fonctionnement de l'application, tu peux exécuter les tests unitaires via PHPUnit. Pour cela, utilise la commande suivante : +### Tests +- [ ] **Tests unitaires** — `ParseInvoiceResultTest`, `ParseInvoiceFileHandlerTest`, parsers CSV/JSON +- [ ] **Tests d'intégration** — upsert réel sur base de données, contrainte d'unicité +- [ ] **Tests fonctionnels** — commande CLI end-to-end - ```bash - docker-compose exec app php vendor/bin/phpunit tests/InvoiceParserTest.php - ``` +### Infrastructure +- [ ] **Rotation des logs** — limiter la taille du fichier `invoice.log` +- [ ] **Chemin du log configurable** — extraire en paramètre Symfony +- [ ] **Barre de progression** — afficher une `ProgressBar` Symfony pour les gros fichiers +- [ ] **Rapport d'erreurs** — exporter les lignes en erreur dans un fichier CSV séparé -Pour exécuter tous les tests dans le projet, tu peux utiliser : +### Outillage & CI/CD +- [ ] **Pipeline CI/CD** — GitHub Actions +- [ ] **CS Fixer** — PHP CS Fixer pour enforcer le style de code +- [ ] **Variables d'environnement** — séparer les configs `local` / `dev` / `preprod` / `prod` +- [ ] **Planification** — cron job ou Symfony Scheduler pour automatiser le parsing - ```bash - docker-compose exec app php vendor/bin/phpunit - ``` +### Sécurité +- [ ] **Chiffrement des données sensibles** — chiffrer les champs sensibles en base (`name`…) via DoctrineEncryptBundle ou solution custom +- [ ] **Secrets** — ne jamais committer les credentials (`DATABASE_URL`…) +- [ ] **Permissions fichiers** — restreindre l'accès aux fichiers à parser (dossier dédié, utilisateur dédié) From 12e0ed8d74277e33c9fb0cb9b9b15dc52e641751 Mon Sep 17 00:00:00 2001 From: InnobyteNo8651 Date: Mon, 8 Jun 2026 22:00:03 +0200 Subject: [PATCH 6/6] refactor(parser): replace two-level routing (Resolver -> Strategy) with single strategy --- README.md | 178 +++++++----------- config/services.yaml | 8 +- .../ParseInvoiceFileHandler.php | 6 +- .../InvoiceExtensionStrategyInterface.php | 13 -- src/Domain/Parser/InvoiceParserInterface.php | 2 +- ...php => InvoiceParserRegistryInterface.php} | 4 +- .../Parser/Csv/AbstractCsvInvoiceParser.php | 10 + .../Parser/Csv/Cid1CsvInvoiceParser.php | 6 +- .../Parser/Csv/Cid2CsvInvoiceParser.php | 4 +- .../Parser/Csv/CsvInvoiceParserStrategy.php | 32 ---- .../Parser/InvoiceParserRegistry.php | 33 ++++ .../Parser/InvoiceParserResolver.php | 43 ----- .../Parser/Json/AbstractJsonInvoiceParser.php | 10 + .../Parser/Json/Cid1JsonInvoiceParser.php | 4 +- .../Parser/Json/Cid4JsonInvoiceParser.php | 6 +- .../Parser/Json/JsonInvoiceParserStrategy.php | 32 ---- 16 files changed, 138 insertions(+), 253 deletions(-) delete mode 100644 src/Domain/Parser/InvoiceExtensionStrategyInterface.php rename src/Domain/Parser/{InvoiceParserResolverInterface.php => InvoiceParserRegistryInterface.php} (56%) delete mode 100644 src/Infrastructure/Parser/Csv/CsvInvoiceParserStrategy.php create mode 100644 src/Infrastructure/Parser/InvoiceParserRegistry.php delete mode 100644 src/Infrastructure/Parser/InvoiceParserResolver.php delete mode 100644 src/Infrastructure/Parser/Json/JsonInvoiceParserStrategy.php diff --git a/README.md b/README.md index 2cc92fa..9ad1773 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,8 @@ src/ │ ├── Entity/ │ │ └── Invoice.php # Entité Doctrine (name, amount, currency, date) │ ├── Parser/ -│ │ ├── InvoiceParserInterface.php # getClientId() + parse() -│ │ ├── InvoiceExtensionStrategyInterface.php # getExtension() + findParserForClient() -│ │ └── InvoiceParserResolverInterface.php # getParser(filePath) +│ │ ├── InvoiceParserInterface.php # supports() + parse() +│ │ └── InvoiceParserRegistryInterface.php # getParser(filePath) │ └── Repository/ │ └── InvoiceRepositoryInterface.php # create / update / flush / findByNameAndDate │ @@ -39,57 +38,61 @@ src/ ├── Logger/ │ └── InvoiceLogger.php # var/log/invoice.log ├── Parser/ - │ ├── InvoiceParserResolver.php # Routing extension → clientId → parser + │ ├── InvoiceParserRegistry.php # Itère les parsers, retourne le premier qui supporte le fichier │ ├── Csv/ - │ │ ├── AbstractCsvInvoiceParser.php # Logique CSV générique - │ │ ├── CsvInvoiceParserStrategy.php # Registry des parsers CSV par client + │ │ ├── AbstractCsvInvoiceParser.php # Logique CSV commune (supports + parse) │ │ ├── Cid1CsvInvoiceParser.php # Client 1 — TSV, sans header - │ │ └── Cid2CsvInvoiceParser.php # Client 2 — CSV, avec header - │ └── Json/ - │ ├── AbstractJsonInvoiceParser.php # Logique JSON générique - │ ├── JsonInvoiceParserStrategy.php # Registry des parsers JSON par client - │ ├── Cid1JsonInvoiceParser.php # Client 1 — champs FR - │ └── Cid4JsonInvoiceParser.php # Client 4 — champs mixtes + │ │ └── Cid2CsvInvoiceParser.php # Client 2 — CSV virgule, avec header + │ ├── Json/ + │ │ ├── AbstractJsonInvoiceParser.php # Logique JSON commune + │ │ ├── Cid1JsonInvoiceParser.php # Client 1 — champs FR + │ │ └── Cid4JsonInvoiceParser.php # Client 4 — champs mixtes └── Repository/ └── InvoiceRepository.php # Implémentation Doctrine ``` --- -## Pattern Strategy + Resolver +## Pattern Registry -`InvoiceParserResolver` analyse le nom de fichier et sélectionne le bon parser en deux niveaux : - -| Niveau | Interface | Clé de sélection | -|--------|-----------|------------------| -| 1 | `InvoiceExtensionStrategyInterface` | extension du fichier (`csv`, `json`…) | -| 2 | `InvoiceParserInterface` | identifiant client dans le nom (`cid_1`, `cid_2`…) | +`InvoiceParserRegistry` itère tous les parsers et délègue la sélection à chacun via `supports()`. +Chaque parser sait lui-même s'il prend en charge le fichier, en vérifiant l'extension **et** l'identifiant client dans le nom. ``` invoices_20260607100102_cid_1.csv - └─cid─┘└─ext─┘ - │ - ┌─────────────┘ Niveau 1 - ▼ - CsvInvoiceParserStrategy - │ - cid_1│ Niveau 2 - ▼ - Cid1CsvInvoiceParser + └─────┘└──┘ + client ext + │ + ▼ + Cid1CsvInvoiceParser::supports() ✔ + Cid2CsvInvoiceParser::supports() ✘ + Cid1JsonInvoiceParser::supports() ✘ + ... ``` -**Extraction du client ID** — regex `/(cid_\d+)[_.]/` appliquée sur le nom de fichier. +**Un même client peut avoir plusieurs formats.** La clé de sélection est `extension + cid_x`, pas juste le client. +`cid_1` avec un CSV → `Cid1CsvInvoiceParser`. `cid_1` avec un JSON → `Cid1JsonInvoiceParser`. Les deux parsers coexistent sans conflit : -**Upsert** — clé unique `name + date`. Les lignes existantes sont mises à jour (`amount`, `currency`). Les doublons dans le même fichier sont détectés en mémoire via `$invoiceCache` avant le `flush()`, pour éviter les faux inserts. +``` +invoices_xxx_cid_1.csv → Cid1CsvInvoiceParser::supports() ✔ (csv + cid_1) + Cid1JsonInvoiceParser::supports() ✘ (json ≠ csv) ---- +invoices_xxx_cid_1.json → Cid1CsvInvoiceParser::supports() ✘ (csv ≠ json) + Cid1JsonInvoiceParser::supports() ✔ (json + cid_1) +``` -## Format des fichiers +**Format de nom de fichier attendu :** ``` invoices_{datetime}_cid_{id}.{ext} ``` +**Upsert** — clé unique `name + date`. Les lignes existantes sont mises à jour (`amount`, `currency`). Les doublons dans le même fichier sont détectés en mémoire via `$invoiceCache` avant le `flush()`. + +--- + +## Format des fichiers + Exemples : ``` data/invoices_20260607120101_cid_1.csv @@ -105,6 +108,7 @@ data/invoices_20260607120401_cid_2.csv | Commande | Description | |----------|-------------| | `make up` | Démarrer les containers | +| `make start` | Rebuilder et démarrer les containers | | `make down` | Arrêter les containers | | `make app-bash` | Ouvrir un shell dans le container | | `make cc` | Vider le cache Symfony | @@ -119,11 +123,11 @@ data/invoices_20260607120401_cid_2.csv ## Installation ```bash -# 1. Clone le projet dans ton répertoire local +# 1. Clone le projet dans le répertoire local git clone https://github.com/InnobyteNo8651/invoice-parser.git cd invoice-parser -# 2. Aller sur ta branche +# 2. Aller sur la branche git switch refactor/code-quality-improvements # 3. Démarrer les containers @@ -185,16 +189,21 @@ Les erreurs sont affichées en console et loggées dans `var/log/invoice.log`. --- -## Ajouter un nouveau client (extension existante) +## Ajouter un nouveau client -### Client CSV (ex: cid_5) +Il suffit de créer **une seule classe**. Symfony la détecte automatiquement via `_instanceof` dans `services.yaml`. -Créer `src/Infrastructure/Parser/Csv/Cid5CsvInvoiceParser.php` : +### Client CSV (ex: cid_5) ```php +// src/Infrastructure/Parser/Csv/Cid5CsvInvoiceParser.php final class Cid5CsvInvoiceParser extends AbstractCsvInvoiceParser { - public static function getClientId(): string { return 'cid_5'; } + protected function supportsClient(string $filePath): bool + { + return str_contains(basename($filePath), 'cid_5'); + } + public function getSeparator(): string { return ';'; } public function getColumns(): array { return ['name', 'amount', 'currency', 'date']; } public function getPositions(): array { return [0, 1, 2, 3]; } @@ -202,16 +211,16 @@ final class Cid5CsvInvoiceParser extends AbstractCsvInvoiceParser } ``` -Symfony le détecte automatiquement via `_instanceof` dans `services.yaml` — aucune autre configuration nécessaire. - ### Client JSON (ex: cid_6) -Créer `src/Infrastructure/Parser/Json/Cid6JsonInvoiceParser.php` : - ```php +// src/Infrastructure/Parser/Json/Cid6JsonInvoiceParser.php final class Cid6JsonInvoiceParser extends AbstractJsonInvoiceParser { - public static function getClientId(): string { return 'cid_6'; } + protected function supportsClient(string $filePath): bool + { + return str_contains(basename($filePath), 'cid_6'); + } public function getFieldMap(): array { @@ -220,30 +229,27 @@ final class Cid6JsonInvoiceParser extends AbstractJsonInvoiceParser } ``` -Même principe — aucune configuration supplémentaire dans `services.yaml`. - --- -## Ajouter une nouvelle extension (ex : XML) - -Pour ajouter le support d'un nouveau format de fichier, il faut créer **4 éléments** et ajouter **1 ligne** dans `services.yaml`. +## Ajouter un nouveau format de fichier -### Étape 1 — Classe abstraite +Pour un format inédit (ex: XML), créer **deux fichiers**. Symfony les détecte automatiquement via `_instanceof` sur `InvoiceParserInterface`. -Créer `src/Infrastructure/Parser/Xml/AbstractXmlInvoiceParser.php` : +### 1 — Classe abstraite ```php -namespace App\Infrastructure\Parser\Xml; - -use App\Domain\Entity\Invoice; -use App\Domain\Parser\InvoiceParserInterface; -use DateTimeImmutable; -use Throwable; - +// src/Infrastructure/Parser/Xml/AbstractXmlInvoiceParser.php abstract class AbstractXmlInvoiceParser implements InvoiceParserInterface { + abstract protected function supportsClient(string $filePath): bool; abstract public function getFieldMap(): array; + public function supports(string $filePath): bool + { + return pathinfo($filePath, PATHINFO_EXTENSION) === 'xml' + && $this->supportsClient($filePath); + } + public function parse(string $filePath): array { // lire le fichier, instancier SimpleXMLElement, @@ -252,46 +258,16 @@ abstract class AbstractXmlInvoiceParser implements InvoiceParserInterface } ``` -### Étape 2 — Strategy (registry des parsers XML) - -Créer `src/Infrastructure/Parser/Xml/XmlInvoiceParserStrategy.php` : +### 2 — Parser concret (ex: cid_7) ```php -namespace App\Infrastructure\Parser\Xml; - -use App\Domain\Parser\InvoiceExtensionStrategyInterface; -use App\Domain\Parser\InvoiceParserInterface; -use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; - -final class XmlInvoiceParserStrategy implements InvoiceExtensionStrategyInterface +// src/Infrastructure/Parser/Xml/Cid7XmlInvoiceParser.php +final class Cid7XmlInvoiceParser extends AbstractXmlInvoiceParser { - /** @var array */ - private array $parsers; - - public function __construct( - #[AutowireIterator('app.xml_invoice_parser', defaultIndexMethod: 'getClientId')] - iterable $parsers, - ) { - $this->parsers = iterator_to_array($parsers); - } - - public static function getExtension(): string { return 'xml'; } - - public function findParserForClient(string $clientId): ?InvoiceParserInterface + protected function supportsClient(string $filePath): bool { - return $this->parsers[$clientId] ?? null; + return str_contains(basename($filePath), 'cid_7'); } -} -``` - -### Étape 3 — Parser concret pour chaque client XML - -Créer `src/Infrastructure/Parser/Xml/Cid7XmlInvoiceParser.php` : - -```php -final class Cid7XmlInvoiceParser extends AbstractXmlInvoiceParser -{ - public static function getClientId(): string { return 'cid_7'; } public function getFieldMap(): array { @@ -300,24 +276,6 @@ final class Cid7XmlInvoiceParser extends AbstractXmlInvoiceParser } ``` -### Étape 4 — Déclarer le tag dans `services.yaml` - -```yaml -_instanceof: - # entrées existantes... - App\Infrastructure\Parser\Xml\AbstractXmlInvoiceParser: - tags: ['app.xml_invoice_parser'] -``` - -`XmlInvoiceParserStrategy` est auto-détecté via `_instanceof: InvoiceExtensionStrategyInterface` (déjà configuré) — aucune autre modification nécessaire. - -### Vérification - -```bash -make app-bash -php bin/console app:parse-invoices data/invoices_[DATETIME]_cid_7.xml -``` - --- ## Tests diff --git a/config/services.yaml b/config/services.yaml index 9f101d6..9de87a0 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -21,9 +21,5 @@ services: - '../src/Kernel.php' _instanceof: - App\Domain\Parser\InvoiceExtensionStrategyInterface: - tags: ['app.extension_registry'] - App\Infrastructure\Parser\Csv\AbstractCsvInvoiceParser: - tags: ['app.csv_invoice_parser'] - App\Infrastructure\Parser\Json\AbstractJsonInvoiceParser: - tags: ['app.json_invoice_parser'] + App\Domain\Parser\InvoiceParserInterface: + tags: ['app.invoice_parser'] diff --git a/src/Application/Command/ParseInvoiceFile/ParseInvoiceFileHandler.php b/src/Application/Command/ParseInvoiceFile/ParseInvoiceFileHandler.php index db0e23b..0b787c1 100644 --- a/src/Application/Command/ParseInvoiceFile/ParseInvoiceFileHandler.php +++ b/src/Application/Command/ParseInvoiceFile/ParseInvoiceFileHandler.php @@ -5,7 +5,7 @@ namespace App\Application\Command\ParseInvoiceFile; use App\Domain\Entity\Invoice; -use App\Domain\Parser\InvoiceParserResolverInterface; +use App\Domain\Parser\InvoiceParserRegistryInterface; use App\Domain\Repository\InvoiceRepositoryInterface; use App\Infrastructure\Logger\InvoiceLogger; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -14,7 +14,7 @@ final class ParseInvoiceFileHandler { public function __construct( - private readonly InvoiceParserResolverInterface $parserResolver, + private readonly InvoiceParserRegistryInterface $parserRegistry, private readonly InvoiceRepositoryInterface $invoiceRepository, private readonly ValidatorInterface $validator, private readonly InvoiceLogger $logger, @@ -24,7 +24,7 @@ public function handle(ParseInvoiceFileCommand $command): ParseInvoiceResult { $result = new ParseInvoiceResult(); $file = $command->filePath; - $parser = $this->parserResolver->getParser($file); + $parser = $this->parserRegistry->getParser($file); if ($parser === null) { $message = sprintf('No parser found for file: "%s".', basename($file)); diff --git a/src/Domain/Parser/InvoiceExtensionStrategyInterface.php b/src/Domain/Parser/InvoiceExtensionStrategyInterface.php deleted file mode 100644 index 8565e34..0000000 --- a/src/Domain/Parser/InvoiceExtensionStrategyInterface.php +++ /dev/null @@ -1,13 +0,0 @@ - */ public function parse(string $filePath): array; diff --git a/src/Domain/Parser/InvoiceParserResolverInterface.php b/src/Domain/Parser/InvoiceParserRegistryInterface.php similarity index 56% rename from src/Domain/Parser/InvoiceParserResolverInterface.php rename to src/Domain/Parser/InvoiceParserRegistryInterface.php index 7dac738..df6dd6c 100644 --- a/src/Domain/Parser/InvoiceParserResolverInterface.php +++ b/src/Domain/Parser/InvoiceParserRegistryInterface.php @@ -4,9 +4,7 @@ namespace App\Domain\Parser; -interface InvoiceParserResolverInterface +interface InvoiceParserRegistryInterface { - public const FILENAME_CLIENT_ID_PATTERN = '/(cid_\d+)[_.]/'; - public function getParser(string $filePath): ?InvoiceParserInterface; } diff --git a/src/Infrastructure/Parser/Csv/AbstractCsvInvoiceParser.php b/src/Infrastructure/Parser/Csv/AbstractCsvInvoiceParser.php index 0ee55de..d941f0a 100644 --- a/src/Infrastructure/Parser/Csv/AbstractCsvInvoiceParser.php +++ b/src/Infrastructure/Parser/Csv/AbstractCsvInvoiceParser.php @@ -12,6 +12,10 @@ abstract class AbstractCsvInvoiceParser implements InvoiceParserInterface { + protected const EXTENSION = 'csv'; + + abstract protected function supportsClient(string $filePath): bool; + abstract public function getSeparator(): string; /** @return string[] logical field names: ['name', 'amount', 'currency', 'date'] */ @@ -22,6 +26,12 @@ abstract public function getPositions(): array; abstract public function hasHeader(): bool; + public function supports(string $filePath): bool + { + return pathinfo($filePath, PATHINFO_EXTENSION) === self::EXTENSION + && $this->supportsClient($filePath); + } + public function parse(string $filePath): array { $lines = @file($filePath, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES); diff --git a/src/Infrastructure/Parser/Csv/Cid1CsvInvoiceParser.php b/src/Infrastructure/Parser/Csv/Cid1CsvInvoiceParser.php index fd97900..98e7a16 100644 --- a/src/Infrastructure/Parser/Csv/Cid1CsvInvoiceParser.php +++ b/src/Infrastructure/Parser/Csv/Cid1CsvInvoiceParser.php @@ -6,9 +6,9 @@ final class Cid1CsvInvoiceParser extends AbstractCsvInvoiceParser { - public static function getClientId(): string - { - return 'cid_1'; + protected function supportsClient(string $filePath): bool + { + return str_contains(basename($filePath), 'cid_1'); } public function getSeparator(): string diff --git a/src/Infrastructure/Parser/Csv/Cid2CsvInvoiceParser.php b/src/Infrastructure/Parser/Csv/Cid2CsvInvoiceParser.php index a67becd..70499f1 100644 --- a/src/Infrastructure/Parser/Csv/Cid2CsvInvoiceParser.php +++ b/src/Infrastructure/Parser/Csv/Cid2CsvInvoiceParser.php @@ -6,9 +6,9 @@ final class Cid2CsvInvoiceParser extends AbstractCsvInvoiceParser { - public static function getClientId(): string + protected function supportsClient(string $filePath): bool { - return 'cid_2'; + return str_contains(basename($filePath), 'cid_2'); } public function getSeparator(): string diff --git a/src/Infrastructure/Parser/Csv/CsvInvoiceParserStrategy.php b/src/Infrastructure/Parser/Csv/CsvInvoiceParserStrategy.php deleted file mode 100644 index 43625b8..0000000 --- a/src/Infrastructure/Parser/Csv/CsvInvoiceParserStrategy.php +++ /dev/null @@ -1,32 +0,0 @@ - */ - private array $parsers; - - public function __construct( - #[AutowireIterator('app.csv_invoice_parser', defaultIndexMethod: 'getClientId')] - iterable $parsers, - ) { - $this->parsers = iterator_to_array($parsers); - } - - public static function getExtension(): string - { - return 'csv'; - } - - public function findParserForClient(string $clientId): ?InvoiceParserInterface - { - return $this->parsers[$clientId] ?? null; - } -} diff --git a/src/Infrastructure/Parser/InvoiceParserRegistry.php b/src/Infrastructure/Parser/InvoiceParserRegistry.php new file mode 100644 index 0000000..b7c12f3 --- /dev/null +++ b/src/Infrastructure/Parser/InvoiceParserRegistry.php @@ -0,0 +1,33 @@ + */ + private iterable $parsers; + + public function __construct( + #[AutowireIterator('app.invoice_parser')] + iterable $parsers, + ) { + $this->parsers = $parsers; + } + + public function getParser(string $filePath): ?InvoiceParserInterface + { + foreach ($this->parsers as $parser) { + if ($parser->supports($filePath)) { + return $parser; + } + } + + return null; + } +} diff --git a/src/Infrastructure/Parser/InvoiceParserResolver.php b/src/Infrastructure/Parser/InvoiceParserResolver.php deleted file mode 100644 index d8e3897..0000000 --- a/src/Infrastructure/Parser/InvoiceParserResolver.php +++ /dev/null @@ -1,43 +0,0 @@ - */ - private array $extensionStrategies; - - public function __construct( - #[AutowireIterator('app.extension_registry', defaultIndexMethod: 'getExtension')] - iterable $extensionStrategies, - ) { - $this->extensionStrategies = iterator_to_array($extensionStrategies); - } - - public function getParser(string $filePath): ?InvoiceParserInterface - { - $extension = pathinfo($filePath, PATHINFO_EXTENSION); - $clientId = $this->extractClientId($filePath); - - if ($clientId === null) { - return null; - } - - return ($this->extensionStrategies[$extension] ?? null)?->findParserForClient($clientId); - } - - private function extractClientId(string $filePath): ?string - { - if (preg_match(InvoiceParserResolverInterface::FILENAME_CLIENT_ID_PATTERN, basename($filePath), $matches)) { - return $matches[1]; - } - return null; - } -} diff --git a/src/Infrastructure/Parser/Json/AbstractJsonInvoiceParser.php b/src/Infrastructure/Parser/Json/AbstractJsonInvoiceParser.php index 593c115..5e14ab4 100644 --- a/src/Infrastructure/Parser/Json/AbstractJsonInvoiceParser.php +++ b/src/Infrastructure/Parser/Json/AbstractJsonInvoiceParser.php @@ -12,6 +12,10 @@ abstract class AbstractJsonInvoiceParser implements InvoiceParserInterface { + protected const EXTENSION = 'json'; + + abstract protected function supportsClient(string $filePath): bool; + /** * Maps logical field names to JSON keys. * e.g. ['name' => 'nom', 'amount' => 'montant', 'currency' => 'devise', 'date' => 'date'] @@ -20,6 +24,12 @@ abstract class AbstractJsonInvoiceParser implements InvoiceParserInterface */ abstract public function getFieldMap(): array; + public function supports(string $filePath): bool + { + return pathinfo($filePath, PATHINFO_EXTENSION) === self::EXTENSION + && $this->supportsClient($filePath); + } + public function parse(string $filePath): array { $contents = @file_get_contents($filePath); diff --git a/src/Infrastructure/Parser/Json/Cid1JsonInvoiceParser.php b/src/Infrastructure/Parser/Json/Cid1JsonInvoiceParser.php index b9213ef..34ada50 100644 --- a/src/Infrastructure/Parser/Json/Cid1JsonInvoiceParser.php +++ b/src/Infrastructure/Parser/Json/Cid1JsonInvoiceParser.php @@ -6,9 +6,9 @@ final class Cid1JsonInvoiceParser extends AbstractJsonInvoiceParser { - public static function getClientId(): string + protected function supportsClient(string $filePath): bool { - return 'cid_1'; + return str_contains(basename($filePath), 'cid_1'); } public function getFieldMap(): array diff --git a/src/Infrastructure/Parser/Json/Cid4JsonInvoiceParser.php b/src/Infrastructure/Parser/Json/Cid4JsonInvoiceParser.php index 3f58ddb..bed6c22 100644 --- a/src/Infrastructure/Parser/Json/Cid4JsonInvoiceParser.php +++ b/src/Infrastructure/Parser/Json/Cid4JsonInvoiceParser.php @@ -6,9 +6,9 @@ final class Cid4JsonInvoiceParser extends AbstractJsonInvoiceParser { - public static function getClientId(): string - { - return 'cid_4'; + protected function supportsClient(string $filePath): bool + { + return str_contains(basename($filePath), 'cid_4'); } public function getFieldMap(): array diff --git a/src/Infrastructure/Parser/Json/JsonInvoiceParserStrategy.php b/src/Infrastructure/Parser/Json/JsonInvoiceParserStrategy.php deleted file mode 100644 index f85af5b..0000000 --- a/src/Infrastructure/Parser/Json/JsonInvoiceParserStrategy.php +++ /dev/null @@ -1,32 +0,0 @@ - */ - private array $parsers; - - public function __construct( - #[AutowireIterator('app.json_invoice_parser', defaultIndexMethod: 'getClientId')] - iterable $parsers, - ) { - $this->parsers = iterator_to_array($parsers); - } - - public static function getExtension(): string - { - return 'json'; - } - - public function findParserForClient(string $clientId): ?InvoiceParserInterface - { - return $this->parsers[$clientId] ?? null; - } -}