diff --git a/Dockerfile b/Dockerfile index 1ead9c3..2cfa5c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ #syntax=docker/dockerfile:1.4 # Versions -FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream +FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream # The different stages of this Dockerfile are meant to be built into separate images # https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage diff --git a/assets/vue/app/domain/setting/presentation/components/ContentSourceForm.vue b/assets/vue/app/domain/setting/presentation/components/ContentSourceForm.vue index 0761ad0..1d3222b 100644 --- a/assets/vue/app/domain/setting/presentation/components/ContentSourceForm.vue +++ b/assets/vue/app/domain/setting/presentation/components/ContentSourceForm.vue @@ -242,8 +242,17 @@ watch(() => props.source, (newSource) => { } }, { immediate: true }); +const buildPayload = (formData) => { + const data = { ...formData }; + const raw = data.testChapterNumber; + data.testChapterNumber = (raw === '' || raw === null || raw === undefined) + ? null + : parseFloat(raw); + return data; +}; + const handleSubmit = () => { - emit('submit', { ...form.value }); + emit('submit', buildPayload(form.value)); }; defineExpose({ submitForm: handleSubmit }); @@ -252,7 +261,7 @@ const testConfiguration = async () => { testing.value = true; try { await emit('test', { - configuration: { ...form.value }, + configuration: buildPayload(form.value), testData: { mangaSlug: form.value.testSlug, chapterNumber: form.value.testChapterNumber, diff --git a/composer.json b/composer.json index a11d519..2e3815d 100644 --- a/composer.json +++ b/composer.json @@ -3,56 +3,55 @@ "type": "project", "license": "MIT", "description": "A minimal Symfony project recommended to create bare bones applications", - "minimum-stability": "stable", + "minimum-stability": "dev", "prefer-stable": true, "require": { - "php": ">=8.3.1", + "php": ">=8.4.0", "ext-ctype": "*", "ext-curl": "*", "ext-gd": "*", "ext-iconv": "*", "ext-zip": "*", - "api-platform/core": "^3.2", - "doctrine/dbal": "^3", - "doctrine/doctrine-bundle": "^2.11", + "api-platform/core": "^4.0", + "doctrine/dbal": "^4", + "doctrine/doctrine-bundle": "^3.0", "doctrine/doctrine-migrations-bundle": "^3.3", - "doctrine/orm": "^2.17", + "doctrine/orm": "^3.0", "guzzlehttp/guzzle": "^7.8", "intervention/image": "^3.7", "nelmio/cors-bundle": "^2.4", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpdoc-parser": "^1.25", "ramsey/uuid": "^4.7", - "runtime/frankenphp-symfony": "^0.2.0", - "symfony/asset": "7.0.*", - "symfony/console": "7.0.*", - "symfony/css-selector": "7.0.*", - "symfony/doctrine-messenger": "7.0.*", - "symfony/dotenv": "7.0.*", - "symfony/expression-language": "7.0.*", + "symfony/asset": "8.0.*", + "symfony/console": "8.0.*", + "symfony/css-selector": "8.0.*", + "symfony/doctrine-messenger": "8.0.*", + "symfony/dotenv": "8.0.*", + "symfony/expression-language": "8.0.*", "symfony/flex": "^2", - "symfony/form": "7.0.*", - "symfony/framework-bundle": "7.0.*", - "symfony/http-client": "7.0.*", - "symfony/mercure-bundle": "^0.3.9", - "symfony/messenger": "7.0.*", - "symfony/mime": "7.0.*", - "symfony/monolog-bundle": "^3.10", + "symfony/form": "8.0.*", + "symfony/framework-bundle": "8.0.*", + "symfony/http-client": "8.0.*", + "symfony/mercure-bundle": "^0.4", + "symfony/messenger": "8.0.*", + "symfony/mime": "8.0.*", + "symfony/monolog-bundle": "^4.0", "symfony/panther": "^2.1", - "symfony/property-access": "7.0.*", - "symfony/property-info": "7.0.*", - "symfony/runtime": "7.0.*", - "symfony/scheduler": "7.0.*", - "symfony/security-bundle": "7.0.*", - "symfony/serializer": "7.0.*", + "symfony/property-access": "8.0.*", + "symfony/property-info": "8.0.*", + "symfony/runtime": "8.0.*", + "symfony/scheduler": "8.0.*", + "symfony/security-bundle": "8.0.*", + "symfony/serializer": "8.0.*", "symfony/stimulus-bundle": "^2.17", - "symfony/twig-bundle": "7.0.*", + "symfony/twig-bundle": "8.0.*", "symfony/ux-live-component": "^2.17", "symfony/ux-react": "^2.23", "symfony/ux-turbo": "^2.18", - "symfony/validator": "7.0.*", + "symfony/validator": "8.0.*", "symfony/webpack-encore-bundle": "^2.1", - "symfony/yaml": "7.0.*", + "symfony/yaml": "8.0.*", "twig/extra-bundle": "^2.12|^3.0", "twig/twig": "^2.12|^3.0", "vich/uploader-bundle": "^2.7" @@ -103,7 +102,7 @@ "extra": { "symfony": { "allow-contrib": false, - "require": "7.0.*", + "require": "8.0.*", "docker": true } }, @@ -111,18 +110,18 @@ "dama/doctrine-test-bundle": "^8.2", "dbrekelmans/bdi": "^1.3", "deployer/deployer": "^7.5", - "doctrine/doctrine-fixtures-bundle": "^3.5", + "doctrine/doctrine-fixtures-bundle": "^4.0", "friendsofphp/php-cs-fixer": "^3.48", "mtdowling/jmespath.php": "^2.7", - "phparkitect/phparkitect": "^0.3.33", - "phpmd/phpmd": "^2.15", + "phparkitect/phparkitect": "^0.8", + "phpmd/phpmd": "3.x-dev", "phpunit/phpunit": "^10.5", - "symfony/browser-kit": "7.0.*", + "symfony/browser-kit": "8.0.*", "symfony/maker-bundle": "^1.52", - "symfony/phpunit-bridge": "^7.0", - "symfony/stopwatch": "7.0.*", - "symfony/web-profiler-bundle": "7.0.*", + "symfony/phpunit-bridge": "^8.0", + "symfony/stopwatch": "8.0.*", + "symfony/web-profiler-bundle": "8.0.*", "zenstruck/browser": "^1.8", - "zenstruck/foundry": "^1.36" + "zenstruck/foundry": "^2.0" } } diff --git a/composer.lock b/composer.lock index 3769969..31cc5bd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,118 +4,155 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0fc13b604085c0e8bcdf062505a21389", + "content-hash": "281edff65ffa4e019c69d0ffbef5f223", "packages": [ { "name": "api-platform/core", - "version": "v3.3.15", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/api-platform/core.git", - "reference": "406285383ddcab5db56825fc054ca4ba65691ff6" + "reference": "bb36677b9e948661f2afe3d6a9a9721b541b2b5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/api-platform/core/zipball/406285383ddcab5db56825fc054ca4ba65691ff6", - "reference": "406285383ddcab5db56825fc054ca4ba65691ff6", + "url": "https://api.github.com/repos/api-platform/core/zipball/bb36677b9e948661f2afe3d6a9a9721b541b2b5d", + "reference": "bb36677b9e948661f2afe3d6a9a9721b541b2b5d", "shasum": "" }, "require": { - "doctrine/inflector": "^1.0 || ^2.0", - "php": ">=8.1", + "composer/semver": "^3.4", + "doctrine/inflector": "^2.0", + "php": ">=8.2", "psr/cache": "^1.0 || ^2.0 || ^3.0", "psr/container": "^1.0 || ^2.0", "symfony/deprecation-contracts": "^3.1", - "symfony/http-foundation": "^6.4 || ^7.0", - "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/property-access": "^6.4 || ^7.0", - "symfony/property-info": "^6.4 || ^7.0", - "symfony/serializer": "^6.4 || ^7.0", + "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0", + "symfony/property-access": "^6.4 || ^7.0 || ^8.0", + "symfony/property-info": "^6.4 || ^7.1 || ^8.0", + "symfony/serializer": "^6.4 || ^7.0 || ^8.0", "symfony/translation-contracts": "^3.3", - "symfony/web-link": "^6.4 || ^7.0", - "willdurand/negotiation": "^3.0" + "symfony/type-info": "^7.4 || ^8.0", + "symfony/validator": "^6.4.11 || ^7.1 || ^8.0", + "symfony/web-link": "^6.4 || ^7.1 || ^8.0", + "willdurand/negotiation": "^3.1" }, "conflict": { "doctrine/common": "<3.2.2", "doctrine/dbal": "<2.10", "doctrine/mongodb-odm": "<2.4", - "doctrine/orm": "<2.14.0", + "doctrine/orm": "<2.14.0 || 3.0.0", "doctrine/persistence": "<1.3", - "elasticsearch/elasticsearch": ">=8.0,<8.4", "phpspec/prophecy": "<1.15", "phpunit/phpunit": "<9.5", "symfony/framework-bundle": "6.4.6 || 7.0.6", + "symfony/object-mapper": "<7.3.4", "symfony/var-exporter": "<6.1.1" }, + "replace": { + "api-platform/doctrine-common": "self.version", + "api-platform/doctrine-odm": "self.version", + "api-platform/doctrine-orm": "self.version", + "api-platform/documentation": "self.version", + "api-platform/elasticsearch": "self.version", + "api-platform/graphql": "self.version", + "api-platform/hal": "self.version", + "api-platform/http-cache": "self.version", + "api-platform/hydra": "self.version", + "api-platform/json-api": "self.version", + "api-platform/json-schema": "self.version", + "api-platform/jsonld": "self.version", + "api-platform/laravel": "self.version", + "api-platform/mcp": "self.version", + "api-platform/metadata": "self.version", + "api-platform/openapi": "self.version", + "api-platform/parameter-validator": "self.version", + "api-platform/ramsey-uuid": "self.version", + "api-platform/serializer": "self.version", + "api-platform/state": "self.version", + "api-platform/symfony": "self.version", + "api-platform/validator": "self.version" + }, "require-dev": { "behat/behat": "^3.11", "behat/mink": "^1.9", - "doctrine/cache": "^1.11 || ^2.1", "doctrine/common": "^3.2.2", - "doctrine/dbal": "^3.4.0", - "doctrine/doctrine-bundle": "^1.12 || ^2.0", - "doctrine/mongodb-odm": "^2.2", - "doctrine/mongodb-odm-bundle": "^4.0 || ^5.0", - "doctrine/orm": "^2.14 || ^3.0", - "elasticsearch/elasticsearch": "^7.11 || ^8.4", + "doctrine/dbal": "^4.0", + "doctrine/doctrine-bundle": "^2.11 || ^3.1", + "doctrine/orm": "^2.17 || ^3.0", + "elasticsearch/elasticsearch": "^7.17 || ^8.4 || ^9.0", "friends-of-behat/mink-browserkit-driver": "^1.3.1", "friends-of-behat/mink-extension": "^2.2", "friends-of-behat/symfony-extension": "^2.1", + "friendsofphp/php-cs-fixer": "^3.93", "guzzlehttp/guzzle": "^6.0 || ^7.0", - "jangregor/phpstan-prophecy": "^1.0", - "justinrainbow/json-schema": "^5.2.1", - "phpspec/prophecy-phpunit": "^2.0", + "illuminate/config": "^11.0 || ^12.0", + "illuminate/contracts": "^11.0 || ^12.0", + "illuminate/database": "^11.0 || ^12.0", + "illuminate/http": "^11.0 || ^12.0", + "illuminate/pagination": "^11.0 || ^12.0", + "illuminate/routing": "^11.0 || ^12.0", + "illuminate/support": "^11.0 || ^12.0", + "jangregor/phpstan-prophecy": "^2.1.11", + "justinrainbow/json-schema": "^6.5.2", + "laravel/framework": "^11.0 || ^12.0", + "mcp/sdk": "^0.4.0", + "orchestra/testbench": "^10.9", + "phpspec/prophecy-phpunit": "^2.2", "phpstan/extension-installer": "^1.1", - "phpstan/phpdoc-parser": "^1.13", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-doctrine": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-symfony": "^1.0", - "phpunit/phpunit": "^9.6", + "phpstan/phpdoc-parser": "^1.29 || ^2.0", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-doctrine": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "phpunit/phpunit": "^12.2", "psr/log": "^1.0 || ^2.0 || ^3.0", - "ramsey/uuid": "^3.9.7 || ^4.0", - "ramsey/uuid-doctrine": "^1.4 || ^2.0", - "sebastian/comparator": "<5.0", - "soyuka/contexts": "v3.3.9", - "soyuka/pmu": "^0.0.2", + "ramsey/uuid": "^4.7", + "ramsey/uuid-doctrine": "^2.0", + "soyuka/contexts": "^3.3.10", + "soyuka/pmu": "^0.2.0", "soyuka/stubs-mongodb": "^1.0", - "symfony/asset": "^6.4 || ^7.0", - "symfony/browser-kit": "^6.4 || ^7.0", - "symfony/cache": "^6.4 || ^7.0", - "symfony/config": "^6.4 || ^7.0", - "symfony/console": "^6.4 || ^7.0", - "symfony/css-selector": "^6.4 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.0.12", - "symfony/doctrine-bridge": "^6.4 || ^7.0", - "symfony/dom-crawler": "^6.4 || ^7.0", - "symfony/error-handler": "^6.4 || ^7.0", - "symfony/event-dispatcher": "^6.4 || ^7.0", - "symfony/expression-language": "^6.4 || ^7.0", - "symfony/finder": "^6.4 || ^7.0", - "symfony/form": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", - "symfony/http-client": "^6.4 || ^7.0", - "symfony/intl": "^6.4 || ^7.0", + "symfony/asset": "^6.4 || ^7.0 || ^8.0", + "symfony/browser-kit": "^6.4 || ^7.0 || ^8.0", + "symfony/cache": "^6.4 || ^7.0 || ^8.0", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/css-selector": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/doctrine-bridge": "^6.4.2 || ^7.1 || ^8.0", + "symfony/dom-crawler": "^6.4 || ^7.0 || ^8.0", + "symfony/error-handler": "^6.4 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^6.4 || ^7.0 || ^8.0", + "symfony/expression-language": "^6.4 || ^7.0 || ^8.0", + "symfony/finder": "^6.4 || ^7.0 || ^8.0", + "symfony/form": "^6.4 || ^7.0 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/http-client": "^6.4 || ^7.0 || ^8.0", + "symfony/intl": "^6.4 || ^7.0 || ^8.0", + "symfony/json-streamer": "^7.4 || ^8.0", "symfony/maker-bundle": "^1.24", + "symfony/mcp-bundle": "dev-main", "symfony/mercure-bundle": "*", - "symfony/messenger": "^6.4 || ^7.0", - "symfony/phpunit-bridge": "^6.4.1 || ^7.0", - "symfony/routing": "^6.4 || ^7.0", - "symfony/security-bundle": "^6.4 || ^7.0", - "symfony/security-core": "^6.4 || ^7.0", - "symfony/stopwatch": "^6.4 || ^7.0", - "symfony/twig-bundle": "^6.4 || ^7.0", - "symfony/uid": "^6.4 || ^7.0", - "symfony/validator": "^6.4 || ^7.0", - "symfony/web-profiler-bundle": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0", + "symfony/messenger": "^6.4 || ^7.0 || ^8.0", + "symfony/object-mapper": "^7.4 || ^8.0", + "symfony/routing": "^6.4 || ^7.0 || ^8.0", + "symfony/security-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/security-core": "^6.4 || ^7.0 || ^8.0", + "symfony/stopwatch": "^6.4 || ^7.0 || ^8.0", + "symfony/string": "^6.4 || ^7.0 || ^8.0", + "symfony/twig-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/uid": "^6.4 || ^7.0 || ^8.0", + "symfony/var-exporter": "^7.4 || ^8.0", + "symfony/web-profiler-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0", "twig/twig": "^1.42.3 || ^2.12 || ^3.0", - "webonyx/graphql-php": "^14.0 || ^15.0" + "webonyx/graphql-php": "^15.0" }, "suggest": { "doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.", "elasticsearch/elasticsearch": "To support Elasticsearch.", - "ocramius/package-versions": "To display the API Platform's version in the debug bar.", + "opensearch-project/opensearch-php": "To support OpenSearch (^2.5).", "phpstan/phpdoc-parser": "To support extracting metadata from PHPDoc.", "psr/cache-implementation": "To use metadata caching.", "ramsey/uuid": "To support Ramsey's UUID identifiers.", @@ -123,6 +160,7 @@ "symfony/config": "To load XML configuration files.", "symfony/expression-language": "To use authorization features.", "symfony/http-client": "To use the HTTP cache invalidation system.", + "symfony/json-streamer": "To use the JSON Streamer component.", "symfony/messenger": "To support messenger integration.", "symfony/security": "To use authorization features.", "symfony/twig-bundle": "To use the Swagger UI integration.", @@ -132,34 +170,30 @@ }, "type": "library", "extra": { - "symfony": { - "require": "^6.4 || ^7.0" + "pmu": { + "projects": [ + "./src/*/composer.json", + "src/Doctrine/*/composer.json" + ] + }, + "thanks": { + "url": "https://github.com/api-platform/api-platform", + "name": "api-platform/api-platform" + }, + "symfony": { + "require": "^6.4 || ^7.1 || ^8.0" }, - "projects": [ - "api-platform/doctrine-common", - "api-platform/doctrine-orm", - "api-platform/doctrine-odm", - "api-platform/metadata", - "api-platform/json-schema", - "api-platform/elasticsearch", - "api-platform/jsonld", - "api-platform/hydra", - "api-platform/openapi", - "api-platform/graphql", - "api-platform/http-cache", - "api-platform/documentation", - "api-platform/parameter-validator", - "api-platform/ramsey-uuid", - "api-platform/serializer", - "api-platform/state", - "api-platform/symfony", - "api-platform/validator" - ], "branch-alias": { - "dev-main": "3.3.x-dev" + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev", + "dev-4.2": "4.2.x-dev", + "dev-main": "4.4.x-dev" } }, "autoload": { + "files": [ + "src/JsonLd/HydraContext.php" + ], "psr-4": { "ApiPlatform\\": "src/" } @@ -184,15 +218,17 @@ "graphql", "hal", "jsonapi", + "laravel", "openapi", "rest", - "swagger" + "swagger", + "symfony" ], "support": { "issues": "https://github.com/api-platform/core/issues", - "source": "https://github.com/api-platform/core/tree/v3.3.15" + "source": "https://github.com/api-platform/core/tree/v4.3.1" }, - "time": "2025-01-17T14:11:31+00:00" + "time": "2026-03-20T10:57:31+00:00" }, { "name": "brick/math", @@ -255,37 +291,35 @@ "time": "2026-02-10T14:33:43+00:00" }, { - "name": "doctrine/cache", - "version": "2.2.0", + "name": "composer/semver", + "version": "3.4.4", "source": { "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "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" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -294,59 +328,44 @@ ], "authors": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" }, { - "name": "Roman Borschel", - "email": "roman@code-factory.org" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" }, { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" } ], - "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", + "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" + "semantic", + "semver", + "validation", + "versioning" ], "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.2.0" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", + "url": "https://packagist.com", "type": "custom" }, { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" + "url": "https://github.com/composer", + "type": "github" } ], - "abandoned": true, - "time": "2022-05-20T20:07:39+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "doctrine/collections", @@ -434,141 +453,42 @@ ], "time": "2026-01-15T10:01:58+00:00" }, - { - "name": "doctrine/common", - "version": "3.5.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "d9ea4a54ca2586db781f0265d36bea731ac66ec5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/d9ea4a54ca2586db781f0265d36bea731ac66ec5", - "reference": "d9ea4a54ca2586db781f0265d36bea731ac66ec5", - "shasum": "" - }, - "require": { - "doctrine/persistence": "^2.0 || ^3.0 || ^4.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9.0 || ^10.0", - "doctrine/collections": "^1", - "phpstan/phpstan": "^1.4.1", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", - "squizlabs/php_codesniffer": "^3.0", - "symfony/phpunit-bridge": "^6.1", - "vimeo/psalm": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", - "homepage": "https://www.doctrine-project.org/projects/common.html", - "keywords": [ - "common", - "doctrine", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.5.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%2Fcommon", - "type": "tidelift" - } - ], - "time": "2025-01-01T22:12:03+00:00" - }, { "name": "doctrine/dbal", - "version": "3.10.5", + "version": "4.4.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "95d84866bf3c04b2ddca1df7c049714660959aef" + "reference": "61e730f1658814821a85f2402c945f3883407dec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/95d84866bf3c04b2ddca1df7c049714660959aef", - "reference": "95d84866bf3c04b2ddca1df7c049714660959aef", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/61e730f1658814821a85f2402c945f3883407dec", + "reference": "61e730f1658814821a85f2402c945f3883407dec", "shasum": "" }, "require": { - "composer-runtime-api": "^2", - "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1|^2", - "php": "^7.4 || ^8.0", + "doctrine/deprecations": "^1.1.5", + "php": "^8.2", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, - "conflict": { - "doctrine/cache": "< 1.11" - }, "require-dev": { - "doctrine/cache": "^1.11|^2.0", "doctrine/coding-standard": "14.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2023.1", + "jetbrains/phpstorm-stubs": "2023.2", "phpstan/phpstan": "2.1.30", + "phpstan/phpstan-phpunit": "2.0.7", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "9.6.34", + "phpunit/phpunit": "11.5.50", "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" + "symfony/cache": "^6.3.8|^7.0|^8.0", + "symfony/console": "^5.4|^6.3|^7.0|^8.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." }, - "bin": [ - "bin/doctrine-dbal" - ], "type": "library", "autoload": { "psr-4": { @@ -621,7 +541,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.10.5" + "source": "https://github.com/doctrine/dbal/tree/4.4.3" }, "funding": [ { @@ -637,7 +557,7 @@ "type": "tidelift" } ], - "time": "2026-02-24T08:03:57+00:00" + "time": "2026-03-20T08:52:12+00:00" }, { "name": "doctrine/deprecations", @@ -689,63 +609,57 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.18.2", + "version": "3.2.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546" + "reference": "af84173db6978c3d2688ea3bcf3a91720b0704ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/0ff098b29b8b3c68307c8987dcaed7fd829c6546", - "reference": "0ff098b29b8b3c68307c8987dcaed7fd829c6546", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/af84173db6978c3d2688ea3bcf3a91720b0704ce", + "reference": "af84173db6978c3d2688ea3bcf3a91720b0704ce", "shasum": "" }, "require": { - "doctrine/dbal": "^3.7.0 || ^4.0", + "doctrine/dbal": "^4.0", "doctrine/deprecations": "^1.0", - "doctrine/persistence": "^3.1 || ^4", + "doctrine/persistence": "^4", "doctrine/sql-formatter": "^1.0.1", - "php": "^8.1", - "symfony/cache": "^6.4 || ^7.0", - "symfony/config": "^6.4 || ^7.0", - "symfony/console": "^6.4 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.0", - "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3", - "symfony/framework-bundle": "^6.4 || ^7.0", - "symfony/service-contracts": "^2.5 || ^3" + "php": "^8.4", + "symfony/cache": "^6.4 || ^7.0 || ^8.0", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/service-contracts": "^3" }, "conflict": { - "doctrine/annotations": ">=3.0", - "doctrine/cache": "< 1.11", - "doctrine/orm": "<2.17 || >=4.0", - "symfony/var-exporter": "< 6.4.1 || 7.0.0", - "twig/twig": "<2.13 || >=3.0 <3.0.4" + "doctrine/orm": "<3.0 || >=4.0", + "twig/twig": "<3.0.4" }, "require-dev": { - "doctrine/annotations": "^1 || ^2", - "doctrine/cache": "^1.11 || ^2.0", "doctrine/coding-standard": "^14", - "doctrine/orm": "^2.17 || ^3.1", - "friendsofphp/proxy-manager-lts": "^1.0", + "doctrine/orm": "^3.4.4", "phpstan/phpstan": "2.1.1", "phpstan/phpstan-phpunit": "2.0.3", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "^10.5.53 || ^12.3.10", - "psr/log": "^1.1.4 || ^2.0 || ^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" + "phpstan/phpstan-symfony": "^2.0", + "phpunit/phpunit": "^12.3.10", + "psr/log": "^3.0", + "symfony/doctrine-messenger": "^6.4 || ^7.0 || ^8.0", + "symfony/expression-language": "^6.4 || ^7.0 || ^8.0", + "symfony/messenger": "^6.4 || ^7.0 || ^8.0", + "symfony/property-info": "^6.4 || ^7.0 || ^8.0", + "symfony/security-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/stopwatch": "^6.4 || ^7.0 || ^8.0", + "symfony/string": "^6.4 || ^7.0 || ^8.0", + "symfony/twig-bridge": "^6.4 || ^7.0 || ^8.0", + "symfony/validator": "^6.4 || ^7.0 || ^8.0", + "symfony/web-profiler-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0", + "twig/twig": "^3.21.1" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -790,7 +704,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.18.2" + "source": "https://github.com/doctrine/DoctrineBundle/tree/3.2.2" }, "funding": [ { @@ -806,7 +720,7 @@ "type": "tidelift" } ], - "time": "2025-12-20T21:35:32+00:00" + "time": "2025-12-24T12:24:29+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", @@ -1076,30 +990,29 @@ }, { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7", + "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.4" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^14", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5.58" }, "type": "library", "autoload": { @@ -1126,7 +1039,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "source": "https://github.com/doctrine/instantiator/tree/2.1.0" }, "funding": [ { @@ -1142,7 +1055,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2026-01-05T06:47:08+00:00" }, { "name": "doctrine/lexer", @@ -1326,61 +1239,48 @@ }, { "name": "doctrine/orm", - "version": "2.20.9", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "87f1ba74e04c8694ca00099f3c64706ebac0b114" + "reference": "4262eb495b4d2a53b45de1ac58881e0091f2970f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/87f1ba74e04c8694ca00099f3c64706ebac0b114", - "reference": "87f1ba74e04c8694ca00099f3c64706ebac0b114", + "url": "https://api.github.com/repos/doctrine/orm/zipball/4262eb495b4d2a53b45de1ac58881e0091f2970f", + "reference": "4262eb495b4d2a53b45de1ac58881e0091f2970f", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/cache": "^1.12.1 || ^2.1.1", - "doctrine/collections": "^1.5 || ^2.1", - "doctrine/common": "^3.0.3", - "doctrine/dbal": "^2.13.1 || ^3.2", + "doctrine/collections": "^2.2", + "doctrine/dbal": "^3.8.2 || ^4", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2", "doctrine/inflector": "^1.4 || ^2.0", "doctrine/instantiator": "^1.3 || ^2", - "doctrine/lexer": "^2 || ^3", - "doctrine/persistence": "^2.4 || ^3", + "doctrine/lexer": "^3", + "doctrine/persistence": "^3.3.1 || ^4", "ext-ctype": "*", - "php": "^7.1 || ^8.0", + "php": "^8.1", "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0", - "symfony/polyfill-php72": "^1.23", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "doctrine/annotations": "<1.13 || >= 3.0" + "symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0" }, "require-dev": { - "doctrine/annotations": "^1.13 || ^2", - "doctrine/coding-standard": "^9.0.2 || ^14.0", - "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/extension-installer": "~1.1.0 || ^1.4", - "phpstan/phpstan": "~1.4.10 || 2.1.23", - "phpstan/phpstan-deprecation-rules": "^1 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "doctrine/coding-standard": "^14.0", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "2.1.23", + "phpstan/phpstan-deprecation-rules": "^2", + "phpunit/phpunit": "^10.5.0 || ^11.5", "psr/log": "^1 || ^2 || ^3", - "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0" + "symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" }, - "bin": [ - "bin/doctrine" - ], "type": "library", "autoload": { "psr-4": { @@ -1421,40 +1321,37 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.20.9" + "source": "https://github.com/doctrine/orm/tree/3.6.2" }, - "time": "2025-11-29T14:03:56+00:00" + "time": "2026-01-30T21:41:41+00:00" }, { "name": "doctrine/persistence", - "version": "3.4.3", + "version": "4.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "d59e6ef7caffe6a30f4b6f9e9079a75f52c64ae0" + "reference": "b9c49ad3558bb77ef973f4e173f2e9c2eca9be09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/d59e6ef7caffe6a30f4b6f9e9079a75f52c64ae0", - "reference": "d59e6ef7caffe6a30f4b6f9e9079a75f52c64ae0", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/b9c49ad3558bb77ef973f4e173f2e9c2eca9be09", + "reference": "b9c49ad3558bb77ef973f4e173f2e9c2eca9be09", "shasum": "" }, "require": { "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 || ^14", - "doctrine/common": "^3.0", - "phpstan/phpstan": "^1 || 2.1.30", - "phpstan/phpstan-phpunit": "^1 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "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", + "symfony/finder": "^4.4 || ^5.4 || ^6.0 || ^7.0" }, "type": "library", "autoload": { @@ -1503,7 +1400,7 @@ ], "support": { "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/3.4.3" + "source": "https://github.com/doctrine/persistence/tree/4.1.1" }, "funding": [ { @@ -1519,7 +1416,7 @@ "type": "tidelift" } ], - "time": "2025-10-21T15:21:39+00:00" + "time": "2025-10-16T20:13:18+00:00" }, { "name": "doctrine/sql-formatter", @@ -1787,16 +1684,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "21dc724a0583619cd1652f673303492272778051" + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", - "reference": "21dc724a0583619cd1652f673303492272778051", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", "shasum": "" }, "require": { @@ -1812,6 +1709,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { @@ -1883,7 +1781,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.8.0" + "source": "https://github.com/guzzle/psr7/tree/2.9.0" }, "funding": [ { @@ -1899,7 +1797,7 @@ "type": "tidelift" } ], - "time": "2025-08-23T21:21:41+00:00" + "time": "2026-03-10T16:41:02+00:00" }, { "name": "intervention/gif", @@ -2182,73 +2080,6 @@ ], "time": "2025-10-17T11:30:53+00:00" }, - { - "name": "masterminds/html5", - "version": "2.10.0", - "source": { - "type": "git", - "url": "https://github.com/Masterminds/html5-php.git", - "reference": "fcf91eb64359852f00d921887b219479b4f21251" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", - "reference": "fcf91eb64359852f00d921887b219479b4f21251", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Masterminds\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Matt Butcher", - "email": "technosophos@gmail.com" - }, - { - "name": "Matt Farina", - "email": "matt@mattfarina.com" - }, - { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - } - ], - "description": "An HTML5 parser and serializer.", - "homepage": "http://masterminds.github.io/html5-php", - "keywords": [ - "HTML5", - "dom", - "html", - "parser", - "querypath", - "serializer", - "xml" - ], - "support": { - "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" - }, - "time": "2025-07-25T09:04:22+00:00" - }, { "name": "monolog/monolog", "version": "3.10.0", @@ -2538,16 +2369,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.6", + "version": "5.6.7", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8" + "reference": "31a105931bc8ffa3a123383829772e832fd8d903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/5cee1d3dfc2d2aa6599834520911d246f656bcb8", - "reference": "5cee1d3dfc2d2aa6599834520911d246f656bcb8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/31a105931bc8ffa3a123383829772e832fd8d903", + "reference": "31a105931bc8ffa3a123383829772e832fd8d903", "shasum": "" }, "require": { @@ -2596,9 +2427,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.6" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.7" }, - "time": "2025-12-22T21:13:58+00:00" + "time": "2026-03-18T20:47:46+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -3369,82 +3200,27 @@ }, "time": "2025-12-14T04:43:48+00:00" }, - { - "name": "runtime/frankenphp-symfony", - "version": "0.2.0", - "source": { - "type": "git", - "url": "https://github.com/php-runtime/frankenphp-symfony.git", - "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-runtime/frankenphp-symfony/zipball/56822c3631d9522a3136a4c33082d006bdfe4bad", - "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", - "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", - "symfony/runtime": "^5.4 || ^6.0 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Runtime\\FrankenPhpSymfony\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kévin Dunglas", - "email": "kevin@dunglas.dev" - } - ], - "description": "FrankenPHP runtime for Symfony", - "support": { - "issues": "https://github.com/php-runtime/frankenphp-symfony/issues", - "source": "https://github.com/php-runtime/frankenphp-symfony/tree/0.2.0" - }, - "funding": [ - { - "url": "https://github.com/nyholm", - "type": "github" - } - ], - "time": "2023-12-12T12:06:11+00:00" - }, { "name": "symfony/asset", - "version": "v7.0.8", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "0f106714bb8d857560edd2ada7f387d2f437c830" + "reference": "e32d8441a7d5dd8db159fd71501bd11ff269b5a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/0f106714bb8d857560edd2ada7f387d2f437c830", - "reference": "0f106714bb8d857560edd2ada7f387d2f437c830", + "url": "https://api.github.com/repos/symfony/asset/zipball/e32d8441a7d5dd8db159fd71501bd11ff269b5a4", + "reference": "e32d8441a7d5dd8db159fd71501bd11ff269b5a4", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "conflict": { - "symfony/http-foundation": "<6.4" + "php": ">=8.4" }, "require-dev": { - "symfony/http-client": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0" + "symfony/http-client": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -3472,7 +3248,7 @@ "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/asset/tree/v7.0.8" + "source": "https://github.com/symfony/asset/tree/v8.0.6" }, "funding": [ { @@ -3483,36 +3259,40 @@ "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-05-31T14:55:39+00:00" + "time": "2026-02-09T10:14:57+00:00" }, { "name": "symfony/browser-kit", - "version": "v7.0.8", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "0030d568e9147b853e168117edf72b74f9643e85" + "reference": "0d998c101e1920fc68572209d1316fec0db728ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/0030d568e9147b853e168117edf72b74f9643e85", - "reference": "0030d568e9147b853e168117edf72b74f9643e85", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/0d998c101e1920fc68572209d1316fec0db728ef", + "reference": "0d998c101e1920fc68572209d1316fec0db728ef", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/dom-crawler": "^6.4|^7.0" + "php": ">=8.4", + "symfony/dom-crawler": "^7.4|^8.0" }, "require-dev": { - "symfony/css-selector": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0" + "symfony/css-selector": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -3540,7 +3320,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v7.0.8" + "source": "https://github.com/symfony/browser-kit/tree/v8.0.4" }, "funding": [ { @@ -3551,40 +3331,43 @@ "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-05-31T14:55:39+00:00" + "time": "2026-01-13T13:06:50+00:00" }, { "name": "symfony/cache", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "0ee03f2aa8bb920f7041678f5f46f60998e3a7a8" + "reference": "b7b0f4ce5fb57a8dc061d494639e44e2cf7aa30f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/0ee03f2aa8bb920f7041678f5f46f60998e3a7a8", - "reference": "0ee03f2aa8bb920f7041678f5f46f60998e3a7a8", + "url": "https://api.github.com/repos/symfony/cache/zipball/b7b0f4ce5fb57a8dc061d494639e44e2cf7aa30f", + "reference": "b7b0f4ce5fb57a8dc061d494639e44e2cf7aa30f", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^2.5|^3", + "symfony/cache-contracts": "^3.6", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^7.4|^8.0" }, "conflict": { - "doctrine/dbal": "<3.6", - "symfony/dependency-injection": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/var-dumper": "<6.4" + "doctrine/dbal": "<4.3", + "ext-redis": "<6.1", + "ext-relay": "<0.12.1" }, "provide": { "psr/cache-implementation": "2.0|3.0", @@ -3593,15 +3376,16 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/dbal": "^3.6|^4", + "doctrine/dbal": "^4.3", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/filesystem": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -3636,7 +3420,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.0.10" + "source": "https://github.com/symfony/cache/tree/v8.0.7" }, "funding": [ { @@ -3647,12 +3431,16 @@ "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-07-17T06:06:58+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/cache-contracts", @@ -3732,22 +3520,21 @@ }, { "name": "symfony/clock", - "version": "v7.0.8", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "817e27b87908632f647f8684a603b70ec89b75e4" + "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/817e27b87908632f647f8684a603b70ec89b75e4", - "reference": "817e27b87908632f647f8684a603b70ec89b75e4", + "url": "https://api.github.com/repos/symfony/clock/zipball/832119f9b8dbc6c8e6f65f30c5969eca1e88764f", + "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/clock": "^1.0", - "symfony/polyfill-php83": "^1.28" + "php": ">=8.4", + "psr/clock": "^1.0" }, "provide": { "psr/clock-implementation": "1.0" @@ -3786,7 +3573,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.0.8" + "source": "https://github.com/symfony/clock/tree/v8.0.0" }, "funding": [ { @@ -3797,43 +3584,46 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-05-31T14:55:39+00:00" + "time": "2025-11-12T15:46:48+00:00" }, { "name": "symfony/config", - "version": "v7.0.8", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "f8a8fb0c2d0a188a00a2dd5af8a4eb070641ec60" + "reference": "9a34c52187112503d02903ab35e6e3783f580c29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/f8a8fb0c2d0a188a00a2dd5af8a4eb070641ec60", - "reference": "f8a8fb0c2d0a188a00a2dd5af8a4eb070641ec60", + "url": "https://api.github.com/repos/symfony/config/zipball/9a34c52187112503d02903ab35e6e3783f580c29", + "reference": "9a34c52187112503d02903ab35e6e3783f580c29", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^6.4|^7.0", - "symfony/polyfill-ctype": "~1.8" + "symfony/filesystem": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/finder": "<6.4", "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0" + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -3861,7 +3651,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/v7.0.8" + "source": "https://github.com/symfony/config/tree/v8.0.7" }, "funding": [ { @@ -3872,55 +3662,52 @@ "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-05-31T14:55:39+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/console", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f381ef0bc6675a29796d7055088a7193a9e6edff" + "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f381ef0bc6675a29796d7055088a7193a9e6edff", - "reference": "f381ef0bc6675a29796d7055088a7193a9e6edff", + "url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a", + "reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-mbstring": "~1.0", + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "symfony/string": "^7.4|^8.0" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -3954,7 +3741,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.0.10" + "source": "https://github.com/symfony/console/tree/v8.0.7" }, "funding": [ { @@ -3965,29 +3752,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-07-26T12:31:22+00:00" + "time": "2026-03-06T14:06:22+00:00" }, { "name": "symfony/css-selector", - "version": "v7.0.8", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "63b9f8c9b3c28c43ad06764c67fe092af2576d17" + "reference": "2a178bf80f05dbbe469a337730eba79d61315262" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/63b9f8c9b3c28c43ad06764c67fe092af2576d17", - "reference": "63b9f8c9b3c28c43ad06764c67fe092af2576d17", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2a178bf80f05dbbe469a337730eba79d61315262", + "reference": "2a178bf80f05dbbe469a337730eba79d61315262", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "type": "library", "autoload": { @@ -4019,7 +3810,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.0.8" + "source": "https://github.com/symfony/css-selector/tree/v8.0.6" }, "funding": [ { @@ -4030,48 +3821,49 @@ "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-05-31T14:55:39+00:00" + "time": "2026-02-17T13:07:04+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "01dcf140b25aa351383f2d3829acbcedd9784ee9" + "reference": "1faaac6dbfe069a92ab9e5c270fa43fc4e1761da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/01dcf140b25aa351383f2d3829acbcedd9784ee9", - "reference": "01dcf140b25aa351383f2d3829acbcedd9784ee9", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/1faaac6dbfe069a92ab9e5c270fa43fc4e1761da", + "reference": "1faaac6dbfe069a92ab9e5c270fa43fc4e1761da", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/service-contracts": "^3.3", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/service-contracts": "^3.6", + "symfony/var-exporter": "^7.4|^8.0" }, "conflict": { - "ext-psr": "<1.1|>=2", - "symfony/config": "<6.4", - "symfony/finder": "<6.4", - "symfony/yaml": "<6.4" + "ext-psr": "<1.1|>=2" }, "provide": { "psr/container-implementation": "1.1|2.0", "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "symfony/config": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4099,7 +3891,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/v7.0.10" + "source": "https://github.com/symfony/dependency-injection/tree/v8.0.7" }, "funding": [ { @@ -4110,12 +3902,16 @@ "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-07-26T07:32:22+00:00" + "time": "2026-03-03T07:49:33+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4186,65 +3982,57 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "462b41a82739386c1b5c6304c572ce1790c57dd1" + "reference": "649eec3f9cc806e42ee2e7928d05425ed66108d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/462b41a82739386c1b5c6304c572ce1790c57dd1", - "reference": "462b41a82739386c1b5c6304c572ce1790c57dd1", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/649eec3f9cc806e42ee2e7928d05425ed66108d4", + "reference": "649eec3f9cc806e42ee2e7928d05425ed66108d4", "shasum": "" }, "require": { "doctrine/event-manager": "^2", - "doctrine/persistence": "^3.1", - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", + "doctrine/persistence": "^3.1|^4", + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "doctrine/dbal": "<3.6", + "doctrine/collections": "<1.8", + "doctrine/dbal": "<4.3", "doctrine/lexer": "<1.1", - "doctrine/orm": "<2.15", - "symfony/cache": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/form": "<6.4.6|>=7,<7.0.6", - "symfony/http-foundation": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/lock": "<6.4", - "symfony/messenger": "<6.4", - "symfony/property-info": "<6.4", - "symfony/security-bundle": "<6.4", - "symfony/security-core": "<6.4", - "symfony/validator": "<6.4" + "doctrine/orm": "<3.4", + "symfony/property-info": "<8.0" }, "require-dev": { - "doctrine/collections": "^1.0|^2.0", - "doctrine/data-fixtures": "^1.1", - "doctrine/dbal": "^3.6|^4", - "doctrine/orm": "^2.15|^3", + "doctrine/collections": "^1.8|^2.0", + "doctrine/data-fixtures": "^1.1|^2", + "doctrine/dbal": "^4.3", + "doctrine/orm": "^3.4", "psr/log": "^1|^2|^3", - "symfony/cache": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/doctrine-messenger": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/form": "^6.4.6|^7.0.6", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/security-core": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/uid": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/doctrine-messenger": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/type-info": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "symfony-bridge", "autoload": { @@ -4272,7 +4060,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v7.0.10" + "source": "https://github.com/symfony/doctrine-bridge/tree/v8.0.7" }, "funding": [ { @@ -4283,31 +4071,35 @@ "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-07-26T12:31:22+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/doctrine-messenger", - "version": "v7.0.9", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-messenger.git", - "reference": "2f47c6f2eb35b6a409aef6eeadec093c62e63ea4" + "reference": "88329a3faba5023cfb569b3fc5b8a771336c4a88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/2f47c6f2eb35b6a409aef6eeadec093c62e63ea4", - "reference": "2f47c6f2eb35b6a409aef6eeadec093c62e63ea4", + "url": "https://api.github.com/repos/symfony/doctrine-messenger/zipball/88329a3faba5023cfb569b3fc5b8a771336c4a88", + "reference": "88329a3faba5023cfb569b3fc5b8a771336c4a88", "shasum": "" }, "require": { - "doctrine/dbal": "^3.6|^4", - "php": ">=8.2", - "symfony/messenger": "^6.4|^7.0", + "doctrine/dbal": "^4.3", + "php": ">=8.4", + "symfony/messenger": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -4315,8 +4107,8 @@ }, "require-dev": { "doctrine/persistence": "^1.3|^2|^3", - "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" }, "type": "symfony-messenger-bridge", "autoload": { @@ -4344,7 +4136,7 @@ "description": "Symfony Doctrine Messenger Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-messenger/tree/v7.0.9" + "source": "https://github.com/symfony/doctrine-messenger/tree/v8.0.6" }, "funding": [ { @@ -4355,35 +4147,38 @@ "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-06-20T15:46:52+00:00" + "time": "2026-02-20T07:51:53+00:00" }, { "name": "symfony/dom-crawler", - "version": "v7.0.8", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "46cdbb48603db7b9ea55172d3edb6b3e9de58028" + "reference": "7f504fe7fb7fa5fee40a653104842cf6f851a6d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/46cdbb48603db7b9ea55172d3edb6b3e9de58028", - "reference": "46cdbb48603db7b9ea55172d3edb6b3e9de58028", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/7f504fe7fb7fa5fee40a653104842cf6f851a6d8", + "reference": "7f504fe7fb7fa5fee40a653104842cf6f851a6d8", "shasum": "" }, "require": { - "masterminds/html5": "^2.6", - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.0" }, "require-dev": { - "symfony/css-selector": "^6.4|^7.0" + "symfony/css-selector": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4411,7 +4206,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.0.8" + "source": "https://github.com/symfony/dom-crawler/tree/v8.0.6" }, "funding": [ { @@ -4422,37 +4217,37 @@ "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-05-31T14:55:39+00:00" + "time": "2026-02-17T13:07:04+00:00" }, { "name": "symfony/dotenv", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "3a0c9ce95ed71a1ad297604742a46085e808c7a3" + "reference": "23bd13cf3f6cca8b7661548ef958ff4f4aa7c458" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/3a0c9ce95ed71a1ad297604742a46085e808c7a3", - "reference": "3a0c9ce95ed71a1ad297604742a46085e808c7a3", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/23bd13cf3f6cca8b7661548ef958ff4f4aa7c458", + "reference": "23bd13cf3f6cca8b7661548ef958ff4f4aa7c458", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "conflict": { - "symfony/console": "<6.4", - "symfony/process": "<6.4" + "php": ">=8.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0" + "symfony/console": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4485,7 +4280,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v7.0.10" + "source": "https://github.com/symfony/dotenv/tree/v8.0.7" }, "funding": [ { @@ -4496,40 +4291,46 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-07-09T18:35:37+00:00" + "time": "2026-03-03T07:49:33+00:00" }, { "name": "symfony/error-handler", - "version": "v7.0.10", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "fd1cc26512b502c8fe8dfe90b67a9d8228bbafa2" + "reference": "7620b97ec0ab1d2d6c7fb737aa55da411bea776a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/fd1cc26512b502c8fe8dfe90b67a9d8228bbafa2", - "reference": "fd1cc26512b502c8fe8dfe90b67a9d8228bbafa2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/7620b97ec0ab1d2d6c7fb737aa55da411bea776a", + "reference": "7620b97ec0ab1d2d6c7fb737aa55da411bea776a", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^7.4|^8.0" }, "conflict": { - "symfony/deprecation-contracts": "<2.5", - "symfony/http-kernel": "<6.4" + "symfony/deprecation-contracts": "<2.5" }, "require-dev": { + "symfony/console": "^7.4|^8.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/http-kernel": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -4560,7 +4361,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/v7.0.10" + "source": "https://github.com/symfony/error-handler/tree/v8.0.4" }, "funding": [ { @@ -4571,33 +4372,37 @@ "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-07-26T12:31:22+00:00" + "time": "2026-01-23T11:07:10+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.0.8", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "5c30c7fc4ccf847e4dd8a18b6158cb1f77702550" + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5c30c7fc4ccf847e4dd8a18b6158cb1f77702550", - "reference": "5c30c7fc4ccf847e4dd8a18b6158cb1f77702550", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47", + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<6.4", + "symfony/security-http": "<7.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -4606,13 +4411,14 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/error-handler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4640,7 +4446,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/v7.0.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4" }, "funding": [ { @@ -4651,12 +4457,16 @@ "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-05-31T14:55:39+00:00" + "time": "2026-01-05T11:45:55+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -4736,21 +4546,21 @@ }, { "name": "symfony/expression-language", - "version": "v7.0.8", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "8e64bd4eb4c4dd180fc7de9c72011c49ebbdc822" + "reference": "83989cfbcf2ba08a400be339a88468b05ddc21c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/8e64bd4eb4c4dd180fc7de9c72011c49ebbdc822", - "reference": "8e64bd4eb4c4dd180fc7de9c72011c49ebbdc822", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/83989cfbcf2ba08a400be339a88468b05ddc21c9", + "reference": "83989cfbcf2ba08a400be339a88468b05ddc21c9", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/cache": "^6.4|^7.0", + "php": ">=8.4", + "symfony/cache": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -4779,7 +4589,7 @@ "description": "Provides an engine that can compile and evaluate expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/expression-language/tree/v7.0.8" + "source": "https://github.com/symfony/expression-language/tree/v8.0.4" }, "funding": [ { @@ -4790,34 +4600,38 @@ "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-05-31T14:55:39+00:00" + "time": "2026-01-05T09:27:50+00:00" }, { "name": "symfony/filesystem", - "version": "v7.0.9", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "f6b35b0de74a2577196114eef957f2414b5599d5" + "reference": "7bf9162d7a0dff98d079b72948508fa48018a770" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/f6b35b0de74a2577196114eef957f2414b5599d5", - "reference": "f6b35b0de74a2577196114eef957f2414b5599d5", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7bf9162d7a0dff98d079b72948508fa48018a770", + "reference": "7bf9162d7a0dff98d079b72948508fa48018a770", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4845,7 +4659,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.0.9" + "source": "https://github.com/symfony/filesystem/tree/v8.0.6" }, "funding": [ { @@ -4856,32 +4670,36 @@ "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-06-28T09:58:46+00:00" + "time": "2026-02-25T16:59:43+00:00" }, { "name": "symfony/finder", - "version": "v7.0.10", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "25b267662f297a8479bf6cf88fdc92e4b16cf24c" + "reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/25b267662f297a8479bf6cf88fdc92e4b16cf24c", - "reference": "25b267662f297a8479bf6cf88fdc92e4b16cf24c", + "url": "https://api.github.com/repos/symfony/finder/zipball/441404f09a54de6d1bd6ad219e088cdf4c91f97c", + "reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "symfony/filesystem": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4909,7 +4727,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.0.10" + "source": "https://github.com/symfony/finder/tree/v8.0.6" }, "funding": [ { @@ -4920,12 +4738,16 @@ "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-07-24T07:06:56+00:00" + "time": "2026-01-29T09:41:02+00:00" }, { "name": "symfony/flex", @@ -5002,55 +4824,50 @@ }, { "name": "symfony/form", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "2713488dbb290a6c0d043766b0e9d0b02672c935" + "reference": "954e17b053dad9fb227ebd90260752e3a46bb06a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/2713488dbb290a6c0d043766b0e9d0b02672c935", - "reference": "2713488dbb290a6c0d043766b0e9d0b02672c935", + "url": "https://api.github.com/repos/symfony/form/zipball/954e17b053dad9fb227ebd90260752e3a46bb06a", + "reference": "954e17b053dad9fb227ebd90260752e3a46bb06a", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/options-resolver": "^6.4|^7.0", - "symfony/polyfill-ctype": "~1.8", + "php": ">=8.4", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/options-resolver": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-intl-icu": "^1.21", - "symfony/polyfill-mbstring": "~1.0", - "symfony/property-access": "^6.4|^7.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/property-access": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/console": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<6.4", - "symfony/error-handler": "<6.4", - "symfony/framework-bundle": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/intl": "<7.4", "symfony/translation-contracts": "<2.5", - "symfony/twig-bridge": "<6.4" + "symfony/validator": "<7.4" }, "require-dev": { "doctrine/collections": "^1.0|^2.0", - "symfony/config": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/html-sanitizer": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", - "symfony/security-core": "^6.4|^7.0", - "symfony/security-csrf": "^6.4|^7.0", - "symfony/translation": "^6.4.3|^7.0.3", - "symfony/uid": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/html-sanitizer": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -5078,7 +4895,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v7.0.10" + "source": "https://github.com/symfony/form/tree/v8.0.7" }, "funding": [ { @@ -5089,114 +4906,108 @@ "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-07-19T08:29:37+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/framework-bundle", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "07a37173aace78420ccaf5eceaf4a79c7dfab375" + "reference": "6a43d76538d52d4b7660f07054a07f8346f73eae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/07a37173aace78420ccaf5eceaf4a79c7dfab375", - "reference": "07a37173aace78420ccaf5eceaf4a79c7dfab375", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/6a43d76538d52d4b7660f07054a07f8346f73eae", + "reference": "6a43d76538d52d4b7660f07054a07f8346f73eae", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.2", - "symfony/cache": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "php": ">=8.4", + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4.4|^8.0.4", + "symfony/dependency-injection": "^7.4.4|^8.0.4", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/filesystem": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/routing": "^6.4|^7.0" + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php85": "^1.32", + "symfony/routing": "^7.4|^8.0" }, "conflict": { "doctrine/persistence": "<1.3", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/asset": "<6.4", - "symfony/asset-mapper": "<6.4", - "symfony/clock": "<6.4", - "symfony/console": "<6.4", - "symfony/dom-crawler": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/form": "<6.4", - "symfony/http-client": "<6.4", - "symfony/lock": "<6.4", - "symfony/mailer": "<6.4", - "symfony/messenger": "<6.4", - "symfony/mime": "<6.4", - "symfony/property-access": "<6.4", - "symfony/property-info": "<6.4", - "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", - "symfony/security-core": "<6.4", - "symfony/security-csrf": "<6.4", - "symfony/serializer": "<6.4", - "symfony/stopwatch": "<6.4", - "symfony/translation": "<6.4", - "symfony/twig-bridge": "<6.4", - "symfony/twig-bundle": "<6.4", - "symfony/validator": "<6.4", - "symfony/web-profiler-bundle": "<6.4", - "symfony/workflow": "<6.4" + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/console": "<7.4", + "symfony/form": "<7.4", + "symfony/json-streamer": "<7.4", + "symfony/messenger": "<7.4", + "symfony/security-csrf": "<7.4", + "symfony/serializer": "<7.4", + "symfony/translation": "<7.4", + "symfony/webhook": "<7.4", + "symfony/workflow": "<7.4" }, "require-dev": { "doctrine/persistence": "^1.3|^2|^3", "dragonmantank/cron-expression": "^3.1", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", "seld/jsonlint": "^1.10", - "symfony/asset": "^6.4|^7.0", - "symfony/asset-mapper": "^6.4|^7.0", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/clock": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", - "symfony/dom-crawler": "^6.4|^7.0", - "symfony/dotenv": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/html-sanitizer": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/mailer": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/notifier": "^6.4|^7.0", - "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/scheduler": "^6.4.4|^7.0.4", - "symfony/security-bundle": "^6.4|^7.0", - "symfony/semaphore": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/string": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/twig-bundle": "^6.4|^7.0", - "symfony/uid": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/web-link": "^6.4|^7.0", - "symfony/workflow": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "symfony/asset": "^7.4|^8.0", + "symfony/asset-mapper": "^7.4|^8.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/dotenv": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/html-sanitizer": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/json-streamer": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/mailer": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/notifier": "^7.4|^8.0", + "symfony/object-mapper": "^7.4|^8.0", + "symfony/polyfill-intl-icu": "^1.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/scheduler": "^7.4|^8.0", + "symfony/security-bundle": "^7.4|^8.0", + "symfony/semaphore": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/string": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/twig-bundle": "^7.4|^8.0", + "symfony/type-info": "^7.4.1|^8.0.1", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/webhook": "^7.4|^8.0", + "symfony/workflow": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "symfony-bundle", "autoload": { @@ -5224,7 +5035,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/v7.0.10" + "source": "https://github.com/symfony/framework-bundle/tree/v8.0.7" }, "funding": [ { @@ -5235,36 +5046,40 @@ "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-07-26T13:24:26+00:00" + "time": "2026-03-06T15:40:00+00:00" }, { "name": "symfony/http-client", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "3ae495c67ba9c3b504fecd070a6c28b4143088cf" + "reference": "ade9bd433450382f0af154661fc8e72758b4de36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/3ae495c67ba9c3b504fecd070a6c28b4143088cf", - "reference": "3ae495c67ba9c3b504fecd070a6c28b4143088cf", + "url": "https://api.github.com/repos/symfony/http-client/zipball/ade9bd433450382f0af154661fc8e72758b4de36", + "reference": "ade9bd433450382f0af154661fc8e72758b4de36", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/log": "^1|^2|^3", - "symfony/http-client-contracts": "^3.4.1", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.4" + "amphp/amp": "<3", + "php-http/discovery": "<1.15" }, "provide": { "php-http/async-client-implementation": "*", @@ -5273,19 +5088,19 @@ "symfony/http-client-implementation": "3.0" }, "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", - "amphp/socket": "^1.1", + "amphp/http-client": "^5.3.2", + "amphp/http-tunnel": "^2.0", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -5316,7 +5131,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.0.10" + "source": "https://github.com/symfony/http-client/tree/v8.0.7" }, "funding": [ { @@ -5327,12 +5142,16 @@ "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-07-17T06:06:58+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/http-client-contracts", @@ -5414,36 +5233,35 @@ }, { "name": "symfony/http-foundation", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e7bb762b114f2f1e2610322e025709df08211f1d" + "reference": "c5ecf7b07408dbc4a87482634307654190954ae8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e7bb762b114f2f1e2610322e025709df08211f1d", - "reference": "e7bb762b114f2f1e2610322e025709df08211f1d", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c5ecf7b07408dbc4a87482634307654190954ae8", + "reference": "c5ecf7b07408dbc4a87482634307654190954ae8", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php83": "^1.27" + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.1" }, "conflict": { - "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4" + "doctrine/dbal": "<4.3" }, "require-dev": { - "doctrine/dbal": "^3.6|^4", + "doctrine/dbal": "^4.3", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0" + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -5471,7 +5289,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.0.10" + "source": "https://github.com/symfony/http-foundation/tree/v8.0.7" }, "funding": [ { @@ -5482,81 +5300,72 @@ "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-07-26T12:37:16+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "31cb30794c8bb944a4e1f6bb6aef95840b3345a7" + "reference": "c04721f45723d8ce049fa3eee378b5a505272ac7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/31cb30794c8bb944a4e1f6bb6aef95840b3345a7", - "reference": "31cb30794c8bb944a4e1f6bb6aef95840b3345a7", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/c04721f45723d8ce049fa3eee378b5a505272ac7", + "reference": "c04721f45723d8ce049fa3eee378b5a505272ac7", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/log": "^1|^2|^3", - "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/browser-kit": "<6.4", - "symfony/cache": "<6.4", - "symfony/config": "<6.4", - "symfony/console": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<6.4", - "symfony/form": "<6.4", - "symfony/http-client": "<6.4", + "symfony/flex": "<2.10", "symfony/http-client-contracts": "<2.5", - "symfony/mailer": "<6.4", - "symfony/messenger": "<6.4", - "symfony/translation": "<6.4", "symfony/translation-contracts": "<2.5", - "symfony/twig-bridge": "<6.4", - "symfony/validator": "<6.4", - "symfony/var-dumper": "<6.4", - "twig/twig": "<3.0.4" + "twig/twig": "<3.21" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/clock": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/dom-crawler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", - "symfony/serializer": "^6.4.4|^7.0.4", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0", - "symfony/var-exporter": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0", + "twig/twig": "^3.21" }, "type": "library", "autoload": { @@ -5584,7 +5393,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/v7.0.10" + "source": "https://github.com/symfony/http-kernel/tree/v8.0.7" }, "funding": [ { @@ -5595,41 +5404,44 @@ "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-07-26T14:56:00+00:00" + "time": "2026-03-06T16:58:46+00:00" }, { "name": "symfony/mercure", - "version": "v0.6.5", + "version": "v0.7.2", "source": { "type": "git", "url": "https://github.com/symfony/mercure.git", - "reference": "304cf84609ef645d63adc65fc6250292909a461b" + "reference": "3ba1d19c9792d6bf66cf6cb4412ea289e9a42565" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mercure/zipball/304cf84609ef645d63adc65fc6250292909a461b", - "reference": "304cf84609ef645d63adc65fc6250292909a461b", + "url": "https://api.github.com/repos/symfony/mercure/zipball/3ba1d19c9792d6bf66cf6cb4412ea289e9a42565", + "reference": "3ba1d19c9792d6bf66cf6cb4412ea289e9a42565", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/deprecation-contracts": "^2.0|^3.0|^4.0", - "symfony/http-client": "^4.4|^5.0|^6.0|^7.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0|^7.0", - "symfony/polyfill-php80": "^1.22", - "symfony/web-link": "^4.4|^5.0|^6.0|^7.0" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.0|^3.0", + "symfony/http-client": "^6.4|^7.3|^8.0", + "symfony/http-foundation": "^6.4|^7.3|^8.0", + "symfony/web-link": "^6.4|^7.3|^8.0" }, "require-dev": { "lcobucci/jwt": "^3.4|^4.0|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0|^7.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0|^7.0", - "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0|^7.0", + "symfony/event-dispatcher": "^6.4|^7.3|^8.0", + "symfony/http-kernel": "^6.4|^7.3|^8.0", + "symfony/phpunit-bridge": "^7.3.4|^8.0", + "symfony/stopwatch": "^6.4|^7.3|^8.0", "twig/twig": "^2.0|^3.0|^4.0" }, "suggest": { @@ -5674,7 +5486,7 @@ ], "support": { "issues": "https://github.com/symfony/mercure/issues", - "source": "https://github.com/symfony/mercure/tree/v0.6.5" + "source": "https://github.com/symfony/mercure/tree/v0.7.2" }, "funding": [ { @@ -5686,36 +5498,36 @@ "type": "tidelift" } ], - "time": "2024-04-08T12:51:34+00:00" + "time": "2025-12-15T15:22:09+00:00" }, { "name": "symfony/mercure-bundle", - "version": "v0.3.9", + "version": "v0.4.2", "source": { "type": "git", "url": "https://github.com/symfony/mercure-bundle.git", - "reference": "77435d740b228e9f5f3f065b6db564f85f2cdb64" + "reference": "eae8bf5a75b4e1203bd9aa4181c7950a4df4b3e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mercure-bundle/zipball/77435d740b228e9f5f3f065b6db564f85f2cdb64", - "reference": "77435d740b228e9f5f3f065b6db564f85f2cdb64", + "url": "https://api.github.com/repos/symfony/mercure-bundle/zipball/eae8bf5a75b4e1203bd9aa4181c7950a4df4b3e3", + "reference": "eae8bf5a75b4e1203bd9aa4181c7950a4df4b3e3", "shasum": "" }, "require": { "lcobucci/jwt": "^3.4|^4.0|^5.0", - "php": ">=7.1.3", - "symfony/config": "^4.4|^5.0|^6.0|^7.0", - "symfony/dependency-injection": "^4.4|^5.4|^6.0|^7.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0|^7.0", - "symfony/mercure": "^0.6.1", - "symfony/web-link": "^4.4|^5.0|^6.0|^7.0" + "php": ">=8.1", + "symfony/config": "^6.4|^7.3|^8.0", + "symfony/dependency-injection": "^6.4|^7.3|^8.0", + "symfony/http-kernel": "^6.4|^7.3|^8.0", + "symfony/mercure": "*", + "symfony/web-link": "^6.4|^7.3|^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.3.7|^5.0|^6.0|^7.0", - "symfony/stopwatch": "^4.3.7|^5.0|^6.0|^7.0", + "symfony/phpunit-bridge": "^7.3.4|^8.0", + "symfony/stopwatch": "^6.4|^7.3|^8.0", "symfony/ux-turbo": "*", - "symfony/var-dumper": "^4.3.7|^5.0|^6.0|^7.0" + "symfony/var-dumper": "^6.4|^7.3|^8.0" }, "suggest": { "symfony/messenger": "To use the Messenger integration" @@ -5755,7 +5567,7 @@ ], "support": { "issues": "https://github.com/symfony/mercure-bundle/issues", - "source": "https://github.com/symfony/mercure-bundle/tree/v0.3.9" + "source": "https://github.com/symfony/mercure-bundle/tree/v0.4.2" }, "funding": [ { @@ -5767,49 +5579,49 @@ "type": "tidelift" } ], - "time": "2024-05-31T09:07:18+00:00" + "time": "2025-11-25T12:51:49+00:00" }, { "name": "symfony/messenger", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "f3a9d96ac01e46813de63337dddbf9a81c9fad86" + "reference": "6ba5f08c156cfc95911dbb0da01a9bc390a70fd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/f3a9d96ac01e46813de63337dddbf9a81c9fad86", - "reference": "f3a9d96ac01e46813de63337dddbf9a81c9fad86", + "url": "https://api.github.com/repos/symfony/messenger/zipball/6ba5f08c156cfc95911dbb0da01a9bc390a70fd1", + "reference": "6ba5f08c156cfc95911dbb0da01a9bc390a70fd1", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/log": "^1|^2|^3", - "symfony/clock": "^6.4|^7.0" + "symfony/clock": "^7.4|^8.0" }, "conflict": { - "symfony/console": "<6.4", - "symfony/event-dispatcher": "<6.4", + "symfony/console": "<7.4", "symfony/event-dispatcher-contracts": "<2.5", - "symfony/framework-bundle": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/serializer": "<6.4" + "symfony/lock": "<7.4", + "symfony/serializer": "<7.4.4|>=8.0,<8.0.4" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/serializer": "^7.4.4|^8.0.4", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0" + "symfony/stopwatch": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -5837,7 +5649,7 @@ "description": "Helps applications send and receive messages to/from other applications or via message queues", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/messenger/tree/v7.0.10" + "source": "https://github.com/symfony/messenger/tree/v8.0.7" }, "funding": [ { @@ -5848,48 +5660,50 @@ "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-07-09T18:35:37+00:00" + "time": "2026-03-04T13:55:34+00:00" }, { "name": "symfony/mime", - "version": "v7.0.9", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "60757ea7d562ae1756c1f430a6f7872156a15f32" + "reference": "5d26d1958aeeba2ace8cc64a3a93d4f5d8f8022b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/60757ea7d562ae1756c1f430a6f7872156a15f32", - "reference": "60757ea7d562ae1756c1f430a6f7872156a15f32", + "url": "https://api.github.com/repos/symfony/mime/zipball/5d26d1958aeeba2ace8cc64a3a93d4f5d8f8022b", + "reference": "5d26d1958aeeba2ace8cc64a3a93d4f5d8f8022b", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<6.4", - "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/serializer": "^6.4.3|^7.0.3" + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -5921,7 +5735,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.0.9" + "source": "https://github.com/symfony/mime/tree/v8.0.7" }, "funding": [ { @@ -5932,46 +5746,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-06-28T09:58:46+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v7.0.8", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "d80b7aeabc539538c6ae8962259ac422632d7796" + "reference": "4dae5fe7f503c0e5ed304db684c3f0d95017e429" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/d80b7aeabc539538c6ae8962259ac422632d7796", - "reference": "d80b7aeabc539538c6ae8962259ac422632d7796", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/4dae5fe7f503c0e5ed304db684c3f0d95017e429", + "reference": "4dae5fe7f503c0e5ed304db684c3f0d95017e429", "shasum": "" }, "require": { "monolog/monolog": "^3", - "php": ">=8.2", - "symfony/http-kernel": "^6.4|^7.0", + "php": ">=8.4", + "symfony/http-kernel": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, - "conflict": { - "symfony/console": "<6.4", - "symfony/http-foundation": "<6.4", - "symfony/security-core": "<6.4" - }, "require-dev": { - "symfony/console": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/mailer": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/security-core": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/console": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/mailer": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "symfony-bridge", "autoload": { @@ -5999,7 +5812,7 @@ "description": "Provides integration for Monolog with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/monolog-bridge/tree/v7.0.8" + "source": "https://github.com/symfony/monolog-bridge/tree/v8.0.6" }, "funding": [ { @@ -6010,42 +5823,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-05-31T14:55:39+00:00" + "time": "2026-02-17T13:07:04+00:00" }, { "name": "symfony/monolog-bundle", - "version": "v3.11.1", + "version": "v4.0.1", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bundle.git", - "reference": "0e675a6e08f791ef960dc9c7e392787111a3f0c1" + "reference": "3b4ee2717ee56c5e1edb516140a175eb2a72bc66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/0e675a6e08f791ef960dc9c7e392787111a3f0c1", - "reference": "0e675a6e08f791ef960dc9c7e392787111a3f0c1", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/3b4ee2717ee56c5e1edb516140a175eb2a72bc66", + "reference": "3b4ee2717ee56c5e1edb516140a175eb2a72bc66", "shasum": "" }, "require": { "composer-runtime-api": "^2.0", - "monolog/monolog": "^1.25.1 || ^2.0 || ^3.0", - "php": ">=8.1", - "symfony/config": "^6.4 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.0", - "symfony/deprecation-contracts": "^2.5 || ^3.0", - "symfony/http-kernel": "^6.4 || ^7.0", - "symfony/monolog-bridge": "^6.4 || ^7.0", + "monolog/monolog": "^3.5", + "php": ">=8.2", + "symfony/config": "^7.3 || ^8.0", + "symfony/dependency-injection": "^7.3 || ^8.0", + "symfony/http-kernel": "^7.3 || ^8.0", + "symfony/monolog-bridge": "^7.3 || ^8.0", "symfony/polyfill-php84": "^1.30" }, "require-dev": { - "symfony/console": "^6.4 || ^7.0", - "symfony/phpunit-bridge": "^7.3.3", - "symfony/yaml": "^6.4 || ^7.0" + "phpunit/phpunit": "^11.5.41 || ^12.3", + "symfony/console": "^7.3 || ^8.0", + "symfony/yaml": "^7.3 || ^8.0" }, "type": "symfony-bundle", "autoload": { @@ -6075,7 +5891,7 @@ ], "support": { "issues": "https://github.com/symfony/monolog-bundle/issues", - "source": "https://github.com/symfony/monolog-bundle/tree/v3.11.1" + "source": "https://github.com/symfony/monolog-bundle/tree/v4.0.1" }, "funding": [ { @@ -6095,24 +5911,24 @@ "type": "tidelift" } ], - "time": "2025-12-08T07:58:26+00:00" + "time": "2025-12-08T08:00:13+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.0.8", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "19eecfc6f1b0e4b093db7f4a71eedc91843e711a" + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/19eecfc6f1b0e4b093db7f4a71eedc91843e711a", - "reference": "19eecfc6f1b0e4b093db7f4a71eedc91843e711a", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7", + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -6146,7 +5962,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.0.8" + "source": "https://github.com/symfony/options-resolver/tree/v8.0.0" }, "funding": [ { @@ -6157,45 +5973,49 @@ "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-05-31T14:55:39+00:00" + "time": "2025-11-12T15:55:31+00:00" }, { "name": "symfony/panther", - "version": "v2.2.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/panther.git", - "reference": "b7e0f834c9046918972edb3dde2ecc4a20f6155e" + "reference": "2d810395942e71aea2f7ea8e8b5f82326bb4b8b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/panther/zipball/b7e0f834c9046918972edb3dde2ecc4a20f6155e", - "reference": "b7e0f834c9046918972edb3dde2ecc4a20f6155e", + "url": "https://api.github.com/repos/symfony/panther/zipball/2d810395942e71aea2f7ea8e8b5f82326bb4b8b4", + "reference": "2d810395942e71aea2f7ea8e8b5f82326bb4b8b4", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", - "php": ">=8.0", + "php": ">=8.1", "php-webdriver/webdriver": "^1.8.2", - "symfony/browser-kit": "^5.4 || ^6.4 || ^7.0", - "symfony/dependency-injection": "^5.4 || ^6.4 || ^7.0", + "symfony/browser-kit": "^6.4 || ^7.3 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.3 || ^8.0", "symfony/deprecation-contracts": "^2.4 || ^3", - "symfony/dom-crawler": "^5.4 || ^6.4 || ^7.0", - "symfony/http-client": "^6.4 || ^7.0", - "symfony/http-kernel": "^5.4 || ^6.4 || ^7.0", - "symfony/process": "^5.4 || ^6.4 || ^7.0" + "symfony/dom-crawler": "^6.4 || ^7.3 || ^8.0", + "symfony/http-client": "^6.4 || ^7.0 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.3 || ^8.0", + "symfony/process": "^6.4 || ^7.3 || ^8.0" }, "require-dev": { - "symfony/css-selector": "^5.4 || ^6.4 || ^7.0", - "symfony/framework-bundle": "^5.4 || ^6.4 || ^7.0", - "symfony/mime": "^5.4 || ^6.4 || ^7.0", - "symfony/phpunit-bridge": "^7.2.0" + "symfony/css-selector": "^6.4 || ^7.3 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.3 || ^8.0", + "symfony/mime": "^6.4 || ^7.3 || ^8.0", + "symfony/phpunit-bridge": ">=7.3.4" }, "type": "library", "extra": { @@ -6215,8 +6035,8 @@ "authors": [ { "name": "Kévin Dunglas", - "email": "dunglas@gmail.com", - "homepage": "https://dunglas.fr" + "email": "kevin@dunglas.dev", + "homepage": "https://dunglas.dev" }, { "name": "Symfony Community", @@ -6224,7 +6044,7 @@ } ], "description": "A browser testing and web scraping library for PHP and Symfony.", - "homepage": "https://dunglas.fr", + "homepage": "https://symfony.com/packages/Panther", "keywords": [ "e2e", "scraping", @@ -6235,7 +6055,7 @@ ], "support": { "issues": "https://github.com/symfony/panther/issues", - "source": "https://github.com/symfony/panther/tree/v2.2.0" + "source": "https://github.com/symfony/panther/tree/v2.4.0" }, "funding": [ { @@ -6251,31 +6071,28 @@ "type": "tidelift" } ], - "time": "2025-01-30T13:11:55+00:00" + "time": "2026-01-08T05:29:21+00:00" }, { "name": "symfony/password-hasher", - "version": "v7.0.8", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "25c66dba8ca72c9636b16e9a4b33d18554969a3f" + "reference": "ff98a0be88030c5f4ba800414f911678cf9dad9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/25c66dba8ca72c9636b16e9a4b33d18554969a3f", - "reference": "25c66dba8ca72c9636b16e9a4b33d18554969a3f", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/ff98a0be88030c5f4ba800414f911678cf9dad9a", + "reference": "ff98a0be88030c5f4ba800414f911678cf9dad9a", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "conflict": { - "symfony/security-core": "<6.4" + "php": ">=8.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0", - "symfony/security-core": "^6.4|^7.0" + "symfony/console": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -6307,7 +6124,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v7.0.8" + "source": "https://github.com/symfony/password-hasher/tree/v8.0.6" }, "funding": [ { @@ -6318,12 +6135,16 @@ "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-05-31T14:55:39+00:00" + "time": "2026-02-13T09:57:13+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -6752,86 +6573,6 @@ ], "time": "2024-12-23T08:48:59+00:00" }, - { - "name": "symfony/polyfill-php83", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-07-08T02:45:35+00:00" - }, { "name": "symfony/polyfill-php84", "version": "v1.33.0", @@ -6913,21 +6654,101 @@ "time": "2025-06-24T13:30:11+00:00" }, { - "name": "symfony/process", - "version": "v7.0.8", + "name": "symfony/polyfill-php85", + "version": "v1.33.0", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "a358943d5a15277fc6001f47541c08b7d815338f" + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/a358943d5a15277fc6001f47541c08b7d815338f", - "reference": "a358943d5a15277fc6001f47541c08b7d815338f", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "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.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, + { + "name": "symfony/process", + "version": "v8.0.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674", + "reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674", + "shasum": "" + }, + "require": { + "php": ">=8.4" }, "type": "library", "autoload": { @@ -6955,7 +6776,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.0.8" + "source": "https://github.com/symfony/process/tree/v8.0.5" }, "funding": [ { @@ -6966,33 +6787,38 @@ "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-05-31T14:55:39+00:00" + "time": "2026-01-26T15:08:38+00:00" }, { "name": "symfony/property-access", - "version": "v7.0.8", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "ca11e9661ea88664873dba66412525fe301dd744" + "reference": "a35a5ec85b605d0d1a9fd802cb44d87682c746fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/ca11e9661ea88664873dba66412525fe301dd744", - "reference": "ca11e9661ea88664873dba66412525fe301dd744", + "url": "https://api.github.com/repos/symfony/property-access/zipball/a35a5ec85b605d0d1a9fd802cb44d87682c746fd", + "reference": "a35a5ec85b605d0d1a9fd802cb44d87682c746fd", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/property-info": "^6.4|^7.0" + "php": ">=8.4", + "symfony/property-info": "^7.4.4|^8.0.4" }, "require-dev": { - "symfony/cache": "^6.4|^7.0" + "symfony/cache": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7031,7 +6857,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.0.8" + "source": "https://github.com/symfony/property-access/tree/v8.0.4" }, "funding": [ { @@ -7042,43 +6868,46 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-05-31T14:55:39+00:00" + "time": "2026-01-05T09:27:50+00:00" }, { "name": "symfony/property-info", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "ee413c259f1af416e306709ef699102afd56c27a" + "reference": "e1a6b5d10ee3455ae698c4a3f4ef580b78af27ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/ee413c259f1af416e306709ef699102afd56c27a", - "reference": "ee413c259f1af416e306709ef699102afd56c27a", + "url": "https://api.github.com/repos/symfony/property-info/zipball/e1a6b5d10ee3455ae698c4a3f4ef580b78af27ba", + "reference": "e1a6b5d10ee3455ae698c4a3f4ef580b78af27ba", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/string": "^6.4|^7.0" + "php": ">=8.4", + "symfony/string": "^7.4|^8.0", + "symfony/type-info": "^7.4.7|^8.0.7" }, "conflict": { - "phpdocumentor/reflection-docblock": "<5.2", - "phpdocumentor/type-resolver": "<1.5.1", - "symfony/dependency-injection": "<6.4", - "symfony/serializer": "<6.4" + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1" }, "require-dev": { - "phpdocumentor/reflection-docblock": "^5.2", - "phpstan/phpdoc-parser": "^1.0", - "symfony/cache": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7114,7 +6943,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.0.10" + "source": "https://github.com/symfony/property-info/tree/v8.0.7" }, "funding": [ { @@ -7125,43 +6954,42 @@ "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-07-26T07:32:22+00:00" + "time": "2026-03-04T15:54:04+00:00" }, { "name": "symfony/routing", - "version": "v7.0.10", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "ed9fe56db2eb080b4fc1ecea9d66277ef6d1fb8a" + "reference": "053c40fd46e1d19c5c5a94cada93ce6c3facdd55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/ed9fe56db2eb080b4fc1ecea9d66277ef6d1fb8a", - "reference": "ed9fe56db2eb080b4fc1ecea9d66277ef6d1fb8a", + "url": "https://api.github.com/repos/symfony/routing/zipball/053c40fd46e1d19c5c5a94cada93ce6c3facdd55", + "reference": "053c40fd46e1d19c5c5a94cada93ce6c3facdd55", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/deprecation-contracts": "^2.5|^3" }, - "conflict": { - "symfony/config": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/yaml": "<6.4" - }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7195,7 +7023,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.0.10" + "source": "https://github.com/symfony/routing/tree/v8.0.6" }, "funding": [ { @@ -7206,40 +7034,44 @@ "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-07-17T06:06:58+00:00" + "time": "2026-02-25T16:59:43+00:00" }, { "name": "symfony/runtime", - "version": "v7.0.8", + "version": "v8.0.1", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "ea34522c447dd91a2b31cb330ee4540a56ba53f6" + "reference": "73b34037b23db051048ba2873031ddb89be9f19d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/ea34522c447dd91a2b31cb330ee4540a56ba53f6", - "reference": "ea34522c447dd91a2b31cb330ee4540a56ba53f6", + "url": "https://api.github.com/repos/symfony/runtime/zipball/73b34037b23db051048ba2873031ddb89be9f19d", + "reference": "73b34037b23db051048ba2873031ddb89be9f19d", "shasum": "" }, "require": { "composer-plugin-api": "^1.0|^2.0", - "php": ">=8.2" + "php": ">=8.4" }, "conflict": { - "symfony/dotenv": "<6.4" + "symfony/error-handler": "<7.4" }, "require-dev": { "composer/composer": "^2.6", - "symfony/console": "^6.4|^7.0", - "symfony/dotenv": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0" + "symfony/console": "^7.4|^8.0", + "symfony/dotenv": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0" }, "type": "composer-plugin", "extra": { @@ -7274,7 +7106,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v7.0.8" + "source": "https://github.com/symfony/runtime/tree/v8.0.1" }, "funding": [ { @@ -7285,39 +7117,44 @@ "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-05-31T14:55:39+00:00" + "time": "2025-12-05T14:08:45+00:00" }, { "name": "symfony/scheduler", - "version": "v7.0.8", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/scheduler.git", - "reference": "91a0c028f2183b111e92e32061bb9db9a9599133" + "reference": "51125aec4e370172fa1ba7ce2e8cf9d94a230f69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/scheduler/zipball/91a0c028f2183b111e92e32061bb9db9a9599133", - "reference": "91a0c028f2183b111e92e32061bb9db9a9599133", + "url": "https://api.github.com/repos/symfony/scheduler/zipball/51125aec4e370172fa1ba7ce2e8cf9d94a230f69", + "reference": "51125aec4e370172fa1ba7ce2e8cf9d94a230f69", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/clock": "^6.4|^7.0" + "php": ">=8.4", + "symfony/clock": "^7.4|^8.0" }, "require-dev": { "dragonmantank/cron-expression": "^3.1", - "symfony/cache": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0" + "symfony/cache": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7354,7 +7191,7 @@ "scheduler" ], "support": { - "source": "https://github.com/symfony/scheduler/tree/v7.0.8" + "source": "https://github.com/symfony/scheduler/tree/v8.0.4" }, "funding": [ { @@ -7365,79 +7202,68 @@ "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-06-02T15:49:03+00:00" + "time": "2026-01-23T11:07:10+00:00" }, { "name": "symfony/security-bundle", - "version": "v7.0.10", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "c9a134cffcb4ca10bebe22a69f1322b2db3082e1" + "reference": "73ba33c215a5e4516c7045c26f6fec71e4ab5727" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/c9a134cffcb4ca10bebe22a69f1322b2db3082e1", - "reference": "c9a134cffcb4ca10bebe22a69f1322b2db3082e1", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/73ba33c215a5e4516c7045c26f6fec71e4ab5727", + "reference": "73ba33c215a5e4516c7045c26f6fec71e4ab5727", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=8.2", - "symfony/clock": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/password-hasher": "^6.4|^7.0", - "symfony/security-core": "^6.4|^7.0", - "symfony/security-csrf": "^6.4|^7.0", - "symfony/security-http": "^6.4|^7.0", + "php": ">=8.4", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/password-hasher": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/security-http": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, - "conflict": { - "symfony/browser-kit": "<6.4", - "symfony/console": "<6.4", - "symfony/framework-bundle": "<6.4", - "symfony/http-client": "<6.4", - "symfony/ldap": "<6.4", - "symfony/serializer": "<6.4", - "symfony/twig-bundle": "<6.4", - "symfony/validator": "<6.4" - }, "require-dev": { - "symfony/asset": "^6.4|^7.0", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", - "symfony/dom-crawler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/framework-bundle": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/ldap": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/twig-bridge": "^6.4|^7.0", - "symfony/twig-bundle": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0", - "twig/twig": "^3.0.4", - "web-token/jwt-checker": "^3.1", - "web-token/jwt-signature-algorithm-ecdsa": "^3.1", - "web-token/jwt-signature-algorithm-eddsa": "^3.1", - "web-token/jwt-signature-algorithm-hmac": "^3.1", - "web-token/jwt-signature-algorithm-none": "^3.1", - "web-token/jwt-signature-algorithm-rsa": "^3.1" + "symfony/asset": "^7.4|^8.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/ldap": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.0", + "symfony/twig-bundle": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0", + "web-token/jwt-library": "^3.3.2|^4.0" }, "type": "symfony-bundle", "autoload": { @@ -7465,7 +7291,7 @@ "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-bundle/tree/v7.0.10" + "source": "https://github.com/symfony/security-bundle/tree/v8.0.6" }, "funding": [ { @@ -7476,52 +7302,50 @@ "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-07-24T07:06:56+00:00" + "time": "2026-02-22T22:01:53+00:00" }, { "name": "symfony/security-core", - "version": "v7.0.10", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "6048754a72768c43419129d3e1c5b2cf7e349adc" + "reference": "c62565de41a136535ffa79a4db0373a7173b4d02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/6048754a72768c43419129d3e1c5b2cf7e349adc", - "reference": "6048754a72768c43419129d3e1c5b2cf7e349adc", + "url": "https://api.github.com/repos/symfony/security-core/zipball/c62565de41a136535ffa79a4db0373a7173b4d02", + "reference": "c62565de41a136535ffa79a4db0373a7173b4d02", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/event-dispatcher-contracts": "^2.5|^3", - "symfony/password-hasher": "^6.4|^7.0", + "symfony/password-hasher": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, - "conflict": { - "symfony/event-dispatcher": "<6.4", - "symfony/http-foundation": "<6.4", - "symfony/ldap": "<6.4", - "symfony/translation": "<6.4.3|>=7.0,<7.0.3", - "symfony/validator": "<6.4" - }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", "psr/container": "^1.1|^2.0", "psr/log": "^1|^2|^3", - "symfony/cache": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/ldap": "^6.4|^7.0", - "symfony/string": "^6.4|^7.0", - "symfony/translation": "^6.4.3|^7.0.3", - "symfony/validator": "^6.4|^7.0" + "symfony/cache": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/ldap": "^7.4|^8.0", + "symfony/string": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7549,7 +7373,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v7.0.10" + "source": "https://github.com/symfony/security-core/tree/v8.0.4" }, "funding": [ { @@ -7560,36 +7384,39 @@ "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-07-26T12:31:22+00:00" + "time": "2026-01-23T11:07:10+00:00" }, { "name": "symfony/security-csrf", - "version": "v7.0.8", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "40543b13b35999316d3f79a18d0d4075f30b8d9b" + "reference": "60efcc82a33a33df87dcdec3ce3d6915b88958fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/40543b13b35999316d3f79a18d0d4075f30b8d9b", - "reference": "40543b13b35999316d3f79a18d0d4075f30b8d9b", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/60efcc82a33a33df87dcdec3ce3d6915b88958fd", + "reference": "60efcc82a33a33df87dcdec3ce3d6915b88958fd", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/security-core": "^6.4|^7.0" - }, - "conflict": { - "symfony/http-foundation": "<6.4" + "php": ">=8.4", + "symfony/security-core": "^7.4|^8.0" }, "require-dev": { - "symfony/http-foundation": "^6.4|^7.0" + "psr/log": "^1|^2|^3", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7617,7 +7444,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v7.0.8" + "source": "https://github.com/symfony/security-csrf/tree/v8.0.6" }, "funding": [ { @@ -7628,55 +7455,55 @@ "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-05-31T14:55:39+00:00" + "time": "2026-02-13T09:57:13+00:00" }, { "name": "symfony/security-http", - "version": "v7.0.9", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "828b0ce72c7e178aa56c6694f1ba9593cac531d9" + "reference": "ff6cdab586fed68f1ebc2a2ed42ae0dffafada1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/828b0ce72c7e178aa56c6694f1ba9593cac531d9", - "reference": "828b0ce72c7e178aa56c6694f1ba9593cac531d9", + "url": "https://api.github.com/repos/symfony/security-http/zipball/ff6cdab586fed68f1ebc2a2ed42ae0dffafada1f", + "reference": "ff6cdab586fed68f1ebc2a2ed42ae0dffafada1f", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/security-core": "^6.4|^7.0", + "php": ">=8.4", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/security-core": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/clock": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/http-client-contracts": "<3.0", - "symfony/security-bundle": "<6.4", - "symfony/security-csrf": "<6.4" + "symfony/http-client-contracts": "<3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/cache": "^6.4|^7.0", - "symfony/clock": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", "symfony/http-client-contracts": "^3.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", - "symfony/security-csrf": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "web-token/jwt-checker": "^3.1", - "web-token/jwt-signature-algorithm-ecdsa": "^3.1" + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "web-token/jwt-library": "^3.3.2|^4.0" }, "type": "library", "autoload": { @@ -7704,7 +7531,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v7.0.9" + "source": "https://github.com/symfony/security-http/tree/v8.0.6" }, "funding": [ { @@ -7715,63 +7542,65 @@ "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-06-22T11:38:48+00:00" + "time": "2026-02-17T13:07:04+00:00" }, { "name": "symfony/serializer", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "79b073ce21280bae2567cf1c48ae078dc3eeb01d" + "reference": "18bbaf7317e33e7e4bcd7ef281357ec4335fc900" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/79b073ce21280bae2567cf1c48ae078dc3eeb01d", - "reference": "79b073ce21280bae2567cf1c48ae078dc3eeb01d", + "url": "https://api.github.com/repos/symfony/serializer/zipball/18bbaf7317e33e7e4bcd7ef281357ec4335fc900", + "reference": "18bbaf7317e33e7e4bcd7ef281357ec4335fc900", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8" + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<6.4", - "symfony/property-access": "<6.4", - "symfony/property-info": "<6.4", - "symfony/uid": "<6.4", - "symfony/validator": "<6.4", - "symfony/yaml": "<6.4" + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/property-info": "<7.4", + "symfony/type-info": "<7.4" }, "require-dev": { - "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", "seld/jsonlint": "^1.10", - "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/error-handler": "^6.4|^7.0", - "symfony/filesystem": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/filesystem": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^6.4|^7.0", - "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0", - "symfony/var-exporter": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "symfony/type-info": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-exporter": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -7799,7 +7628,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.0.10" + "source": "https://github.com/symfony/serializer/tree/v8.0.7" }, "funding": [ { @@ -7810,12 +7639,16 @@ "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-07-17T06:06:58+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/service-contracts", @@ -7906,16 +7739,16 @@ }, { "name": "symfony/stimulus-bundle", - "version": "v2.32.0", + "version": "v2.34.0", "source": { "type": "git", "url": "https://github.com/symfony/stimulus-bundle.git", - "reference": "dfbf6b443bb381cb611e06f64dc23603b614b575" + "reference": "d610a2e021cf63f955838b4bfe40da7e4cafe850" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/dfbf6b443bb381cb611e06f64dc23603b614b575", - "reference": "dfbf6b443bb381cb611e06f64dc23603b614b575", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/d610a2e021cf63f955838b4bfe40da7e4cafe850", + "reference": "d610a2e021cf63f955838b4bfe40da7e4cafe850", "shasum": "" }, "require": { @@ -7955,7 +7788,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/stimulus-bundle/tree/v2.32.0" + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.34.0" }, "funding": [ { @@ -7975,24 +7808,24 @@ "type": "tidelift" } ], - "time": "2025-12-02T07:12:06+00:00" + "time": "2026-03-21T22:29:11+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.0.8", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "e4a0d6fef3dd428ca23172e62d1d863f6f25d541" + "reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/e4a0d6fef3dd428ca23172e62d1d863f6f25d541", - "reference": "e4a0d6fef3dd428ca23172e62d1d863f6f25d541", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/67df1914c6ccd2d7b52f70d40cf2aea02159d942", + "reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -8021,7 +7854,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.0.8" + "source": "https://github.com/symfony/stopwatch/tree/v8.0.0" }, "funding": [ { @@ -8032,43 +7865,47 @@ "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-05-31T14:55:39+00:00" + "time": "2025-08-04T07:36:47+00:00" }, { "name": "symfony/string", - "version": "v7.0.10", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "a1ac40b358a5e45c2b6c32ec9b183828c1dcf5d6" + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/a1ac40b358a5e45c2b6c32ec9b183828c1dcf5d6", - "reference": "a1ac40b358a5e45c2b6c32ec9b183828c1dcf5d6", + "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4", + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -8107,7 +7944,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.0.10" + "source": "https://github.com/symfony/string/tree/v8.0.6" }, "funding": [ { @@ -8118,12 +7955,16 @@ "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-07-22T10:25:05+00:00" + "time": "2026-02-09T10:14:57+00:00" }, { "name": "symfony/translation-contracts", @@ -8209,67 +8050,62 @@ }, { "name": "symfony/twig-bridge", - "version": "v7.0.8", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "c8e05d7545962198df715d705c132de0674dc5b2" + "reference": "e0539400f53d8305945c06eba7e8df007402f5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/c8e05d7545962198df715d705c132de0674dc5b2", - "reference": "c8e05d7545962198df715d705c132de0674dc5b2", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/e0539400f53d8305945c06eba7e8df007402f5e2", + "reference": "e0539400f53d8305945c06eba7e8df007402f5e2", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.0.4" + "twig/twig": "^3.21" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<6.4", - "symfony/form": "<6.4", - "symfony/http-foundation": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/mime": "<6.4", - "symfony/serializer": "<6.4", - "symfony/translation": "<6.4", - "symfony/workflow": "<6.4" + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/form": "<7.4.4|>8.0,<8.0.4" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^6.4|^7.0", - "symfony/asset-mapper": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/html-sanitizer": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/asset": "^7.4|^8.0", + "symfony/asset-mapper": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/form": "^7.4.4|^8.0.4", + "symfony/html-sanitizer": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/polyfill-intl-icu": "^1.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^6.4|^7.0", - "symfony/security-csrf": "^6.4|^7.0", - "symfony/security-http": "^6.4|^7.0", - "symfony/serializer": "^6.4.3|^7.0.3", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/web-link": "^6.4|^7.0", - "symfony/workflow": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0", - "twig/cssinliner-extra": "^2.12|^3", - "twig/inky-extra": "^2.12|^3", - "twig/markdown-extra": "^2.12|^3" + "symfony/security-core": "^7.4|^8.0", + "symfony/security-csrf": "^7.4|^8.0", + "symfony/security-http": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/workflow": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0", + "twig/cssinliner-extra": "^3", + "twig/inky-extra": "^3", + "twig/markdown-extra": "^3" }, "type": "symfony-bridge", "autoload": { @@ -8297,7 +8133,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.0.8" + "source": "https://github.com/symfony/twig-bridge/tree/v8.0.7" }, "funding": [ { @@ -8308,52 +8144,52 @@ "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-05-31T14:55:39+00:00" + "time": "2026-03-04T15:37:12+00:00" }, { "name": "symfony/twig-bundle", - "version": "v7.0.8", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "a90e474bc260e59bed98a556db63673e6420a0be" + "reference": "5a68f2e0e06996514bf04900c3982b93b42487af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/a90e474bc260e59bed98a556db63673e6420a0be", - "reference": "a90e474bc260e59bed98a556db63673e6420a0be", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/5a68f2e0e06996514bf04900c3982b93b42487af", + "reference": "5a68f2e0e06996514bf04900c3982b93b42487af", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", - "php": ">=8.2", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/twig-bridge": "^6.4|^7.0", - "twig/twig": "^3.0.4" - }, - "conflict": { - "symfony/framework-bundle": "<6.4", - "symfony/translation": "<6.4" + "php": ">=8.4", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.0" }, "require-dev": { - "symfony/asset": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/framework-bundle": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/web-link": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "symfony/asset": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/form": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/web-link": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "symfony-bundle", "autoload": { @@ -8381,7 +8217,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v7.0.8" + "source": "https://github.com/symfony/twig-bundle/tree/v8.0.4" }, "funding": [ { @@ -8392,57 +8228,147 @@ "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-05-31T14:55:39+00:00" + "time": "2026-01-06T12:43:21+00:00" }, { - "name": "symfony/ux-live-component", - "version": "v2.25.2", + "name": "symfony/type-info", + "version": "v8.0.7", "source": { "type": "git", - "url": "https://github.com/symfony/ux-live-component.git", - "reference": "79e8cc179eb21119547c492ae21e4bf529ac1a15" + "url": "https://github.com/symfony/type-info.git", + "reference": "3c7de103dd6cb68be24e155838a64ef4a70ae195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-live-component/zipball/79e8cc179eb21119547c492ae21e4bf529ac1a15", - "reference": "79e8cc179eb21119547c492ae21e4bf529ac1a15", + "url": "https://api.github.com/repos/symfony/type-info/zipball/3c7de103dd6cb68be24e155838a64ef4a70ae195", + "reference": "3c7de103dd6cb68be24e155838a64ef4a70ae195", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v8.0.7" + }, + "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-03-04T13:55:34+00:00" + }, + { + "name": "symfony/ux-live-component", + "version": "v2.34.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-live-component.git", + "reference": "f246c189192121781c267e26a64ff6942ef61ab6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-live-component/zipball/f246c189192121781c267e26a64ff6942ef61ab6", + "reference": "f246c189192121781c267e26a64ff6942ef61ab6", "shasum": "" }, "require": { "php": ">=8.1", "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/property-access": "^5.4.5|^6.0|^7.0", - "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4.5|^6.0|^7.0|^8.0", + "symfony/property-info": "^5.4|^6.0|^7.0|^8.0", "symfony/stimulus-bundle": "^2.9", - "symfony/ux-twig-component": "^2.25.1", + "symfony/ux-twig-component": "^2.33.0", "twig/twig": "^3.10.3" }, "conflict": { - "symfony/config": "<5.4.0" + "symfony/config": "<5.4.0", + "symfony/property-info": "~7.0.0", + "symfony/type-info": "<7.2" }, "require-dev": { - "doctrine/annotations": "^1.0", + "doctrine/annotations": "^1.0|^2.0", "doctrine/collections": "^1.6.8|^2.0", - "doctrine/doctrine-bundle": "^2.4.3", - "doctrine/orm": "^2.9.4", - "doctrine/persistence": "^2.5.2|^3.0", - "phpdocumentor/reflection-docblock": "5.x-dev", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/form": "^5.4|^6.0|^7.0", - "symfony/framework-bundle": "^5.4|^6.0|^7.0", - "symfony/options-resolver": "^5.4|^6.0|^7.0", - "symfony/phpunit-bridge": "^6.1|^7.0", - "symfony/security-bundle": "^5.4|^6.0|^7.0", - "symfony/serializer": "^5.4|^6.0|^7.0", - "symfony/twig-bundle": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "symfony/validator": "^5.4|^6.0|^7.0", + "doctrine/doctrine-bundle": "^2.4.3|^3.0|^4.0", + "doctrine/orm": "^2.9.4|^3.0", + "doctrine/persistence": "^2.5.2|^3.0|^4.0", + "phpdocumentor/reflection-docblock": "^5.6.2", + "symfony/config": "^6.3|^7.0|^8.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0|^8.0", + "symfony/expression-language": "^5.4|^6.0|^7.0|^8.0", + "symfony/form": "^5.4|^6.0|^7.0|^8.0", + "symfony/framework-bundle": "^5.4|^6.1|^7.0|^8.0", + "symfony/http-kernel": "^6.1|^7.0|^8.0", + "symfony/options-resolver": "^5.4|^6.0|^7.0|^8.0", + "symfony/phpunit-bridge": "^6.1|^7.0|^8.0", + "symfony/security-bundle": "^5.4|^6.0|^7.0|^8.0", + "symfony/serializer": "^5.4|^6.0|^7.0|^8.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0|^8.0", + "symfony/uid": "^5.4|^6.0|^7.0|^8.0", + "symfony/validator": "^5.4|^6.0|^7.0|^8.0", "zenstruck/browser": "^1.2.0", "zenstruck/foundry": "^2.0" }, @@ -8476,7 +8402,7 @@ "twig" ], "support": { - "source": "https://github.com/symfony/ux-live-component/tree/v2.25.2" + "source": "https://github.com/symfony/ux-live-component/tree/v2.34.0" }, "funding": [ { @@ -8487,25 +8413,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-05-19T11:54:27+00:00" + "time": "2026-03-21T22:29:11+00:00" }, { "name": "symfony/ux-react", - "version": "v2.32.0", + "version": "v2.34.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-react.git", - "reference": "e9b28032f593d3b2ff37044e194fb15ef9a2abfa" + "reference": "42ee2b86e3af8493e4a008ebe2af166c2c3d4d05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-react/zipball/e9b28032f593d3b2ff37044e194fb15ef9a2abfa", - "reference": "e9b28032f593d3b2ff37044e194fb15ef9a2abfa", + "url": "https://api.github.com/repos/symfony/ux-react/zipball/42ee2b86e3af8493e4a008ebe2af166c2c3d4d05", + "reference": "42ee2b86e3af8493e4a008ebe2af166c2c3d4d05", "shasum": "" }, "require": { @@ -8552,7 +8482,7 @@ "symfony-ux" ], "support": { - "source": "https://github.com/symfony/ux-react/tree/v2.32.0" + "source": "https://github.com/symfony/ux-react/tree/v2.34.0" }, "funding": [ { @@ -8572,20 +8502,20 @@ "type": "tidelift" } ], - "time": "2025-12-02T07:12:06+00:00" + "time": "2026-03-21T22:29:11+00:00" }, { "name": "symfony/ux-turbo", - "version": "v2.32.0", + "version": "v2.34.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-turbo.git", - "reference": "0deaa8abef20933d11f8bbe9899d950b4333ca1e" + "reference": "87511f621db238302a3bb819958a72feda27fc45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/0deaa8abef20933d11f8bbe9899d950b4333ca1e", - "reference": "0deaa8abef20933d11f8bbe9899d950b4333ca1e", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/87511f621db238302a3bb819958a72feda27fc45", + "reference": "87511f621db238302a3bb819958a72feda27fc45", "shasum": "" }, "require": { @@ -8655,7 +8585,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.32.0" + "source": "https://github.com/symfony/ux-turbo/tree/v2.34.0" }, "funding": [ { @@ -8675,20 +8605,20 @@ "type": "tidelift" } ], - "time": "2025-12-17T06:03:34+00:00" + "time": "2026-03-21T22:29:11+00:00" }, { "name": "symfony/ux-twig-component", - "version": "v2.32.0", + "version": "v2.34.0", "source": { "type": "git", "url": "https://github.com/symfony/ux-twig-component.git", - "reference": "0a300088327d1b766733fdcd81ae4a77852d6177" + "reference": "f9942e32246fe3fa9d31f60cffc1ada4d274830a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/0a300088327d1b766733fdcd81ae4a77852d6177", - "reference": "0a300088327d1b766733fdcd81ae4a77852d6177", + "url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/f9942e32246fe3fa9d31f60cffc1ada4d274830a", + "reference": "f9942e32246fe3fa9d31f60cffc1ada4d274830a", "shasum": "" }, "require": { @@ -8742,7 +8672,7 @@ "twig" ], "support": { - "source": "https://github.com/symfony/ux-twig-component/tree/v2.32.0" + "source": "https://github.com/symfony/ux-twig-component/tree/v2.34.0" }, "funding": [ { @@ -8762,57 +8692,53 @@ "type": "tidelift" } ], - "time": "2025-12-25T09:25:01+00:00" + "time": "2026-03-15T18:48:53+00:00" }, { "name": "symfony/validator", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "b3e4d838cdae9f2882402c2ad8018a27d469c075" + "reference": "04f7111e6f246d8211081fdc76e34b1298a9fc27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/b3e4d838cdae9f2882402c2ad8018a27d469c075", - "reference": "b3e4d838cdae9f2882402c2ad8018a27d469c075", + "url": "https://api.github.com/repos/symfony/validator/zipball/04f7111e6f246d8211081fdc76e34b1298a9fc27", + "reference": "04f7111e6f246d8211081fdc76e34b1298a9fc27", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php83": "^1.27", + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.0", "symfony/translation-contracts": "^2.5|^3" }, "conflict": { "doctrine/lexer": "<1.1", - "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<7.0", - "symfony/expression-language": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/intl": "<6.4", - "symfony/property-info": "<6.4", - "symfony/translation": "<6.4.3|>=7.0,<7.0.3", - "symfony/yaml": "<6.4" + "symfony/doctrine-bridge": "<7.4", + "symfony/expression-language": "<7.4" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", - "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/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/translation": "^6.4.3|^7.0.3", - "symfony/yaml": "^6.4|^7.0" + "symfony/cache": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/string": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/type-info": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -8841,7 +8767,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.0.10" + "source": "https://github.com/symfony/validator/tree/v8.0.7" }, "funding": [ { @@ -8852,41 +8778,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-07-26T12:31:22+00:00" + "time": "2026-03-06T13:17:40+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.0.10", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "3b5bed54f7c541aa0bf4cb0d3eb63b9e422ccb8b" + "reference": "2e14f7e0bf5ff02c6e63bd31cb8e4855a13d6209" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3b5bed54f7c541aa0bf4cb0d3eb63b9e422ccb8b", - "reference": "3b5bed54f7c541aa0bf4cb0d3eb63b9e422ccb8b", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2e14f7e0bf5ff02c6e63bd31cb8e4855a13d6209", + "reference": "2e14f7e0bf5ff02c6e63bd31cb8e4855a13d6209", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { - "symfony/console": "<6.4" + "symfony/console": "<7.4", + "symfony/error-handler": "<7.4" }, "require-dev": { - "ext-iconv": "*", - "symfony/console": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/uid": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "symfony/console": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "twig/twig": "^3.12" }, "bin": [ "Resources/bin/var-dump-server" @@ -8924,7 +8854,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.0.10" + "source": "https://github.com/symfony/var-dumper/tree/v8.0.6" }, "funding": [ { @@ -8935,34 +8865,38 @@ "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-07-26T12:31:22+00:00" + "time": "2026-02-15T10:53:29+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.0.9", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "18053b0e249c7303f9461f78d15cceae69c83b02" + "reference": "7345f46c251f2eb27c7b3ebdb5bb076b3ffcae04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/18053b0e249c7303f9461f78d15cceae69c83b02", - "reference": "18053b0e249c7303f9461f78d15cceae69c83b02", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/7345f46c251f2eb27c7b3ebdb5bb076b3ffcae04", + "reference": "7345f46c251f2eb27c7b3ebdb5bb076b3ffcae04", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "require-dev": { - "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -9000,7 +8934,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.0.9" + "source": "https://github.com/symfony/var-exporter/tree/v8.0.0" }, "funding": [ { @@ -9011,39 +8945,40 @@ "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-06-28T07:59:17+00:00" + "time": "2025-11-05T18:53:00+00:00" }, { "name": "symfony/web-link", - "version": "v7.0.8", + "version": "v8.0.4", "source": { "type": "git", "url": "https://github.com/symfony/web-link.git", - "reference": "270f39e3d1c7cc62b5a931fd6e9f2ea4e99ee9cf" + "reference": "0f79e9e89c4a8ecf4964cac25a12e152bcb23a99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-link/zipball/270f39e3d1c7cc62b5a931fd6e9f2ea4e99ee9cf", - "reference": "270f39e3d1c7cc62b5a931fd6e9f2ea4e99ee9cf", + "url": "https://api.github.com/repos/symfony/web-link/zipball/0f79e9e89c4a8ecf4964cac25a12e152bcb23a99", + "reference": "0f79e9e89c4a8ecf4964cac25a12e152bcb23a99", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "psr/link": "^1.1|^2.0" }, - "conflict": { - "symfony/http-kernel": "<6.4" - }, "provide": { "psr/link-implementation": "1.0|2.0" }, "require-dev": { - "symfony/http-kernel": "^6.4|^7.0" + "symfony/http-kernel": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -9083,7 +9018,7 @@ "push" ], "support": { - "source": "https://github.com/symfony/web-link/tree/v7.0.8" + "source": "https://github.com/symfony/web-link/tree/v8.0.4" }, "funding": [ { @@ -9094,12 +9029,16 @@ "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-05-31T14:55:39+00:00" + "time": "2026-01-01T23:07:29+00:00" }, { "name": "symfony/webpack-encore-bundle", @@ -9179,27 +9118,27 @@ }, { "name": "symfony/yaml", - "version": "v7.0.8", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "89bdddd79e918448ce978be664768ef27b9e5798" + "reference": "5f006c50a981e1630bbb70ad409c5d85f9a716e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/89bdddd79e918448ce978be664768ef27b9e5798", - "reference": "89bdddd79e918448ce978be664768ef27b9e5798", + "url": "https://api.github.com/repos/symfony/yaml/zipball/5f006c50a981e1630bbb70ad409c5d85f9a716e0", + "reference": "5f006c50a981e1630bbb70ad409c5d85f9a716e0", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<6.4" + "symfony/console": "<7.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0" + "symfony/console": "^7.4|^8.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -9230,7 +9169,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.0.8" + "source": "https://github.com/symfony/yaml/tree/v8.0.6" }, "funding": [ { @@ -9241,25 +9180,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-05-31T14:55:39+00:00" + "time": "2026-02-09T10:14:57+00:00" }, { "name": "twig/extra-bundle", - "version": "v3.23.0", + "version": "v3.24.0", "source": { "type": "git", "url": "https://github.com/twigphp/twig-extra-bundle.git", - "reference": "7a27e784dc56eddfef5e9295829b290ce06f1682" + "reference": "6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/7a27e784dc56eddfef5e9295829b290ce06f1682", - "reference": "7a27e784dc56eddfef5e9295829b290ce06f1682", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9", + "reference": "6a621fcb1f28aa9ea7b34a99047ae0cdf5b834c9", "shasum": "" }, "require": { @@ -9308,7 +9251,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.23.0" + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.24.0" }, "funding": [ { @@ -9320,20 +9263,20 @@ "type": "tidelift" } ], - "time": "2025-12-18T20:46:15+00:00" + "time": "2026-02-07T08:07:38+00:00" }, { "name": "twig/twig", - "version": "v3.23.0", + "version": "v3.24.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9" + "reference": "a6769aefb305efef849dc25c9fd1653358c148f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", - "reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6769aefb305efef849dc25c9fd1653358c148f0", + "reference": "a6769aefb305efef849dc25c9fd1653358c148f0", "shasum": "" }, "require": { @@ -9343,7 +9286,8 @@ "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "phpstan/phpstan": "^2.0", + "php-cs-fixer/shim": "^3.0@stable", + "phpstan/phpstan": "^2.0@stable", "psr/container": "^1.0|^2.0", "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, @@ -9387,7 +9331,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.23.0" + "source": "https://github.com/twigphp/Twig/tree/v3.24.0" }, "funding": [ { @@ -9399,20 +9343,20 @@ "type": "tidelift" } ], - "time": "2026-01-23T21:00:41+00:00" + "time": "2026-03-17T21:31:11+00:00" }, { "name": "vich/uploader-bundle", - "version": "v2.9.1", + "version": "v2.9.2", "source": { "type": "git", "url": "https://github.com/dustin10/VichUploaderBundle.git", - "reference": "945939a04a33c0b78c5fbb7ead31533d85112df5" + "reference": "4b88b23cb859a121413df4a831d960a46d6fe27f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dustin10/VichUploaderBundle/zipball/945939a04a33c0b78c5fbb7ead31533d85112df5", - "reference": "945939a04a33c0b78c5fbb7ead31533d85112df5", + "url": "https://api.github.com/repos/dustin10/VichUploaderBundle/zipball/4b88b23cb859a121413df4a831d960a46d6fe27f", + "reference": "4b88b23cb859a121413df4a831d960a46d6fe27f", "shasum": "" }, "require": { @@ -9505,9 +9449,9 @@ ], "support": { "issues": "https://github.com/dustin10/VichUploaderBundle/issues", - "source": "https://github.com/dustin10/VichUploaderBundle/tree/v2.9.1" + "source": "https://github.com/dustin10/VichUploaderBundle/tree/v2.9.2" }, - "time": "2025-12-10T08:23:38+00:00" + "time": "2026-03-12T08:18:50+00:00" }, { "name": "webmozart/assert", @@ -9835,83 +9779,6 @@ ], "time": "2024-11-12T16:29:46+00:00" }, - { - "name": "composer/semver", - "version": "3.4.4", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - } - ], - "time": "2025-08-20T19:15:30+00:00" - }, { "name": "composer/xdebug-handler", "version": "3.0.5", @@ -9980,34 +9847,36 @@ }, { "name": "dama/doctrine-test-bundle", - "version": "v8.2.2", + "version": "v8.4.1", "source": { "type": "git", "url": "https://github.com/dmaicher/doctrine-test-bundle.git", - "reference": "eefe54fdf00d910f808efea9cfce9cc261064a0a" + "reference": "d9f4fb01a43da2e279ca190fa25ab9e26f15a0c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/eefe54fdf00d910f808efea9cfce9cc261064a0a", - "reference": "eefe54fdf00d910f808efea9cfce9cc261064a0a", + "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/d9f4fb01a43da2e279ca190fa25ab9e26f15a0c8", + "reference": "d9f4fb01a43da2e279ca190fa25ab9e26f15a0c8", "shasum": "" }, "require": { "doctrine/dbal": "^3.3 || ^4.0", - "doctrine/doctrine-bundle": "^2.11.0", - "php": "^7.4 || ^8.0", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^5.4 || ^6.3 || ^7.0", - "symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0" + "doctrine/doctrine-bundle": "^2.11.0 || ^3.0", + "php": ">= 8.1", + "psr/cache": "^2.0 || ^3.0", + "symfony/cache": "^6.4 || ^7.3 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.3 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<10.0" }, "require-dev": { "behat/behat": "^3.0", "friendsofphp/php-cs-fixer": "^3.27", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0 || ^11.0", - "symfony/phpunit-bridge": "^7.2", - "symfony/process": "^5.4 || ^6.3 || ^7.0", - "symfony/yaml": "^5.4 || ^6.3 || ^7.0" + "phpunit/phpunit": "^10.5.57 || ^11.5.41|| ^12.3.14", + "symfony/dotenv": "^6.4 || ^7.3 || ^8.0", + "symfony/process": "^6.4 || ^7.3 || ^8.0" }, "type": "symfony-bundle", "extra": { @@ -10017,7 +9886,7 @@ }, "autoload": { "psr-4": { - "DAMA\\DoctrineTestBundle\\": "src/DAMA/DoctrineTestBundle" + "DAMA\\DoctrineTestBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -10041,9 +9910,9 @@ ], "support": { "issues": "https://github.com/dmaicher/doctrine-test-bundle/issues", - "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.2.2" + "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.4.1" }, - "time": "2025-02-04T14:37:36+00:00" + "time": "2025-12-07T21:48:15+00:00" }, { "name": "dbrekelmans/bdi", @@ -10150,23 +10019,22 @@ }, { "name": "doctrine/data-fixtures", - "version": "1.8.2", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "6fb221da56dae2011b33d47508e3b8aeb1d91db5" + "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/6fb221da56dae2011b33d47508e3b8aeb1d91db5", - "reference": "6fb221da56dae2011b33d47508e3b8aeb1d91db5", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/7a615ba135e45d67674bb623d90f34f6c7b6bd97", + "reference": "7a615ba135e45d67674bb623d90f34f6c7b6bd97", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3 || ^1.0", - "doctrine/persistence": "^2.0 || ^3.0", - "php": "^7.4 || ^8.0", - "symfony/polyfill-php80": "^1" + "doctrine/persistence": "^3.1 || ^4.0", + "php": "^8.1", + "psr/log": "^1.1 || ^2 || ^3" }, "conflict": { "doctrine/dbal": "<3.5 || >=5", @@ -10174,18 +10042,16 @@ "doctrine/phpcr-odm": "<1.3.0" }, "require-dev": { - "doctrine/annotations": "^1.12 || ^2", - "doctrine/coding-standard": "^13", + "doctrine/coding-standard": "^14", "doctrine/dbal": "^3.5 || ^4", "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", "doctrine/orm": "^2.14 || ^3", "ext-sqlite3": "*", "fig/log-test": "^1", - "phpstan/phpstan": "2.1.17", - "phpunit/phpunit": "^9.6.13 || 10.5.45", - "psr/log": "^1.1 || ^2 || ^3", - "symfony/cache": "^5.4 || ^6.3 || ^7", - "symfony/var-exporter": "^5.4 || ^6.3 || ^7" + "phpstan/phpstan": "2.1.31", + "phpunit/phpunit": "10.5.45 || 12.4.0", + "symfony/cache": "^6.4 || ^7", + "symfony/var-exporter": "^6.4 || ^7" }, "suggest": { "alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)", @@ -10216,7 +10082,7 @@ ], "support": { "issues": "https://github.com/doctrine/data-fixtures/issues", - "source": "https://github.com/doctrine/data-fixtures/tree/1.8.2" + "source": "https://github.com/doctrine/data-fixtures/tree/2.2.0" }, "funding": [ { @@ -10232,44 +10098,43 @@ "type": "tidelift" } ], - "time": "2025-06-10T07:00:05+00:00" + "time": "2025-10-17T20:06:20+00:00" }, { "name": "doctrine/doctrine-fixtures-bundle", - "version": "3.6.2", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineFixturesBundle.git", - "reference": "f44a224e27573b79140197a44e68484c45fb24da" + "reference": "9e013ed10d49bf7746b07204d336384a7d9b5a4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/f44a224e27573b79140197a44e68484c45fb24da", - "reference": "f44a224e27573b79140197a44e68484c45fb24da", + "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/9e013ed10d49bf7746b07204d336384a7d9b5a4d", + "reference": "9e013ed10d49bf7746b07204d336384a7d9b5a4d", "shasum": "" }, "require": { - "doctrine/data-fixtures": "^1.3", - "doctrine/doctrine-bundle": "^2.2", + "doctrine/data-fixtures": "^2.2", + "doctrine/doctrine-bundle": "^2.2 || ^3.0", "doctrine/orm": "^2.14.0 || ^3.0", - "doctrine/persistence": "^2.4|^3.0", - "php": "^7.4 || ^8.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|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0" + "doctrine/persistence": "^2.4 || ^3.0 || ^4.0", + "php": "^8.1", + "psr/log": "^2 || ^3", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/doctrine-bridge": "^6.4.16 || ^7.1.9 || ^8.0", + "symfony/http-kernel": "^6.4 || ^7.0 || ^8.0" }, "conflict": { "doctrine/dbal": "< 3" }, "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.10.39", - "phpunit/phpunit": "^9.6.13", - "symfony/phpunit-bridge": "^6.3.6", - "vimeo/psalm": "^5.15" + "doctrine/coding-standard": "14.0.0", + "phpstan/phpstan": "2.1.11", + "phpunit/phpunit": "^10.5.38 || 11.4.14" }, "type": "symfony-bundle", "autoload": { @@ -10303,7 +10168,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineFixturesBundle/issues", - "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/3.6.2" + "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/4.3.1" }, "funding": [ { @@ -10319,7 +10184,7 @@ "type": "tidelift" } ], - "time": "2024-11-13T07:41:29+00:00" + "time": "2025-12-03T16:05:42+00:00" }, { "name": "evenement/evenement", @@ -10494,57 +10359,58 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.66.0", + "version": "v3.94.2", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "5f5f2a142ff36b93c41885bca29cc5f861c013e6" + "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/5f5f2a142ff36b93c41885bca29cc5f861c013e6", - "reference": "5f5f2a142ff36b93c41885bca29cc5f861c013e6", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7787ceff91365ba7d623ec410b8f429cdebb4f63", + "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63", "shasum": "" }, "require": { - "clue/ndjson-react": "^1.0", + "clue/ndjson-react": "^1.3", "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.3", + "composer/xdebug-handler": "^3.0.5", "ext-filter": "*", + "ext-hash": "*", "ext-json": "*", "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.2", + "fidry/cpu-core-counter": "^1.3", "php": "^7.4 || ^8.0", - "react/child-process": "^0.6.5", - "react/event-loop": "^1.0", - "react/promise": "^2.0 || ^3.0", - "react/socket": "^1.0", - "react/stream": "^1.0", - "sebastian/diff": "^4.0 || ^5.0 || ^6.0", - "symfony/console": "^5.4 || ^6.0 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", - "symfony/finder": "^5.4 || ^6.0 || ^7.0", - "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", - "symfony/polyfill-mbstring": "^1.28", - "symfony/polyfill-php80": "^1.28", - "symfony/polyfill-php81": "^1.28", - "symfony/process": "^5.4 || ^6.0 || ^7.0 <7.2", - "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + "react/child-process": "^0.6.6", + "react/event-loop": "^1.5", + "react/socket": "^1.16", + "react/stream": "^1.4", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", + "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.33", + "symfony/polyfill-php80": "^1.33", + "symfony/polyfill-php81": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", + "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" }, "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.4", - "infection/infection": "^0.29.8", - "justinrainbow/json-schema": "^5.3 || ^6.0", - "keradus/cli-executor": "^2.1", + "facile-it/paraunit": "^1.3.1 || ^2.7.1", + "infection/infection": "^0.32.3", + "justinrainbow/json-schema": "^6.6.4", + "keradus/cli-executor": "^2.3", "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.7", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", - "phpunit/phpunit": "^9.6.21 || ^10.5.38 || ^11.4.3", - "symfony/var-dumper": "^5.4.47 || ^6.4.15 || ^7.1.8", - "symfony/yaml": "^5.4.45 || ^6.4.13 || ^7.1.6" + "php-coveralls/php-coveralls": "^2.9.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.7", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.7", + "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.51", + "symfony/polyfill-php85": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.4", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.1" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -10559,7 +10425,7 @@ "PhpCsFixer\\": "src/" }, "exclude-from-classmap": [ - "src/Fixer/Internal/*" + "src/**/Internal/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -10585,7 +10451,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.66.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.94.2" }, "funding": [ { @@ -10593,7 +10459,7 @@ "type": "github" } ], - "time": "2024-12-29T13:46:23+00:00" + "time": "2026-02-20T16:13:53+00:00" }, { "name": "mtdowling/jmespath.php", @@ -10723,30 +10589,37 @@ }, { "name": "nikic/php-parser", - "version": "v4.19.5", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", - "reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.1" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, "autoload": { "psr-4": { "PhpParser\\": "lib/PhpParser" @@ -10768,9 +10641,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.5" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-12-06T11:45:25+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "ondram/ci-detector", @@ -10852,32 +10725,44 @@ }, { "name": "pdepend/pdepend", - "version": "2.16.2", + "version": "3.x-dev", "source": { "type": "git", "url": "https://github.com/pdepend/pdepend.git", - "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" + "reference": "fca1f5fb1953df6713e35e06e10644e3caff7246" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", - "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/fca1f5fb1953df6713e35e06e10644e3caff7246", + "reference": "fca1f5fb1953df6713e35e06e10644e3caff7246", "shasum": "" }, "require": { - "php": ">=5.3.7", - "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0", - "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", - "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", - "symfony/polyfill-mbstring": "^1.19" + "fidry/cpu-core-counter": "^1.1", + "php": ">=8.1", + "php-64bit": "*", + "react/child-process": "^0.6.7", + "symfony/config": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.19", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "easy-doc/easy-doc": "0.0.0|^1.2.3", - "gregwar/rst": "^1.0", - "squizlabs/php_codesniffer": "^2.0.0" + "brianium/paratest": "^7.3", + "easy-doc/easy-doc": "^1.2.3", + "friendsofphp/php-cs-fixer": "^3.57", + "gregwar/rst": "^1.0.6", + "phpstan/phpstan": "~2.1.32", + "phpunit/phpunit": "^10.5.20,<10.5.32 || ^11.0.0", + "squizlabs/php_codesniffer": "^3.8.0" }, + "suggest": { + "ext-sockets": "Recommended on Windows for multiprocessing support" + }, + "default-branch": true, "bin": [ - "src/bin/pdepend" + "bin/pdepend" ], "type": "library", "extra": { @@ -10887,7 +10772,7 @@ }, "autoload": { "psr-4": { - "PDepend\\": "src/main/php/PDepend" + "PDepend\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -10903,7 +10788,7 @@ ], "support": { "issues": "https://github.com/pdepend/pdepend/issues", - "source": "https://github.com/pdepend/pdepend/tree/2.16.2" + "source": "https://github.com/pdepend/pdepend/tree/3.x" }, "funding": [ { @@ -10911,7 +10796,7 @@ "type": "tidelift" } ], - "time": "2023-12-17T18:09:59+00:00" + "time": "2026-02-10T15:29:38+00:00" }, { "name": "phar-io/manifest", @@ -11033,38 +10918,38 @@ }, { "name": "phparkitect/phparkitect", - "version": "0.3.33", + "version": "0.8.0", "source": { "type": "git", "url": "https://github.com/phparkitect/arkitect.git", - "reference": "aa15c864bef601d797e24f479852ec3f7d35cdd1" + "reference": "8097d801c1f76498d9fdd24e321c4a3b42b256f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phparkitect/arkitect/zipball/aa15c864bef601d797e24f479852ec3f7d35cdd1", - "reference": "aa15c864bef601d797e24f479852ec3f7d35cdd1", + "url": "https://api.github.com/repos/phparkitect/arkitect/zipball/8097d801c1f76498d9fdd24e321c4a3b42b256f8", + "reference": "8097d801c1f76498d9fdd24e321c4a3b42b256f8", "shasum": "" }, "require": { "ext-json": "*", - "nikic/php-parser": "~4", - "ondram/ci-detector": "^4.1", - "php": "^7.1|^8", - "phpstan/phpdoc-parser": "^1.2", - "symfony/console": "^3.0|^4.0|^5.0|^6.0|^7.0", - "symfony/event-dispatcher": "^3.0|^4.0|^5.0|^6.0|^7.0", - "symfony/finder": "^3.0|^4.0|^5.0|^6.0|^7.0", - "symfony/polyfill-php80": "^1.20", - "webmozart/assert": "^1.9" + "nikic/php-parser": "~5", + "ondram/ci-detector": "^4.2", + "php": "^7.4|^8", + "phpstan/phpdoc-parser": "^1.2|^2.0", + "symfony/console": "^3.0|^4.0|^5.0|^6.0|^7.0|^8.0", + "symfony/event-dispatcher": "^3.0|^4.0|^5.0|^6.0|^7.0|^8.0", + "symfony/finder": "^3.0|^4.0|^5.0|^6.0|^7.0|^8.0", + "symfony/polyfill-php80": "^1.33", + "webmozart/assert": "^1.12" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.0", + "friendsofphp/php-cs-fixer": "^3.92", "mikey179/vfsstream": "^1.6", "phpspec/prophecy": "^1.10", + "phpspec/prophecy-phpunit": "^2.4", "phpunit/phpunit": "^7.5|^9.0|^10.0", "roave/security-advisories": "dev-master", - "symfony/var-dumper": "^3.0|^4.0|^5.0|^6.0|^7.0", - "vimeo/psalm": "^4.6" + "symfony/var-dumper": "^3.0|^4.0|^5.0|^6.0|^7.0|^8.0" }, "bin": [ "bin-stub/phparkitect" @@ -11100,7 +10985,7 @@ "description": "Enforce architectural constraints in your PHP applications", "support": { "issues": "https://github.com/phparkitect/arkitect/issues", - "source": "https://github.com/phparkitect/arkitect/tree/0.3.33" + "source": "https://github.com/phparkitect/arkitect/tree/0.8.0" }, "funding": [ { @@ -11112,43 +10997,49 @@ "type": "github" } ], - "time": "2024-05-28T05:56:58+00:00" + "time": "2026-01-28T20:58:15+00:00" }, { "name": "phpmd/phpmd", - "version": "2.15.0", + "version": "3.x-dev", "source": { "type": "git", "url": "https://github.com/phpmd/phpmd.git", - "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" + "reference": "40b34162767e86b78f894124bd7c4cb03ff27ad4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", - "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/40b34162767e86b78f894124bd7c4cb03ff27ad4", + "reference": "40b34162767e86b78f894124bd7c4cb03ff27ad4", "shasum": "" }, "require": { - "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^3.0", "ext-xml": "*", - "pdepend/pdepend": "^2.16.1", - "php": ">=5.3.9" + "pdepend/pdepend": "3.x-dev#43ad2f3435cc94ecc8e114d2d18be7a46e5ad101", + "php": "^8.1", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/yaml": "^5.4.31 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { - "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "brianium/paratest": "^7.3", + "easy-doc/easy-doc": "^1.3.2", "ext-json": "*", "ext-simplexml": "*", + "friendsofphp/php-cs-fixer": "^3.57", "gregwar/rst": "^1.0", "mikey179/vfsstream": "^1.6.8", - "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" + "phpstan/phpstan": "~2.1.38", + "phpunit/phpunit": "^10.5.20,<10.5.32|^11.0.0", + "squizlabs/php_codesniffer": "^3.8.0" }, "bin": [ - "src/bin/phpmd" + "bin/phpmd" ], "type": "library", "autoload": { - "psr-0": { - "PHPMD\\": "src/main/php" + "psr-4": { + "PHPMD\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -11187,7 +11078,7 @@ "support": { "irc": "irc://irc.freenode.org/phpmd", "issues": "https://github.com/phpmd/phpmd/issues", - "source": "https://github.com/phpmd/phpmd/tree/2.15.0" + "source": "https://github.com/phpmd/phpmd/tree/3.x" }, "funding": [ { @@ -11195,7 +11086,7 @@ "type": "tidelift" } ], - "time": "2023-12-11T08:22:20+00:00" + "time": "2026-02-15T12:53:29+00:00" }, { "name": "phpunit/php-code-coverage", @@ -13108,31 +12999,32 @@ }, { "name": "symfony/maker-bundle", - "version": "v1.62.1", + "version": "v1.67.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "468ff2708200c95ebc0d85d3174b6c6711b8a590" + "reference": "6ce8b313845f16bcf385ee3cb31d8b24e30d5516" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/468ff2708200c95ebc0d85d3174b6c6711b8a590", - "reference": "468ff2708200c95ebc0d85d3174b6c6711b8a590", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/6ce8b313845f16bcf385ee3cb31d8b24e30d5516", + "reference": "6ce8b313845f16bcf385ee3cb31d8b24e30d5516", "shasum": "" }, "require": { + "composer-runtime-api": "^2.1", "doctrine/inflector": "^2.0", - "nikic/php-parser": "^4.18|^5.0", + "nikic/php-parser": "^5.0", "php": ">=8.1", - "symfony/config": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/deprecation-contracts": "^2.2|^3", - "symfony/filesystem": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/framework-bundle": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0" + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0" }, "conflict": { "doctrine/doctrine-bundle": "<2.10", @@ -13140,12 +13032,14 @@ }, "require-dev": { "composer/semver": "^3.0", - "doctrine/doctrine-bundle": "^2.5.0", + "doctrine/doctrine-bundle": "^2.10|^3.0", "doctrine/orm": "^2.15|^3", - "symfony/http-client": "^6.4|^7.0", - "symfony/phpunit-bridge": "^6.4.1|^7.0", - "symfony/security-core": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0", + "doctrine/persistence": "^3.1|^4.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/phpunit-bridge": "^6.4.1|^7.0|^8.0", + "symfony/security-core": "^6.4|^7.0|^8.0", + "symfony/security-http": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0", "twig/twig": "^3.0|^4.x-dev" }, "type": "symfony-bundle", @@ -13180,7 +13074,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.62.1" + "source": "https://github.com/symfony/maker-bundle/tree/v1.67.0" }, "funding": [ { @@ -13191,25 +13085,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-15T00:21:40+00:00" + "time": "2026-03-18T13:39:06+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v7.4.7", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "53c5a606cb4ae19c9466a5f8ffe60f61b0c93b5f" + "reference": "f95d88d54e34b13ee220a81133261a3c8a6a287a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/53c5a606cb4ae19c9466a5f8ffe60f61b0c93b5f", - "reference": "53c5a606cb4ae19c9466a5f8ffe60f61b0c93b5f", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/f95d88d54e34b13ee220a81133261a3c8a6a287a", + "reference": "f95d88d54e34b13ee220a81133261a3c8a6a287a", "shasum": "" }, "require": { @@ -13261,7 +13159,7 @@ "testing" ], "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v7.4.7" + "source": "https://github.com/symfony/phpunit-bridge/tree/v8.0.7" }, "funding": [ { @@ -13281,41 +13179,41 @@ "type": "tidelift" } ], - "time": "2026-03-04T13:54:41+00:00" + "time": "2026-03-04T13:55:34+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v7.0.10", + "version": "v8.0.7", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "884ed0470bd703ec40a78dfec94fe971ca55cad6" + "reference": "141336fd018b9ac77ba4910c04c2ca05c35e7ad2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/884ed0470bd703ec40a78dfec94fe971ca55cad6", - "reference": "884ed0470bd703ec40a78dfec94fe971ca55cad6", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/141336fd018b9ac77ba4910c04c2ca05c35e7ad2", + "reference": "141336fd018b9ac77ba4910c04c2ca05c35e7ad2", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/config": "^6.4|^7.0", - "symfony/framework-bundle": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", - "symfony/twig-bundle": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "composer-runtime-api": ">=2.1", + "php": ">=8.4", + "symfony/config": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/twig-bundle": "^7.4|^8.0" }, "conflict": { - "symfony/form": "<6.4", - "symfony/mailer": "<6.4", - "symfony/messenger": "<6.4" + "symfony/serializer": "<7.4", + "symfony/workflow": "<7.4" }, "require-dev": { - "symfony/browser-kit": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/browser-kit": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0" }, "type": "symfony-bundle", "autoload": { @@ -13346,7 +13244,7 @@ "dev" ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v7.0.10" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v8.0.7" }, "funding": [ { @@ -13357,12 +13255,16 @@ "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-07-26T12:31:22+00:00" + "time": "2026-03-04T08:20:53+00:00" }, { "name": "theseer/tokenizer", @@ -13614,49 +13516,69 @@ }, { "name": "zenstruck/foundry", - "version": "v1.38.6", + "version": "v2.9.2", "source": { "type": "git", "url": "https://github.com/zenstruck/foundry.git", - "reference": "774956af2d7ccb17ffa5e9ed385c786849c84bb2" + "reference": "9f3fe969d37fd5a0799ca455af9990a88036b6a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zenstruck/foundry/zipball/774956af2d7ccb17ffa5e9ed385c786849c84bb2", - "reference": "774956af2d7ccb17ffa5e9ed385c786849c84bb2", + "url": "https://api.github.com/repos/zenstruck/foundry/zipball/9f3fe969d37fd5a0799ca455af9990a88036b6a0", + "reference": "9f3fe969d37fd5a0799ca455af9990a88036b6a0", "shasum": "" }, "require": { - "doctrine/persistence": "^1.3.3|^2.0|^3.0|^4.0", - "fakerphp/faker": "^1.10", - "php": ">=8.0", + "fakerphp/faker": "^1.24", + "php": ">=8.1", "symfony/deprecation-contracts": "^2.2|^3.0", - "symfony/property-access": "^5.4|^6.0|^7.0", - "symfony/string": "^5.4|^6.0|^7.0", - "zenstruck/assert": "^1.0", - "zenstruck/callback": "^1.1" + "symfony/polyfill-php84": "^1.32", + "symfony/polyfill-php85": "^1.33", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4.9|~7.0.9|^7.1.2|^8.0", + "zenstruck/assert": "^1.4" }, "conflict": { - "doctrine/mongodb-odm": "2.5.0" + "doctrine/cache": "<1.12.1", + "doctrine/persistence": "<2.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8", - "dama/doctrine-test-bundle": "^7.0|^8.0", - "doctrine/doctrine-bundle": "^2.5", + "dama/doctrine-test-bundle": "^8.0", + "doctrine/collections": "^1.7|^2.0", + "doctrine/common": "^3.2.2", + "doctrine/doctrine-bundle": "^2.10|^3.0", "doctrine/doctrine-migrations-bundle": "^2.2|^3.0", "doctrine/mongodb-odm": "^2.4", - "doctrine/mongodb-odm-bundle": "^4.4.0|^5.0", - "doctrine/orm": "^2.11|^3.0", - "matthiasnoback/symfony-dependency-injection-test": "^4.1|^5.0", - "phpunit/phpunit": "^9.5.0", - "symfony/dotenv": "^5.4|^6.0|^7.0", - "symfony/framework-bundle": "^5.4|^6.0|^7.0", - "symfony/maker-bundle": "^1.49", - "symfony/phpunit-bridge": "^5.4|^6.0|^7.0", - "symfony/translation-contracts": "^2.5|^3.0" + "doctrine/mongodb-odm-bundle": "^4.6|^5.0", + "doctrine/orm": "^2.16|^3.0", + "doctrine/persistence": "^2.0|^3.0|^4.0", + "phpunit/phpunit": "^9.5.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dotenv": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/flex": "^2.10", + "symfony/framework-bundle": "^6.4|^7.0|^8.0", + "symfony/maker-bundle": "^1.55", + "symfony/phpunit-bridge": "^6.4.26|^7.0|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/runtime": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^3.4", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0", + "webmozart/assert": "^1.11" }, "type": "library", "extra": { + "psalm": { + "pluginClass": "Zenstruck\\Foundry\\Psalm\\FoundryPlugin" + }, + "symfony": { + "allow-contrib": false + }, "bamarni-bin": { "bin-links": true, "forward-command": false, @@ -13666,10 +13588,12 @@ "autoload": { "files": [ "src/functions.php", - "src/Persistence/functions.php" + "src/Persistence/functions.php", + "src/symfony_console.php" ], "psr-4": { "Zenstruck\\Foundry\\": "src/", + "Zenstruck\\Foundry\\Psalm\\": "utils/psalm", "Zenstruck\\Foundry\\Utils\\Rector\\": "utils/rector/src/" } }, @@ -13681,6 +13605,10 @@ { "name": "Kevin Bond", "email": "kevinbond@gmail.com" + }, + { + "name": "Nicolas PHILIPPE", + "email": "nikophil@gmail.com" } ], "description": "A model factory library for creating expressive, auto-completable, on-demand dev/test fixtures with Symfony and Doctrine.", @@ -13696,24 +13624,30 @@ ], "support": { "issues": "https://github.com/zenstruck/foundry/issues", - "source": "https://github.com/zenstruck/foundry/tree/v1.38.6" + "source": "https://github.com/zenstruck/foundry/tree/v2.9.2" }, "funding": [ { "url": "https://github.com/kbond", "type": "github" + }, + { + "url": "https://github.com/nikophil", + "type": "github" } ], - "time": "2025-04-02T11:40:23+00:00" + "time": "2026-02-17T15:48:50+00:00" } ], "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, + "minimum-stability": "dev", + "stability-flags": { + "phpmd/phpmd": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=8.3.1", + "php": ">=8.4.0", "ext-ctype": "*", "ext-curl": "*", "ext-gd": "*", diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 1299de5..a3db861 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -23,8 +23,6 @@ api_platform: extra_properties: standard_put: true rfc_7807_compliant_errors: true - event_listeners_backward_compatibility_layer: false - keep_legacy_inflector: false mapping: paths: - '%kernel.project_dir%/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto' diff --git a/config/packages/csrf.yaml b/config/packages/csrf.yaml new file mode 100644 index 0000000..40d4040 --- /dev/null +++ b/config/packages/csrf.yaml @@ -0,0 +1,11 @@ +# Enable stateless CSRF protection for forms and logins/logouts +framework: + form: + csrf_protection: + token_id: submit + + csrf_protection: + stateless_token_ids: + - submit + - authenticate + - logout diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 2ddcaab..6105f5f 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -3,7 +3,6 @@ doctrine: connections: default: url: '%env(resolve:DATABASE_URL)%' - use_savepoints: true profiling_collect_backtrace: '%kernel.debug%' # IMPORTANT: You MUST configure your server version, @@ -11,9 +10,6 @@ doctrine: #server_version: '16' orm: - auto_generate_proxy_classes: true - enable_lazy_ghost_objects: true - report_fields_where_declared: true validate_xml_mapping: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware auto_mapping: true @@ -40,7 +36,6 @@ when@test: dbal: connections: default: - use_savepoints: true # "TEST_TOKEN" is typically set by ParaTest dbname_suffix: '_test%env(default::TEST_TOKEN)%' diff --git a/config/packages/property_info.yaml b/config/packages/property_info.yaml new file mode 100644 index 0000000..dd31b9d --- /dev/null +++ b/config/packages/property_info.yaml @@ -0,0 +1,3 @@ +framework: + property_info: + with_constructor_extractor: true diff --git a/config/reference.php b/config/reference.php new file mode 100644 index 0000000..f20bcc9 --- /dev/null +++ b/config/reference.php @@ -0,0 +1,2106 @@ + [ + * 'App\\' => [ + * 'resource' => '../src/', + * ], + * ], + * ]); + * ``` + * + * @psalm-type ImportsConfig = list + * @psalm-type ParametersConfig = array|Param|null>|Param|null> + * @psalm-type ArgumentsType = list|array + * @psalm-type CallType = array|array{0:string, 1?:ArgumentsType, 2?:bool}|array{method:string, arguments?:ArgumentsType, returns_clone?:bool} + * @psalm-type TagsType = list>> // arrays inside the list must have only one element, with the tag name as the key + * @psalm-type CallbackType = string|array{0:string|ReferenceConfigurator,1:string}|\Closure|ReferenceConfigurator|ExpressionConfigurator + * @psalm-type DeprecationType = array{package: string, version: string, message?: string} + * @psalm-type DefaultsType = array{ + * public?: bool, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * } + * @psalm-type InstanceofType = array{ + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type DefinitionType = array{ + * class?: string, + * file?: string, + * parent?: string, + * shared?: bool, + * synthetic?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * configurator?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * decorates?: string, + * decoration_inner_name?: string, + * decoration_priority?: int, + * decoration_on_invalid?: 'exception'|'ignore'|null, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * from_callable?: CallbackType, + * } + * @psalm-type AliasType = string|array{ + * alias: string, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type PrototypeType = array{ + * resource: string, + * namespace?: string, + * exclude?: string|list, + * parent?: string, + * shared?: bool, + * lazy?: bool|string, + * public?: bool, + * abstract?: bool, + * deprecated?: DeprecationType, + * factory?: CallbackType, + * arguments?: ArgumentsType, + * properties?: array, + * configurator?: CallbackType, + * calls?: list, + * tags?: TagsType, + * resource_tags?: TagsType, + * autowire?: bool, + * autoconfigure?: bool, + * bind?: array, + * constructor?: string, + * } + * @psalm-type StackType = array{ + * stack: list>, + * public?: bool, + * deprecated?: DeprecationType, + * } + * @psalm-type ServicesConfig = array{ + * _defaults?: DefaultsType, + * _instanceof?: InstanceofType, + * ... + * } + * @psalm-type ExtensionType = array + * @psalm-type FrameworkConfig = array{ + * secret?: scalar|Param|null, + * http_method_override?: bool|Param, // Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. // Default: false + * allowed_http_method_override?: list|null, + * trust_x_sendfile_type_header?: scalar|Param|null, // Set true to enable support for xsendfile in binary file responses. // Default: "%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%" + * ide?: scalar|Param|null, // Default: "%env(default::SYMFONY_IDE)%" + * test?: bool|Param, + * default_locale?: scalar|Param|null, // Default: "en" + * set_locale_from_accept_language?: bool|Param, // Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed). // Default: false + * set_content_language_from_locale?: bool|Param, // Whether to set the Content-Language HTTP header on the Response using the Request locale. // Default: false + * enabled_locales?: list, + * trusted_hosts?: list, + * trusted_proxies?: mixed, // Default: ["%env(default::SYMFONY_TRUSTED_PROXIES)%"] + * trusted_headers?: list, + * error_controller?: scalar|Param|null, // Default: "error_controller" + * handle_all_throwables?: bool|Param, // HttpKernel will handle all kinds of \Throwable. // Default: true + * csrf_protection?: bool|array{ + * enabled?: scalar|Param|null, // Default: null + * stateless_token_ids?: list, + * check_header?: scalar|Param|null, // Whether to check the CSRF token in a header in addition to a cookie when using stateless protection. // Default: false + * cookie_name?: scalar|Param|null, // The name of the cookie to use when using stateless protection. // Default: "csrf-token" + * }, + * form?: bool|array{ // Form configuration + * enabled?: bool|Param, // Default: true + * csrf_protection?: bool|array{ + * enabled?: scalar|Param|null, // Default: null + * token_id?: scalar|Param|null, // Default: null + * field_name?: scalar|Param|null, // Default: "_token" + * field_attr?: array, + * }, + * }, + * http_cache?: bool|array{ // HTTP cache configuration + * enabled?: bool|Param, // Default: false + * debug?: bool|Param, // Default: "%kernel.debug%" + * trace_level?: "none"|"short"|"full"|Param, + * trace_header?: scalar|Param|null, + * default_ttl?: int|Param, + * private_headers?: list, + * skip_response_headers?: list, + * allow_reload?: bool|Param, + * allow_revalidate?: bool|Param, + * stale_while_revalidate?: int|Param, + * stale_if_error?: int|Param, + * terminate_on_cache_hit?: bool|Param, + * }, + * esi?: bool|array{ // ESI configuration + * enabled?: bool|Param, // Default: false + * }, + * ssi?: bool|array{ // SSI configuration + * enabled?: bool|Param, // Default: false + * }, + * fragments?: bool|array{ // Fragments configuration + * enabled?: bool|Param, // Default: false + * hinclude_default_template?: scalar|Param|null, // Default: null + * path?: scalar|Param|null, // Default: "/_fragment" + * }, + * profiler?: bool|array{ // Profiler configuration + * enabled?: bool|Param, // Default: false + * collect?: bool|Param, // Default: true + * collect_parameter?: scalar|Param|null, // The name of the parameter to use to enable or disable collection on a per request basis. // Default: null + * only_exceptions?: bool|Param, // Default: false + * only_main_requests?: bool|Param, // Default: false + * dsn?: scalar|Param|null, // Default: "file:%kernel.cache_dir%/profiler" + * collect_serializer_data?: true|Param, // Default: true + * }, + * workflows?: bool|array{ + * enabled?: bool|Param, // Default: false + * workflows?: array, + * definition_validators?: list, + * support_strategy?: scalar|Param|null, + * initial_marking?: list, + * events_to_dispatch?: list|null, + * places?: list, + * }>, + * transitions?: list, + * to?: list, + * weight?: int|Param, // Default: 1 + * metadata?: array, + * }>, + * metadata?: array, + * }>, + * }, + * router?: bool|array{ // Router configuration + * enabled?: bool|Param, // Default: false + * resource?: scalar|Param|null, + * type?: scalar|Param|null, + * default_uri?: scalar|Param|null, // The default URI used to generate URLs in a non-HTTP context. // Default: null + * http_port?: scalar|Param|null, // Default: 80 + * https_port?: scalar|Param|null, // Default: 443 + * strict_requirements?: scalar|Param|null, // set to true to throw an exception when a parameter does not match the requirements set to false to disable exceptions when a parameter does not match the requirements (and return null instead) set to null to disable parameter checks against requirements 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production // Default: true + * utf8?: bool|Param, // Default: true + * }, + * session?: bool|array{ // Session configuration + * enabled?: bool|Param, // Default: false + * storage_factory_id?: scalar|Param|null, // Default: "session.storage.factory.native" + * handler_id?: scalar|Param|null, // Defaults to using the native session handler, or to the native *file* session handler if "save_path" is not null. + * name?: scalar|Param|null, + * cookie_lifetime?: scalar|Param|null, + * cookie_path?: scalar|Param|null, + * cookie_domain?: scalar|Param|null, + * cookie_secure?: true|false|"auto"|Param, // Default: "auto" + * cookie_httponly?: bool|Param, // Default: true + * cookie_samesite?: null|"lax"|"strict"|"none"|Param, // Default: "lax" + * use_cookies?: bool|Param, + * gc_divisor?: scalar|Param|null, + * gc_probability?: scalar|Param|null, + * gc_maxlifetime?: scalar|Param|null, + * save_path?: scalar|Param|null, // Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null. + * metadata_update_threshold?: int|Param, // Seconds to wait between 2 session metadata updates. // Default: 0 + * }, + * request?: bool|array{ // Request configuration + * enabled?: bool|Param, // Default: false + * formats?: array>, + * }, + * assets?: bool|array{ // Assets configuration + * enabled?: bool|Param, // Default: true + * strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false + * version_strategy?: scalar|Param|null, // Default: null + * version?: scalar|Param|null, // Default: null + * version_format?: scalar|Param|null, // Default: "%%s?%%s" + * json_manifest_path?: scalar|Param|null, // Default: null + * base_path?: scalar|Param|null, // Default: "" + * base_urls?: list, + * packages?: array, + * }>, + * }, + * asset_mapper?: bool|array{ // Asset Mapper configuration + * enabled?: bool|Param, // Default: false + * paths?: array, + * excluded_patterns?: list, + * exclude_dotfiles?: bool|Param, // If true, any files starting with "." will be excluded from the asset mapper. // Default: true + * server?: bool|Param, // If true, a "dev server" will return the assets from the public directory (true in "debug" mode only by default). // Default: true + * public_prefix?: scalar|Param|null, // The public path where the assets will be written to (and served from when "server" is true). // Default: "/assets/" + * missing_import_mode?: "strict"|"warn"|"ignore"|Param, // Behavior if an asset cannot be found when imported from JavaScript or CSS files - e.g. "import './non-existent.js'". "strict" means an exception is thrown, "warn" means a warning is logged, "ignore" means the import is left as-is. // Default: "warn" + * extensions?: array, + * importmap_path?: scalar|Param|null, // The path of the importmap.php file. // Default: "%kernel.project_dir%/importmap.php" + * importmap_polyfill?: scalar|Param|null, // The importmap name that will be used to load the polyfill. Set to false to disable. // Default: "es-module-shims" + * importmap_script_attributes?: array, + * vendor_dir?: scalar|Param|null, // The directory to store JavaScript vendors. // Default: "%kernel.project_dir%/assets/vendor" + * precompress?: bool|array{ // Precompress assets with Brotli, Zstandard and gzip. + * enabled?: bool|Param, // Default: false + * formats?: list, + * extensions?: list, + * }, + * }, + * translator?: bool|array{ // Translator configuration + * enabled?: bool|Param, // Default: false + * fallbacks?: list, + * logging?: bool|Param, // Default: false + * formatter?: scalar|Param|null, // Default: "translator.formatter.default" + * cache_dir?: scalar|Param|null, // Default: "%kernel.cache_dir%/translations" + * default_path?: scalar|Param|null, // The default path used to load translations. // Default: "%kernel.project_dir%/translations" + * paths?: list, + * pseudo_localization?: bool|array{ + * enabled?: bool|Param, // Default: false + * accents?: bool|Param, // Default: true + * expansion_factor?: float|Param, // Default: 1.0 + * brackets?: bool|Param, // Default: true + * parse_html?: bool|Param, // Default: false + * localizable_html_attributes?: list, + * }, + * providers?: array, + * locales?: list, + * }>, + * globals?: array, + * domain?: string|Param, + * }>, + * }, + * validation?: bool|array{ // Validation configuration + * enabled?: bool|Param, // Default: true + * enable_attributes?: bool|Param, // Default: true + * static_method?: list, + * translation_domain?: scalar|Param|null, // Default: "validators" + * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|Param, // Default: "html5" + * mapping?: array{ + * paths?: list, + * }, + * not_compromised_password?: bool|array{ + * enabled?: bool|Param, // When disabled, compromised passwords will be accepted as valid. // Default: true + * endpoint?: scalar|Param|null, // API endpoint for the NotCompromisedPassword Validator. // Default: null + * }, + * disable_translation?: bool|Param, // Default: false + * auto_mapping?: array, + * }>, + * }, + * serializer?: bool|array{ // Serializer configuration + * enabled?: bool|Param, // Default: true + * enable_attributes?: bool|Param, // Default: true + * name_converter?: scalar|Param|null, + * circular_reference_handler?: scalar|Param|null, + * max_depth_handler?: scalar|Param|null, + * mapping?: array{ + * paths?: list, + * }, + * default_context?: array, + * named_serializers?: array, + * include_built_in_normalizers?: bool|Param, // Whether to include the built-in normalizers // Default: true + * include_built_in_encoders?: bool|Param, // Whether to include the built-in encoders // Default: true + * }>, + * }, + * property_access?: bool|array{ // Property access configuration + * enabled?: bool|Param, // Default: true + * magic_call?: bool|Param, // Default: false + * magic_get?: bool|Param, // Default: true + * magic_set?: bool|Param, // Default: true + * throw_exception_on_invalid_index?: bool|Param, // Default: false + * throw_exception_on_invalid_property_path?: bool|Param, // Default: true + * }, + * type_info?: bool|array{ // Type info configuration + * enabled?: bool|Param, // Default: true + * aliases?: array, + * }, + * property_info?: bool|array{ // Property info configuration + * enabled?: bool|Param, // Default: true + * with_constructor_extractor?: bool|Param, // Registers the constructor extractor. // Default: true + * }, + * cache?: array{ // Cache configuration + * prefix_seed?: scalar|Param|null, // Used to namespace cache keys when using several apps with the same shared backend. // Default: "_%kernel.project_dir%.%kernel.container_class%" + * app?: scalar|Param|null, // App related cache pools configuration. // Default: "cache.adapter.filesystem" + * system?: scalar|Param|null, // System related cache pools configuration. // Default: "cache.adapter.system" + * directory?: scalar|Param|null, // Default: "%kernel.share_dir%/pools/app" + * default_psr6_provider?: scalar|Param|null, + * default_redis_provider?: scalar|Param|null, // Default: "redis://localhost" + * default_valkey_provider?: scalar|Param|null, // Default: "valkey://localhost" + * default_memcached_provider?: scalar|Param|null, // Default: "memcached://localhost" + * default_doctrine_dbal_provider?: scalar|Param|null, // Default: "database_connection" + * default_pdo_provider?: scalar|Param|null, // Default: null + * pools?: array, + * tags?: scalar|Param|null, // Default: null + * public?: bool|Param, // Default: false + * default_lifetime?: scalar|Param|null, // Default lifetime of the pool. + * provider?: scalar|Param|null, // Overwrite the setting from the default provider for this adapter. + * early_expiration_message_bus?: scalar|Param|null, + * clearer?: scalar|Param|null, + * }>, + * }, + * php_errors?: array{ // PHP errors handling configuration + * log?: mixed, // Use the application logger instead of the PHP logger for logging PHP errors. // Default: true + * throw?: bool|Param, // Throw PHP errors as \ErrorException instances. // Default: true + * }, + * exceptions?: array, + * web_link?: bool|array{ // Web links configuration + * enabled?: bool|Param, // Default: true + * }, + * lock?: bool|string|array{ // Lock configuration + * enabled?: bool|Param, // Default: false + * resources?: array>, + * }, + * semaphore?: bool|string|array{ // Semaphore configuration + * enabled?: bool|Param, // Default: false + * resources?: array, + * }, + * messenger?: bool|array{ // Messenger configuration + * enabled?: bool|Param, // Default: true + * routing?: array, + * }>, + * serializer?: array{ + * default_serializer?: scalar|Param|null, // Service id to use as the default serializer for the transports. // Default: "messenger.transport.native_php_serializer" + * symfony_serializer?: array{ + * format?: scalar|Param|null, // Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default). // Default: "json" + * context?: array, + * }, + * }, + * transports?: array, + * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null + * retry_strategy?: string|array{ + * service?: scalar|Param|null, // Service id to override the retry strategy entirely. // Default: null + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries)). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness to apply to the delay (between 0 and 1). // Default: 0.1 + * }, + * rate_limiter?: scalar|Param|null, // Rate limiter name to use when processing messages. // Default: null + * }>, + * failure_transport?: scalar|Param|null, // Transport name to send failed messages to (after all retries have failed). // Default: null + * stop_worker_on_signals?: list, + * default_bus?: scalar|Param|null, // Default: null + * buses?: array, + * }>, + * }>, + * }, + * scheduler?: bool|array{ // Scheduler configuration + * enabled?: bool|Param, // Default: true + * }, + * disallow_search_engine_index?: bool|Param, // Enabled by default when debug is enabled. // Default: true + * http_client?: bool|array{ // HTTP Client configuration + * enabled?: bool|Param, // Default: true + * max_host_connections?: int|Param, // The maximum number of connections to a single host. + * default_options?: array{ + * headers?: array, + * vars?: array, + * max_redirects?: int|Param, // The maximum number of redirects to follow. + * http_version?: scalar|Param|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|Param|null, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|Param|null, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float|Param, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|Param|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|Param|null, // A certificate authority file. + * capath?: scalar|Param|null, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|Param|null, // A PEM formatted certificate file. + * local_pk?: scalar|Param|null, // A private key file. + * passphrase?: scalar|Param|null, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|Param|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...) + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|Param|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|Param|null, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool|Param, // Default: false + * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool|Param, // Default: false + * retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null + * http_codes?: array, + * }>, + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }, + * mock_response_factory?: scalar|Param|null, // The id of the service that should generate mock responses. It should be either an invokable or an iterable. + * scoped_clients?: array, + * headers?: array, + * max_redirects?: int|Param, // The maximum number of redirects to follow. + * http_version?: scalar|Param|null, // The default HTTP version, typically 1.1 or 2.0, leave to null for the best version. + * resolve?: array, + * proxy?: scalar|Param|null, // The URL of the proxy to pass requests through or null for automatic detection. + * no_proxy?: scalar|Param|null, // A comma separated list of hosts that do not require a proxy to be reached. + * timeout?: float|Param, // The idle timeout, defaults to the "default_socket_timeout" ini parameter. + * max_duration?: float|Param, // The maximum execution time for the request+response as a whole. + * bindto?: scalar|Param|null, // A network interface name, IP address, a host name or a UNIX socket to bind to. + * verify_peer?: bool|Param, // Indicates if the peer should be verified in a TLS context. + * verify_host?: bool|Param, // Indicates if the host should exist as a certificate common name. + * cafile?: scalar|Param|null, // A certificate authority file. + * capath?: scalar|Param|null, // A directory that contains multiple certificate authority files. + * local_cert?: scalar|Param|null, // A PEM formatted certificate file. + * local_pk?: scalar|Param|null, // A private key file. + * passphrase?: scalar|Param|null, // The passphrase used to encrypt the "local_pk" file. + * ciphers?: scalar|Param|null, // A list of TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...). + * peer_fingerprint?: array{ // Associative array: hashing algorithm => hash(es). + * sha1?: mixed, + * pin-sha256?: mixed, + * md5?: mixed, + * }, + * crypto_method?: scalar|Param|null, // The minimum version of TLS to accept; must be one of STREAM_CRYPTO_METHOD_TLSv*_CLIENT constants. + * extra?: array, + * rate_limiter?: scalar|Param|null, // Rate limiter name to use for throttling requests. // Default: null + * caching?: bool|array{ // Caching configuration. + * enabled?: bool|Param, // Default: false + * cache_pool?: string|Param, // The taggable cache pool to use for storing the responses. // Default: "cache.http_client" + * shared?: bool|Param, // Indicates whether the cache is shared (public) or private. // Default: true + * max_ttl?: int|Param, // The maximum TTL (in seconds) allowed for cached responses. Null means no cap. // Default: null + * }, + * retry_failed?: bool|array{ + * enabled?: bool|Param, // Default: false + * retry_strategy?: scalar|Param|null, // service id to override the retry strategy. // Default: null + * http_codes?: array, + * }>, + * max_retries?: int|Param, // Default: 3 + * delay?: int|Param, // Time in ms to delay (or the initial value when multiplier is used). // Default: 1000 + * multiplier?: float|Param, // If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries). // Default: 2 + * max_delay?: int|Param, // Max time in ms that a retry should ever be delayed (0 = infinite). // Default: 0 + * jitter?: float|Param, // Randomness in percent (between 0 and 1) to apply to the delay. // Default: 0.1 + * }, + * }>, + * }, + * mailer?: bool|array{ // Mailer configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|Param|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * dsn?: scalar|Param|null, // Default: null + * transports?: array, + * envelope?: array{ // Mailer Envelope configuration + * sender?: scalar|Param|null, + * recipients?: list, + * allowed_recipients?: list, + * }, + * headers?: array, + * dkim_signer?: bool|array{ // DKIM signer configuration + * enabled?: bool|Param, // Default: false + * key?: scalar|Param|null, // Key content, or path to key (in PEM format with the `file://` prefix) // Default: "" + * domain?: scalar|Param|null, // Default: "" + * select?: scalar|Param|null, // Default: "" + * passphrase?: scalar|Param|null, // The private key passphrase // Default: "" + * options?: array, + * }, + * smime_signer?: bool|array{ // S/MIME signer configuration + * enabled?: bool|Param, // Default: false + * key?: scalar|Param|null, // Path to key (in PEM format) // Default: "" + * certificate?: scalar|Param|null, // Path to certificate (in PEM format without the `file://` prefix) // Default: "" + * passphrase?: scalar|Param|null, // The private key passphrase // Default: null + * extra_certificates?: scalar|Param|null, // Default: null + * sign_options?: int|Param, // Default: null + * }, + * smime_encrypter?: bool|array{ // S/MIME encrypter configuration + * enabled?: bool|Param, // Default: false + * repository?: scalar|Param|null, // S/MIME certificate repository service. This service shall implement the `Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface`. // Default: "" + * cipher?: int|Param, // A set of algorithms used to encrypt the message // Default: null + * }, + * }, + * secrets?: bool|array{ + * enabled?: bool|Param, // Default: true + * vault_directory?: scalar|Param|null, // Default: "%kernel.project_dir%/config/secrets/%kernel.runtime_environment%" + * local_dotenv_file?: scalar|Param|null, // Default: "%kernel.project_dir%/.env.%kernel.environment%.local" + * decryption_env_var?: scalar|Param|null, // Default: "base64:default::SYMFONY_DECRYPTION_SECRET" + * }, + * notifier?: bool|array{ // Notifier configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|Param|null, // The message bus to use. Defaults to the default bus if the Messenger component is installed. // Default: null + * chatter_transports?: array, + * texter_transports?: array, + * notification_on_failed_messages?: bool|Param, // Default: false + * channel_policy?: array>, + * admin_recipients?: list, + * }, + * rate_limiter?: bool|array{ // Rate limiter configuration + * enabled?: bool|Param, // Default: false + * limiters?: array, + * limit?: int|Param, // The maximum allowed hits in a fixed interval or burst. + * interval?: scalar|Param|null, // Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * rate?: array{ // Configures the fill rate if "policy" is set to "token_bucket". + * interval?: scalar|Param|null, // Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent). + * amount?: int|Param, // Amount of tokens to add each interval. // Default: 1 + * }, + * }>, + * }, + * uid?: bool|array{ // Uid configuration + * enabled?: bool|Param, // Default: false + * default_uuid_version?: 7|6|4|1|Param, // Default: 7 + * name_based_uuid_version?: 5|3|Param, // Default: 5 + * name_based_uuid_namespace?: scalar|Param|null, + * time_based_uuid_version?: 7|6|1|Param, // Default: 7 + * time_based_uuid_node?: scalar|Param|null, + * }, + * html_sanitizer?: bool|array{ // HtmlSanitizer configuration + * enabled?: bool|Param, // Default: false + * sanitizers?: array, + * block_elements?: list, + * drop_elements?: list, + * allow_attributes?: array, + * drop_attributes?: array, + * force_attributes?: array>, + * force_https_urls?: bool|Param, // Transforms URLs using the HTTP scheme to use the HTTPS scheme instead. // Default: false + * allowed_link_schemes?: list, + * allowed_link_hosts?: list|null, + * allow_relative_links?: bool|Param, // Allows relative URLs to be used in links href attributes. // Default: false + * allowed_media_schemes?: list, + * allowed_media_hosts?: list|null, + * allow_relative_medias?: bool|Param, // Allows relative URLs to be used in media source attributes (img, audio, video, ...). // Default: false + * with_attribute_sanitizers?: list, + * without_attribute_sanitizers?: list, + * max_input_length?: int|Param, // The maximum length allowed for the sanitized input. // Default: 0 + * }>, + * }, + * webhook?: bool|array{ // Webhook configuration + * enabled?: bool|Param, // Default: false + * message_bus?: scalar|Param|null, // The message bus to use. // Default: "messenger.default_bus" + * routing?: array, + * }, + * remote-event?: bool|array{ // RemoteEvent configuration + * enabled?: bool|Param, // Default: false + * }, + * json_streamer?: bool|array{ // JSON streamer configuration + * enabled?: bool|Param, // Default: false + * }, + * } + * @psalm-type DoctrineConfig = array{ + * dbal?: array{ + * default_connection?: scalar|Param|null, + * types?: array, + * driver_schemes?: array, + * connections?: array, + * mapping_types?: array, + * default_table_options?: array, + * schema_manager_factory?: scalar|Param|null, // Default: "doctrine.dbal.default_schema_manager_factory" + * result_cache?: scalar|Param|null, + * replicas?: array, + * }>, + * }, + * orm?: array{ + * default_entity_manager?: scalar|Param|null, + * enable_native_lazy_objects?: bool|Param, // Deprecated: The "enable_native_lazy_objects" option is deprecated and will be removed in DoctrineBundle 4.0, as native lazy objects are now always enabled. // Default: true + * controller_resolver?: bool|array{ + * enabled?: bool|Param, // Default: true + * auto_mapping?: bool|Param, // Deprecated: The "doctrine.orm.controller_resolver.auto_mapping.auto_mapping" option is deprecated and will be removed in DoctrineBundle 4.0, as it only accepts `false` since 3.0. // Set to true to enable using route placeholders as lookup criteria when the primary key doesn't match the argument name // Default: false + * evict_cache?: bool|Param, // Set to true to fetch the entity from the database instead of using the cache, if any // Default: false + * }, + * entity_managers?: array, + * }>, + * }>, + * }, + * connection?: scalar|Param|null, + * class_metadata_factory_name?: scalar|Param|null, // Default: "Doctrine\\ORM\\Mapping\\ClassMetadataFactory" + * default_repository_class?: scalar|Param|null, // Default: "Doctrine\\ORM\\EntityRepository" + * auto_mapping?: scalar|Param|null, // Default: false + * naming_strategy?: scalar|Param|null, // Default: "doctrine.orm.naming_strategy.default" + * quote_strategy?: scalar|Param|null, // Default: "doctrine.orm.quote_strategy.default" + * typed_field_mapper?: scalar|Param|null, // Default: "doctrine.orm.typed_field_mapper.default" + * entity_listener_resolver?: scalar|Param|null, // Default: null + * fetch_mode_subselect_batch_size?: scalar|Param|null, + * repository_factory?: scalar|Param|null, // Default: "doctrine.orm.container_repository_factory" + * schema_ignore_classes?: list, + * validate_xml_mapping?: bool|Param, // Set to "true" to opt-in to the new mapping driver mode that was added in Doctrine ORM 2.14 and will be mandatory in ORM 3.0. See https://github.com/doctrine/orm/pull/6728. // Default: false + * second_level_cache?: array{ + * region_cache_driver?: string|array{ + * type?: scalar|Param|null, // Default: null + * id?: scalar|Param|null, + * pool?: scalar|Param|null, + * }, + * region_lock_lifetime?: scalar|Param|null, // Default: 60 + * log_enabled?: bool|Param, // Default: true + * region_lifetime?: scalar|Param|null, // Default: 3600 + * enabled?: bool|Param, // Default: true + * factory?: scalar|Param|null, + * regions?: array, + * loggers?: array, + * }, + * hydrators?: array, + * mappings?: array, + * dql?: array{ + * string_functions?: array, + * numeric_functions?: array, + * datetime_functions?: array, + * }, + * filters?: array, + * }>, + * identity_generation_preferences?: array, + * }>, + * resolve_target_entities?: array, + * }, + * } + * @psalm-type DoctrineMigrationsConfig = array{ + * enable_service_migrations?: bool|Param, // Whether to enable fetching migrations from the service container. // Default: false + * migrations_paths?: array, + * services?: array, + * factories?: array, + * storage?: array{ // Storage to use for migration status metadata. + * table_storage?: array{ // The default metadata storage, implemented as a table in the database. + * table_name?: scalar|Param|null, // Default: null + * version_column_name?: scalar|Param|null, // Default: null + * version_column_length?: scalar|Param|null, // Default: null + * executed_at_column_name?: scalar|Param|null, // Default: null + * execution_time_column_name?: scalar|Param|null, // Default: null + * }, + * }, + * migrations?: list, + * connection?: scalar|Param|null, // Connection name to use for the migrations database. // Default: null + * em?: scalar|Param|null, // Entity manager name to use for the migrations database (available when doctrine/orm is installed). // Default: null + * all_or_nothing?: scalar|Param|null, // Run all migrations in a transaction. // Default: false + * check_database_platform?: scalar|Param|null, // Adds an extra check in the generated migrations to allow execution only on the same platform as they were initially generated on. // Default: true + * custom_template?: scalar|Param|null, // Custom template path for generated migration classes. // Default: null + * organize_migrations?: scalar|Param|null, // Organize migrations mode. Possible values are: "BY_YEAR", "BY_YEAR_AND_MONTH", false // Default: false + * enable_profiler?: bool|Param, // Whether or not to enable the profiler collector to calculate and visualize migration status. This adds some queries overhead. // Default: false + * transactional?: bool|Param, // Whether or not to wrap migrations in a single transaction. // Default: true + * } + * @psalm-type MakerConfig = array{ + * root_namespace?: scalar|Param|null, // Default: "App" + * generate_final_classes?: bool|Param, // Default: true + * generate_final_entities?: bool|Param, // Default: false + * } + * @psalm-type TwigConfig = array{ + * form_themes?: list, + * globals?: array, + * autoescape_service?: scalar|Param|null, // Default: null + * autoescape_service_method?: scalar|Param|null, // Default: null + * cache?: scalar|Param|null, // Default: true + * charset?: scalar|Param|null, // Default: "%kernel.charset%" + * debug?: bool|Param, // Default: "%kernel.debug%" + * strict_variables?: bool|Param, // Default: "%kernel.debug%" + * auto_reload?: scalar|Param|null, + * optimizations?: int|Param, + * default_path?: scalar|Param|null, // The default path used to load templates. // Default: "%kernel.project_dir%/templates" + * file_name_pattern?: list, + * paths?: array, + * date?: array{ // The default format options used by the date filter. + * format?: scalar|Param|null, // Default: "F j, Y H:i" + * interval_format?: scalar|Param|null, // Default: "%d days" + * timezone?: scalar|Param|null, // The timezone used when formatting dates, when set to null, the timezone returned by date_default_timezone_get() is used. // Default: null + * }, + * number_format?: array{ // The default format options for the number_format filter. + * decimals?: int|Param, // Default: 0 + * decimal_point?: scalar|Param|null, // Default: "." + * thousands_separator?: scalar|Param|null, // Default: "," + * }, + * mailer?: array{ + * html_to_text_converter?: scalar|Param|null, // A service implementing the "Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface". // Default: null + * }, + * } + * @psalm-type SecurityConfig = array{ + * access_denied_url?: scalar|Param|null, // Default: null + * session_fixation_strategy?: "none"|"migrate"|"invalidate"|Param, // Default: "migrate" + * expose_security_errors?: \Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::None|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::AccountStatus|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::All|Param, // Default: "none" + * erase_credentials?: bool|Param, // Default: true + * access_decision_manager?: array{ + * strategy?: "affirmative"|"consensus"|"unanimous"|"priority"|Param, + * service?: scalar|Param|null, + * strategy_service?: scalar|Param|null, + * allow_if_all_abstain?: bool|Param, // Default: false + * allow_if_equal_granted_denied?: bool|Param, // Default: true + * }, + * password_hashers?: array, + * hash_algorithm?: scalar|Param|null, // Name of hashing algorithm for PBKDF2 (i.e. sha256, sha512, etc..) See hash_algos() for a list of supported algorithms. // Default: "sha512" + * key_length?: scalar|Param|null, // Default: 40 + * ignore_case?: bool|Param, // Default: false + * encode_as_base64?: bool|Param, // Default: true + * iterations?: scalar|Param|null, // Default: 5000 + * cost?: int|Param, // Default: null + * memory_cost?: scalar|Param|null, // Default: null + * time_cost?: scalar|Param|null, // Default: null + * id?: scalar|Param|null, + * }>, + * providers?: array, + * }, + * entity?: array{ + * class?: scalar|Param|null, // The full entity class name of your user class. + * property?: scalar|Param|null, // Default: null + * manager_name?: scalar|Param|null, // Default: null + * }, + * memory?: array{ + * users?: array, + * }>, + * }, + * ldap?: array{ + * service?: scalar|Param|null, + * base_dn?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: null + * search_password?: scalar|Param|null, // Default: null + * extra_fields?: list, + * default_roles?: list, + * role_fetcher?: scalar|Param|null, // Default: null + * uid_key?: scalar|Param|null, // Default: "sAMAccountName" + * filter?: scalar|Param|null, // Default: "({uid_key}={user_identifier})" + * password_attribute?: scalar|Param|null, // Default: null + * }, + * }>, + * firewalls?: array, + * security?: bool|Param, // Default: true + * user_checker?: scalar|Param|null, // The UserChecker to use when authenticating users in this firewall. // Default: "security.user_checker" + * request_matcher?: scalar|Param|null, + * access_denied_url?: scalar|Param|null, + * access_denied_handler?: scalar|Param|null, + * entry_point?: scalar|Param|null, // An enabled authenticator name or a service id that implements "Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface". + * provider?: scalar|Param|null, + * stateless?: bool|Param, // Default: false + * lazy?: bool|Param, // Default: false + * context?: scalar|Param|null, + * logout?: array{ + * enable_csrf?: bool|Param|null, // Default: null + * csrf_token_id?: scalar|Param|null, // Default: "logout" + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_manager?: scalar|Param|null, + * path?: scalar|Param|null, // Default: "/logout" + * target?: scalar|Param|null, // Default: "/" + * invalidate_session?: bool|Param, // Default: true + * clear_site_data?: list<"*"|"cache"|"cookies"|"storage"|"executionContexts"|Param>, + * delete_cookies?: array, + * }, + * switch_user?: array{ + * provider?: scalar|Param|null, + * parameter?: scalar|Param|null, // Default: "_switch_user" + * role?: scalar|Param|null, // Default: "ROLE_ALLOWED_TO_SWITCH" + * target_route?: scalar|Param|null, // Default: null + * }, + * required_badges?: list, + * custom_authenticators?: list, + * login_throttling?: array{ + * limiter?: scalar|Param|null, // A service id implementing "Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface". + * max_attempts?: int|Param, // Default: 5 + * interval?: scalar|Param|null, // Default: "1 minute" + * lock_factory?: scalar|Param|null, // The service ID of the lock factory used by the login rate limiter (or null to disable locking). // Default: null + * cache_pool?: string|Param, // The cache pool to use for storing the limiter state // Default: "cache.rate_limiter" + * storage_service?: string|Param, // The service ID of a custom storage implementation, this precedes any configured "cache_pool" // Default: null + * }, + * x509?: array{ + * provider?: scalar|Param|null, + * user?: scalar|Param|null, // Default: "SSL_CLIENT_S_DN_Email" + * credentials?: scalar|Param|null, // Default: "SSL_CLIENT_S_DN" + * user_identifier?: scalar|Param|null, // Default: "emailAddress" + * }, + * remote_user?: array{ + * provider?: scalar|Param|null, + * user?: scalar|Param|null, // Default: "REMOTE_USER" + * }, + * login_link?: array{ + * check_route?: scalar|Param|null, // Route that will validate the login link - e.g. "app_login_link_verify". + * check_post_only?: scalar|Param|null, // If true, only HTTP POST requests to "check_route" will be handled by the authenticator. // Default: false + * signature_properties?: list, + * lifetime?: int|Param, // The lifetime of the login link in seconds. // Default: 600 + * max_uses?: int|Param, // Max number of times a login link can be used - null means unlimited within lifetime. // Default: null + * used_link_cache?: scalar|Param|null, // Cache service id used to expired links of max_uses is set. + * success_handler?: scalar|Param|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface. + * failure_handler?: scalar|Param|null, // A service id that implements Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface. + * provider?: scalar|Param|null, // The user provider to load users from. + * secret?: scalar|Param|null, // Default: "%kernel.secret%" + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * login_path?: scalar|Param|null, // Default: "/login" + * target_path_parameter?: scalar|Param|null, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|Param|null, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" + * }, + * form_login?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_parameter?: scalar|Param|null, // Default: "_username" + * password_parameter?: scalar|Param|null, // Default: "_password" + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|Param|null, // Default: "authenticate" + * enable_csrf?: bool|Param, // Default: false + * post_only?: bool|Param, // Default: true + * form_only?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * target_path_parameter?: scalar|Param|null, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|Param|null, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" + * }, + * form_login_ldap?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_parameter?: scalar|Param|null, // Default: "_username" + * password_parameter?: scalar|Param|null, // Default: "_password" + * csrf_parameter?: scalar|Param|null, // Default: "_csrf_token" + * csrf_token_id?: scalar|Param|null, // Default: "authenticate" + * enable_csrf?: bool|Param, // Default: false + * post_only?: bool|Param, // Default: true + * form_only?: bool|Param, // Default: false + * always_use_default_target_path?: bool|Param, // Default: false + * default_target_path?: scalar|Param|null, // Default: "/" + * target_path_parameter?: scalar|Param|null, // Default: "_target_path" + * use_referer?: bool|Param, // Default: false + * failure_path?: scalar|Param|null, // Default: null + * failure_forward?: bool|Param, // Default: false + * failure_path_parameter?: scalar|Param|null, // Default: "_failure_path" + * service?: scalar|Param|null, // Default: "ldap" + * dn_string?: scalar|Param|null, // Default: "{user_identifier}" + * query_string?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: "" + * search_password?: scalar|Param|null, // Default: "" + * }, + * json_login?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_path?: scalar|Param|null, // Default: "username" + * password_path?: scalar|Param|null, // Default: "password" + * }, + * json_login_ldap?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * check_path?: scalar|Param|null, // Default: "/login_check" + * use_forward?: bool|Param, // Default: false + * login_path?: scalar|Param|null, // Default: "/login" + * username_path?: scalar|Param|null, // Default: "username" + * password_path?: scalar|Param|null, // Default: "password" + * service?: scalar|Param|null, // Default: "ldap" + * dn_string?: scalar|Param|null, // Default: "{user_identifier}" + * query_string?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: "" + * search_password?: scalar|Param|null, // Default: "" + * }, + * access_token?: array{ + * provider?: scalar|Param|null, + * remember_me?: bool|Param, // Default: true + * success_handler?: scalar|Param|null, + * failure_handler?: scalar|Param|null, + * realm?: scalar|Param|null, // Default: null + * token_extractors?: list, + * token_handler?: string|array{ + * id?: scalar|Param|null, + * oidc_user_info?: string|array{ + * base_uri?: scalar|Param|null, // Base URI of the userinfo endpoint on the OIDC server, or the OIDC server URI to use the discovery (require "discovery" to be configured). + * discovery?: array{ // Enable the OIDC discovery. + * cache?: array{ + * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|Param|null, // Claim which contains the user identifier (e.g. sub, email, etc.). // Default: "sub" + * client?: scalar|Param|null, // HttpClient service id to use to call the OIDC server. + * }, + * oidc?: array{ + * discovery?: array{ // Enable the OIDC discovery. + * base_uri?: list, + * cache?: array{ + * id?: scalar|Param|null, // Cache service id to use to cache the OIDC discovery configuration. + * }, + * }, + * claim?: scalar|Param|null, // Claim which contains the user identifier (e.g.: sub, email..). // Default: "sub" + * audience?: scalar|Param|null, // Audience set in the token, for validation purpose. + * issuers?: list, + * algorithms?: list, + * keyset?: scalar|Param|null, // JSON-encoded JWKSet used to sign the token (must contain a list of valid public keys). + * encryption?: bool|array{ + * enabled?: bool|Param, // Default: false + * enforce?: bool|Param, // When enabled, the token shall be encrypted. // Default: false + * algorithms?: list, + * keyset?: scalar|Param|null, // JSON-encoded JWKSet used to decrypt the token (must contain a list of valid private keys). + * }, + * }, + * cas?: array{ + * validation_url?: scalar|Param|null, // CAS server validation URL + * prefix?: scalar|Param|null, // CAS prefix // Default: "cas" + * http_client?: scalar|Param|null, // HTTP Client service // Default: null + * }, + * oauth2?: scalar|Param|null, + * }, + * }, + * http_basic?: array{ + * provider?: scalar|Param|null, + * realm?: scalar|Param|null, // Default: "Secured Area" + * }, + * http_basic_ldap?: array{ + * provider?: scalar|Param|null, + * realm?: scalar|Param|null, // Default: "Secured Area" + * service?: scalar|Param|null, // Default: "ldap" + * dn_string?: scalar|Param|null, // Default: "{user_identifier}" + * query_string?: scalar|Param|null, + * search_dn?: scalar|Param|null, // Default: "" + * search_password?: scalar|Param|null, // Default: "" + * }, + * remember_me?: array{ + * secret?: scalar|Param|null, // Default: "%kernel.secret%" + * service?: scalar|Param|null, + * user_providers?: list, + * catch_exceptions?: bool|Param, // Default: true + * signature_properties?: list, + * token_provider?: string|array{ + * service?: scalar|Param|null, // The service ID of a custom remember-me token provider. + * doctrine?: bool|array{ + * enabled?: bool|Param, // Default: false + * connection?: scalar|Param|null, // Default: null + * }, + * }, + * token_verifier?: scalar|Param|null, // The service ID of a custom rememberme token verifier. + * name?: scalar|Param|null, // Default: "REMEMBERME" + * lifetime?: int|Param, // Default: 31536000 + * path?: scalar|Param|null, // Default: "/" + * domain?: scalar|Param|null, // Default: null + * secure?: true|false|"auto"|Param, // Default: true + * httponly?: bool|Param, // Default: true + * samesite?: null|"lax"|"strict"|"none"|Param, // Default: "none" + * always_remember_me?: bool|Param, // Default: false + * remember_me_parameter?: scalar|Param|null, // Default: "_remember_me" + * }, + * }>, + * access_control?: list, + * attributes?: array, + * route?: scalar|Param|null, // Default: null + * methods?: list, + * allow_if?: scalar|Param|null, // Default: null + * roles?: list, + * }>, + * role_hierarchy?: array>, + * } + * @psalm-type NelmioCorsConfig = array{ + * defaults?: array{ + * allow_credentials?: bool|Param, // Default: false + * allow_origin?: list, + * allow_headers?: list, + * allow_methods?: list, + * allow_private_network?: bool|Param, // Default: false + * expose_headers?: list, + * max_age?: scalar|Param|null, // Default: 0 + * hosts?: list, + * origin_regex?: bool|Param, // Default: false + * forced_allow_origin_value?: scalar|Param|null, // Default: null + * skip_same_as_origin?: bool|Param, // Default: true + * }, + * paths?: array, + * allow_headers?: list, + * allow_methods?: list, + * allow_private_network?: bool|Param, + * expose_headers?: list, + * max_age?: scalar|Param|null, // Default: 0 + * hosts?: list, + * origin_regex?: bool|Param, + * forced_allow_origin_value?: scalar|Param|null, // Default: null + * skip_same_as_origin?: bool|Param, + * }>, + * } + * @psalm-type ApiPlatformConfig = array{ + * title?: scalar|Param|null, // The title of the API. // Default: "" + * description?: scalar|Param|null, // The description of the API. // Default: "" + * version?: scalar|Param|null, // The version of the API. // Default: "0.0.0" + * show_webby?: bool|Param, // If true, show Webby on the documentation page // Default: true + * use_symfony_listeners?: bool|Param, // Uses Symfony event listeners instead of the ApiPlatform\Symfony\Controller\MainController. // Default: false + * name_converter?: scalar|Param|null, // Specify a name converter to use. // Default: null + * asset_package?: scalar|Param|null, // Specify an asset package name to use. // Default: null + * path_segment_name_generator?: scalar|Param|null, // Specify a path name generator to use. // Default: "api_platform.metadata.path_segment_name_generator.underscore" + * inflector?: scalar|Param|null, // Specify an inflector to use. // Default: "api_platform.metadata.inflector" + * validator?: array{ + * serialize_payload_fields?: mixed, // Set to null to serialize all payload fields when a validation error is thrown, or set the fields you want to include explicitly. // Default: [] + * query_parameter_validation?: bool|Param, // Deprecated: Will be removed in API Platform 5.0. // Default: true + * }, + * jsonapi?: array{ + * use_iri_as_id?: bool|Param, // Set to false to use entity identifiers instead of IRIs as the "id" field in JSON:API responses. // Default: true + * }, + * eager_loading?: bool|array{ + * enabled?: bool|Param, // Default: true + * fetch_partial?: bool|Param, // Fetch only partial data according to serialization groups. If enabled, Doctrine ORM entities will not work as expected if any of the other fields are used. // Default: false + * max_joins?: int|Param, // Max number of joined relations before EagerLoading throws a RuntimeException // Default: 30 + * force_eager?: bool|Param, // Force join on every relation. If disabled, it will only join relations having the EAGER fetch mode. // Default: true + * }, + * handle_symfony_errors?: bool|Param, // Allows to handle symfony exceptions. // Default: false + * enable_swagger?: bool|Param, // Enable the Swagger documentation and export. // Default: true + * enable_json_streamer?: bool|Param, // Enable json streamer. // Default: false + * enable_swagger_ui?: bool|Param, // Enable Swagger UI // Default: true + * enable_re_doc?: bool|Param, // Enable ReDoc // Default: true + * enable_scalar?: bool|Param, // Enable Scalar API Reference // Default: true + * enable_entrypoint?: bool|Param, // Enable the entrypoint // Default: true + * enable_docs?: bool|Param, // Enable the docs // Default: true + * enable_profiler?: bool|Param, // Enable the data collector and the WebProfilerBundle integration. // Default: true + * enable_phpdoc_parser?: bool|Param, // Enable resource metadata collector using PHPStan PhpDocParser. // Default: true + * enable_link_security?: bool|Param, // Deprecated: This option is always enabled and will be removed in API Platform 5.0. // Enable security for Links (sub resources). // Default: true + * collection?: array{ + * exists_parameter_name?: scalar|Param|null, // The name of the query parameter to filter on nullable field values. // Default: "exists" + * order?: scalar|Param|null, // The default order of results. // Default: "ASC" + * order_parameter_name?: scalar|Param|null, // The name of the query parameter to order results. // Default: "order" + * order_nulls_comparison?: "nulls_smallest"|"nulls_largest"|"nulls_always_first"|"nulls_always_last"|Param|null, // The nulls comparison strategy. // Default: null + * pagination?: bool|array{ + * enabled?: bool|Param, // Default: true + * page_parameter_name?: scalar|Param|null, // The default name of the parameter handling the page number. // Default: "page" + * enabled_parameter_name?: scalar|Param|null, // The name of the query parameter to enable or disable pagination. // Default: "pagination" + * items_per_page_parameter_name?: scalar|Param|null, // The name of the query parameter to set the number of items per page. // Default: "itemsPerPage" + * partial_parameter_name?: scalar|Param|null, // The name of the query parameter to enable or disable partial pagination. // Default: "partial" + * }, + * }, + * mapping?: array{ + * imports?: list, + * paths?: list, + * }, + * resource_class_directories?: list, + * serializer?: array{ + * hydra_prefix?: bool|Param, // Use the "hydra:" prefix. // Default: false + * }, + * doctrine?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * doctrine_mongodb_odm?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * oauth?: bool|array{ + * enabled?: bool|Param, // Default: false + * clientId?: scalar|Param|null, // The oauth client id. // Default: "" + * clientSecret?: scalar|Param|null, // The OAuth client secret. Never use this parameter in your production environment. It exposes crucial security information. This feature is intended for dev/test environments only. Enable "oauth.pkce" instead // Default: "" + * pkce?: bool|Param, // Enable the oauth PKCE. // Default: false + * type?: scalar|Param|null, // The oauth type. // Default: "oauth2" + * flow?: scalar|Param|null, // The oauth flow grant type. // Default: "application" + * tokenUrl?: scalar|Param|null, // The oauth token url. // Default: "" + * authorizationUrl?: scalar|Param|null, // The oauth authentication url. // Default: "" + * refreshUrl?: scalar|Param|null, // The oauth refresh url. // Default: "" + * scopes?: list, + * }, + * graphql?: bool|array{ + * enabled?: bool|Param, // Default: false + * default_ide?: scalar|Param|null, // Default: "graphiql" + * graphiql?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * introspection?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * max_query_depth?: int|Param, // Default: 20 + * graphql_playground?: bool|array{ // Deprecated: The "graphql_playground" configuration is deprecated and will be ignored. + * enabled?: bool|Param, // Default: false + * }, + * max_query_complexity?: int|Param, // Default: 500 + * nesting_separator?: scalar|Param|null, // The separator to use to filter nested fields. // Default: "_" + * collection?: array{ + * pagination?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * }, + * }, + * swagger?: array{ + * persist_authorization?: bool|Param, // Persist the SwaggerUI Authorization in the localStorage. // Default: false + * versions?: list, + * api_keys?: array, + * http_auth?: array, + * swagger_ui_extra_configuration?: mixed, // To pass extra configuration to Swagger UI, like docExpansion or filter. // Default: [] + * }, + * http_cache?: array{ + * public?: bool|Param|null, // To make all responses public by default. // Default: null + * invalidation?: bool|array{ // Enable the tags-based cache invalidation system. + * enabled?: bool|Param, // Default: false + * varnish_urls?: list, + * urls?: list, + * scoped_clients?: list, + * max_header_length?: int|Param, // Max header length supported by the cache server. // Default: 7500 + * request_options?: mixed, // To pass options to the client charged with the request. // Default: [] + * purger?: scalar|Param|null, // Specify a purger to use (available values: "api_platform.http_cache.purger.varnish.ban", "api_platform.http_cache.purger.varnish.xkey", "api_platform.http_cache.purger.souin"). // Default: "api_platform.http_cache.purger.varnish" + * xkey?: array{ // Deprecated: The "xkey" configuration is deprecated, use your own purger to customize surrogate keys or the appropriate parameters. + * glue?: scalar|Param|null, // xkey glue between keys // Default: " " + * }, + * }, + * }, + * mercure?: bool|array{ + * enabled?: bool|Param, // Default: true + * hub_url?: scalar|Param|null, // The URL sent in the Link HTTP header. If not set, will default to the URL for MercureBundle's default hub. // Default: null + * include_type?: bool|Param, // Always include @type in updates (including delete ones). // Default: false + * }, + * messenger?: bool|array{ + * enabled?: bool|Param, // Default: true + * }, + * elasticsearch?: bool|array{ + * enabled?: bool|Param, // Default: false + * hosts?: list, + * ssl_ca_bundle?: scalar|Param|null, // Path to the SSL CA bundle file for Elasticsearch SSL verification. // Default: null + * ssl_verification?: bool|Param, // Enable or disable SSL verification for Elasticsearch connections. // Default: true + * client?: "elasticsearch"|"opensearch"|Param, // The search engine client to use: "elasticsearch" or "opensearch". // Default: "elasticsearch" + * }, + * openapi?: array{ + * contact?: array{ + * name?: scalar|Param|null, // The identifying name of the contact person/organization. // Default: null + * url?: scalar|Param|null, // The URL pointing to the contact information. MUST be in the format of a URL. // Default: null + * email?: scalar|Param|null, // The email address of the contact person/organization. MUST be in the format of an email address. // Default: null + * }, + * termsOfService?: scalar|Param|null, // A URL to the Terms of Service for the API. MUST be in the format of a URL. // Default: null + * tags?: list, + * license?: array{ + * name?: scalar|Param|null, // The license name used for the API. // Default: null + * url?: scalar|Param|null, // URL to the license used for the API. MUST be in the format of a URL. // Default: null + * identifier?: scalar|Param|null, // An SPDX license expression for the API. The identifier field is mutually exclusive of the url field. // Default: null + * }, + * swagger_ui_extra_configuration?: mixed, // To pass extra configuration to Swagger UI, like docExpansion or filter. // Default: [] + * scalar_extra_configuration?: mixed, // To pass extra configuration to Scalar API Reference, like theme or darkMode. // Default: [] + * overrideResponses?: bool|Param, // Whether API Platform adds automatic responses to the OpenAPI documentation. // Default: true + * error_resource_class?: scalar|Param|null, // The class used to represent errors in the OpenAPI documentation. // Default: null + * validation_error_resource_class?: scalar|Param|null, // The class used to represent validation errors in the OpenAPI documentation. // Default: null + * }, + * maker?: bool|array{ + * enabled?: bool|Param, // Default: true + * namespace_prefix?: scalar|Param|null, // Add a prefix to all maker generated classes. e.g set it to "Api" to set the maker namespace to "App\Api\" (if the maker.root_namespace config is App). e.g. App\Api\State\MyStateProcessor // Default: "" + * }, + * mcp?: bool|array{ + * enabled?: bool|Param, // Default: true + * format?: scalar|Param|null, // The serialization format used for MCP tool input/output. Must be a format registered in api_platform.formats (e.g. "jsonld", "json", "jsonapi"). // Default: "jsonld" + * }, + * exception_to_status?: array, + * formats?: array, + * }>, + * patch_formats?: array, + * }>, + * docs_formats?: array, + * }>, + * error_formats?: array, + * }>, + * jsonschema_formats?: list, + * defaults?: array{ + * uri_template?: mixed, + * short_name?: mixed, + * description?: mixed, + * types?: mixed, + * operations?: mixed, + * formats?: mixed, + * input_formats?: mixed, + * output_formats?: mixed, + * uri_variables?: mixed, + * route_prefix?: mixed, + * defaults?: mixed, + * requirements?: mixed, + * options?: mixed, + * stateless?: mixed, + * sunset?: mixed, + * accept_patch?: mixed, + * status?: mixed, + * host?: mixed, + * schemes?: mixed, + * condition?: mixed, + * controller?: mixed, + * class?: mixed, + * url_generation_strategy?: mixed, + * deprecation_reason?: mixed, + * headers?: mixed, + * cache_headers?: mixed, + * normalization_context?: mixed, + * denormalization_context?: mixed, + * collect_denormalization_errors?: mixed, + * hydra_context?: mixed, + * openapi?: mixed, + * validation_context?: mixed, + * filters?: mixed, + * mercure?: mixed, + * messenger?: mixed, + * input?: mixed, + * output?: mixed, + * order?: mixed, + * fetch_partial?: mixed, + * force_eager?: mixed, + * pagination_client_enabled?: mixed, + * pagination_client_items_per_page?: mixed, + * pagination_client_partial?: mixed, + * pagination_via_cursor?: mixed, + * pagination_enabled?: mixed, + * pagination_fetch_join_collection?: mixed, + * pagination_use_output_walkers?: mixed, + * pagination_items_per_page?: mixed, + * pagination_maximum_items_per_page?: mixed, + * pagination_partial?: mixed, + * pagination_type?: mixed, + * security?: mixed, + * security_message?: mixed, + * security_post_denormalize?: mixed, + * security_post_denormalize_message?: mixed, + * security_post_validation?: mixed, + * security_post_validation_message?: mixed, + * composite_identifier?: mixed, + * exception_to_status?: mixed, + * query_parameter_validation_enabled?: mixed, + * links?: mixed, + * graph_ql_operations?: mixed, + * provider?: mixed, + * processor?: mixed, + * state_options?: mixed, + * rules?: mixed, + * policy?: mixed, + * middleware?: mixed, + * parameters?: array + * }>, + * strict_query_parameter_validation?: mixed, + * hide_hydra_operation?: mixed, + * json_stream?: mixed, + * extra_properties?: mixed, + * map?: mixed, + * mcp?: mixed, + * route_name?: mixed, + * errors?: mixed, + * read?: mixed, + * deserialize?: mixed, + * validate?: mixed, + * write?: mixed, + * serialize?: mixed, + * content_negotiation?: mixed, + * priority?: mixed, + * name?: mixed, + * allow_create?: mixed, + * item_uri_template?: mixed, + * ... + * }, + * } + * @psalm-type WebProfilerConfig = array{ + * toolbar?: bool|array{ // Profiler toolbar configuration + * enabled?: bool|Param, // Default: false + * ajax_replace?: bool|Param, // Replace toolbar on AJAX requests // Default: false + * }, + * intercept_redirects?: bool|Param, // Default: false + * excluded_ajax_paths?: scalar|Param|null, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt" + * } + * @psalm-type ZenstruckFoundryConfig = array{ + * auto_refresh_proxies?: bool|Param|null, // Deprecated: Since 2.0 auto_refresh_proxies defaults to true and this configuration has no effect. // Whether to auto-refresh proxies by default (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#auto-refresh) // Default: null + * enable_auto_refresh_with_lazy_objects?: bool|Param|null, // Enable auto-refresh using PHP 8.4 lazy objects (cannot be enabled if PHP < 8.4). // Default: null + * faker?: array{ // Configure the faker used by your factories. + * locale?: scalar|Param|null, // The default locale to use for faker. // Default: null + * seed?: scalar|Param|null, // Deprecated: The "faker.seed" configuration is deprecated and will be removed in 3.0. Use environment variable "FOUNDRY_FAKER_SEED" instead. // Random number generator seed to produce the same fake values every run. // Default: null + * manage_seed?: bool|Param, // Automatically manage faker seed to ensure consistent data between test runs. // Default: true + * service?: scalar|Param|null, // Service id for custom faker instance. // Default: null + * }, + * instantiator?: array{ // Configure the default instantiator used by your object factories. + * use_constructor?: bool|Param, // Use the constructor to instantiate objects. // Default: true + * allow_extra_attributes?: bool|Param, // Whether or not to skip attributes that do not correspond to properties. // Default: false + * always_force_properties?: bool|Param, // Whether or not to skip setters and force set object properties (public/private/protected) directly. // Default: false + * service?: scalar|Param|null, // Service id of your custom instantiator. // Default: null + * }, + * global_state?: list, + * persistence?: array{ + * flush_once?: bool|Param, // Flush only once per call of `PersistentObjectFactory::create()` in userland. // Default: false + * }, + * orm?: array{ + * auto_persist?: bool|Param, // Deprecated: Since 2.4 auto_persist defaults to true and this configuration has no effect. // Automatically persist entities when created. // Default: true + * reset?: array{ + * connections?: list, + * entity_managers?: list, + * mode?: \Zenstruck\Foundry\ORM\ResetDatabase\ResetDatabaseMode::SCHEMA|\Zenstruck\Foundry\ORM\ResetDatabase\ResetDatabaseMode::MIGRATE|Param, // Reset mode to use with ResetDatabase trait // Default: "schema" + * migrations?: array{ + * configurations?: list, + * }, + * }, + * }, + * mongo?: array{ + * auto_persist?: bool|Param, // Deprecated: Since 2.4 auto_persist defaults to true and this configuration has no effect. // Automatically persist documents when created. // Default: true + * reset?: array{ + * document_managers?: list, + * }, + * }, + * make_factory?: array{ + * default_namespace?: scalar|Param|null, // Default namespace where factories will be created by maker. // Default: "Factory" + * add_hints?: bool|Param, // Add "beginner" hints in the created factory. // Default: true + * }, + * make_story?: array{ + * default_namespace?: scalar|Param|null, // Default namespace where stories will be created by maker. // Default: "Story" + * }, + * } + * @psalm-type MonologConfig = array{ + * use_microseconds?: scalar|Param|null, // Default: true + * channels?: list, + * handlers?: array, + * }>, + * accepted_levels?: list, + * min_level?: scalar|Param|null, // Default: "DEBUG" + * max_level?: scalar|Param|null, // Default: "EMERGENCY" + * buffer_size?: scalar|Param|null, // Default: 0 + * flush_on_overflow?: bool|Param, // Default: false + * handler?: scalar|Param|null, + * url?: scalar|Param|null, + * exchange?: scalar|Param|null, + * exchange_name?: scalar|Param|null, // Default: "log" + * channel?: scalar|Param|null, // Default: null + * bot_name?: scalar|Param|null, // Default: "Monolog" + * use_attachment?: scalar|Param|null, // Default: true + * use_short_attachment?: scalar|Param|null, // Default: false + * include_extra?: scalar|Param|null, // Default: false + * icon_emoji?: scalar|Param|null, // Default: null + * webhook_url?: scalar|Param|null, + * exclude_fields?: list, + * token?: scalar|Param|null, + * region?: scalar|Param|null, + * source?: scalar|Param|null, + * use_ssl?: bool|Param, // Default: true + * user?: mixed, + * title?: scalar|Param|null, // Default: null + * host?: scalar|Param|null, // Default: null + * port?: scalar|Param|null, // Default: 514 + * config?: list, + * members?: list, + * connection_string?: scalar|Param|null, + * timeout?: scalar|Param|null, + * time?: scalar|Param|null, // Default: 60 + * deduplication_level?: scalar|Param|null, // Default: 400 + * store?: scalar|Param|null, // Default: null + * connection_timeout?: scalar|Param|null, + * persistent?: bool|Param, + * message_type?: scalar|Param|null, // Default: 0 + * parse_mode?: scalar|Param|null, // Default: null + * disable_webpage_preview?: bool|Param|null, // Default: null + * disable_notification?: bool|Param|null, // Default: null + * split_long_messages?: bool|Param, // Default: false + * delay_between_messages?: bool|Param, // Default: false + * topic?: int|Param, // Default: null + * factor?: int|Param, // Default: 1 + * tags?: list, + * console_formatter_options?: mixed, // Default: [] + * formatter?: scalar|Param|null, + * nested?: bool|Param, // Default: false + * publisher?: string|array{ + * id?: scalar|Param|null, + * hostname?: scalar|Param|null, + * port?: scalar|Param|null, // Default: 12201 + * chunk_size?: scalar|Param|null, // Default: 1420 + * encoder?: "json"|"compressed_json"|Param, + * }, + * mongodb?: string|array{ + * id?: scalar|Param|null, // ID of a MongoDB\Client service + * uri?: scalar|Param|null, + * username?: scalar|Param|null, + * password?: scalar|Param|null, + * database?: scalar|Param|null, // Default: "monolog" + * collection?: scalar|Param|null, // Default: "logs" + * }, + * elasticsearch?: string|array{ + * id?: scalar|Param|null, + * hosts?: list, + * host?: scalar|Param|null, + * port?: scalar|Param|null, // Default: 9200 + * transport?: scalar|Param|null, // Default: "Http" + * user?: scalar|Param|null, // Default: null + * password?: scalar|Param|null, // Default: null + * }, + * index?: scalar|Param|null, // Default: "monolog" + * document_type?: scalar|Param|null, // Default: "logs" + * ignore_error?: scalar|Param|null, // Default: false + * redis?: string|array{ + * id?: scalar|Param|null, + * host?: scalar|Param|null, + * password?: scalar|Param|null, // Default: null + * port?: scalar|Param|null, // Default: 6379 + * database?: scalar|Param|null, // Default: 0 + * key_name?: scalar|Param|null, // Default: "monolog_redis" + * }, + * predis?: string|array{ + * id?: scalar|Param|null, + * host?: scalar|Param|null, + * }, + * from_email?: scalar|Param|null, + * to_email?: list, + * subject?: scalar|Param|null, + * content_type?: scalar|Param|null, // Default: null + * headers?: list, + * mailer?: scalar|Param|null, // Default: null + * email_prototype?: string|array{ + * id?: scalar|Param|null, + * method?: scalar|Param|null, // Default: null + * }, + * verbosity_levels?: array{ + * VERBOSITY_QUIET?: scalar|Param|null, // Default: "ERROR" + * VERBOSITY_NORMAL?: scalar|Param|null, // Default: "WARNING" + * VERBOSITY_VERBOSE?: scalar|Param|null, // Default: "NOTICE" + * VERBOSITY_VERY_VERBOSE?: scalar|Param|null, // Default: "INFO" + * VERBOSITY_DEBUG?: scalar|Param|null, // Default: "DEBUG" + * }, + * channels?: string|array{ + * type?: scalar|Param|null, + * elements?: list, + * }, + * }>, + * } + * @psalm-type WebpackEncoreConfig = array{ + * output_path?: scalar|Param|null, // The path where Encore is building the assets - i.e. Encore.setOutputPath() + * crossorigin?: false|"anonymous"|"use-credentials"|Param, // crossorigin value when Encore.enableIntegrityHashes() is used, can be false (default), anonymous or use-credentials // Default: false + * preload?: bool|Param, // preload all rendered script and link tags automatically via the http2 Link header. // Default: false + * cache?: bool|Param, // Enable caching of the entry point file(s) // Default: false + * strict_mode?: bool|Param, // Throw an exception if the entrypoints.json file is missing or an entry is missing from the data // Default: true + * builds?: array, + * script_attributes?: array, + * link_attributes?: array, + * } + * @psalm-type TwigComponentConfig = array{ + * defaults?: array, + * anonymous_template_directory?: scalar|Param|null, // Defaults to `components` + * profiler?: bool|array{ // Enables the profiler for Twig Component + * enabled?: bool|Param, // Default: "%kernel.debug%" + * collect_components?: bool|Param, // Collect components instances // Default: true + * }, + * controllers_json?: scalar|Param|null, // Deprecated: The "twig_component.controllers_json" config option is deprecated, and will be removed in 3.0. // Default: null + * } + * @psalm-type LiveComponentConfig = array{ + * secret?: scalar|Param|null, // The secret used to compute fingerprints and checksums // Default: "%kernel.secret%" + * fetch_credentials?: "same-origin"|"include"|"omit"|Param, // The default fetch credentials mode for all Live Components ('same-origin', 'include', 'omit') // Default: "same-origin" + * } + * @psalm-type StimulusConfig = array{ + * controller_paths?: list, + * controllers_json?: scalar|Param|null, // Default: "%kernel.project_dir%/assets/controllers.json" + * } + * @psalm-type TwigExtraConfig = array{ + * cache?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * html?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * markdown?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * intl?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * cssinliner?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * inky?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * string?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, + * commonmark?: array{ + * renderer?: array{ // Array of options for rendering HTML. + * block_separator?: scalar|Param|null, + * inner_separator?: scalar|Param|null, + * soft_break?: scalar|Param|null, + * }, + * html_input?: "strip"|"allow"|"escape"|Param, // How to handle HTML input. + * allow_unsafe_links?: bool|Param, // Remove risky link and image URLs by setting this to false. // Default: true + * max_nesting_level?: int|Param, // The maximum nesting level for blocks. // Default: 9223372036854775807 + * max_delimiters_per_line?: int|Param, // The maximum number of strong/emphasis delimiters per line. // Default: 9223372036854775807 + * slug_normalizer?: array{ // Array of options for configuring how URL-safe slugs are created. + * instance?: mixed, + * max_length?: int|Param, // Default: 255 + * unique?: mixed, + * }, + * commonmark?: array{ // Array of options for configuring the CommonMark core extension. + * enable_em?: bool|Param, // Default: true + * enable_strong?: bool|Param, // Default: true + * use_asterisk?: bool|Param, // Default: true + * use_underscore?: bool|Param, // Default: true + * unordered_list_markers?: list, + * }, + * ... + * }, + * } + * @psalm-type MercureConfig = array{ + * hubs?: array, + * subscribe?: list, + * secret?: scalar|Param|null, // The JWT Secret to use. + * passphrase?: scalar|Param|null, // The JWT secret passphrase. // Default: "" + * algorithm?: scalar|Param|null, // The algorithm to use to sign the JWT // Default: "hmac.sha256" + * }, + * jwt_provider?: scalar|Param|null, // Deprecated: The child node "jwt_provider" at path "mercure.hubs..jwt_provider" is deprecated, use "jwt.provider" instead. // The ID of a service to call to generate the JSON Web Token. + * bus?: scalar|Param|null, // Name of the Messenger bus where the handler for this hub must be registered. Default to the default bus if Messenger is enabled. + * }>, + * default_hub?: scalar|Param|null, + * default_cookie_lifetime?: int|Param, // Default lifetime of the cookie containing the JWT, in seconds. Defaults to the value of "framework.session.cookie_lifetime". // Default: null + * enable_profiler?: bool|Param, // Deprecated: The child node "enable_profiler" at path "mercure.enable_profiler" is deprecated. // Enable Symfony Web Profiler integration. + * } + * @psalm-type TurboConfig = array{ + * broadcast?: bool|array{ + * enabled?: bool|Param, // Default: true + * entity_template_prefixes?: list, + * doctrine_orm?: bool|array{ // Enable the Doctrine ORM integration + * enabled?: bool|Param, // Default: true + * }, + * }, + * default_transport?: scalar|Param|null, // Default: "default" + * } + * @psalm-type DamaDoctrineTestConfig = array{ + * enable_static_connection?: mixed, // Default: true + * enable_static_meta_data_cache?: bool|Param, // Default: true + * enable_static_query_cache?: bool|Param, // Default: true + * connection_keys?: list, + * } + * @psalm-type ReactConfig = array{ + * controllers_path?: scalar|Param|null, // The path to the directory where React controller components are stored - relevant only when using symfony/asset-mapper. // Default: "%kernel.project_dir%/assets/react/controllers" + * name_glob?: list, + * } + * @psalm-type VichUploaderConfig = array{ + * default_filename_attribute_suffix?: scalar|Param|null, // Default: "_name" + * db_driver?: scalar|Param|null, + * storage?: scalar|Param|null, // Default: "file_system" + * use_flysystem_to_resolve_uri?: bool|Param, // Default: false + * twig?: scalar|Param|null, // twig requires templating // Default: true + * form?: scalar|Param|null, // Default: true + * metadata?: array{ + * cache?: scalar|Param|null, // Default: "file" + * type?: scalar|Param|null, // Default: "attribute" + * file_cache?: array{ + * dir?: scalar|Param|null, // Default: "%kernel.cache_dir%/vich_uploader" + * }, + * auto_detection?: bool|Param, // Default: true + * directories?: list, + * }, + * mappings?: array, + * } + * @psalm-type ConfigType = array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * doctrine?: DoctrineConfig, + * doctrine_migrations?: DoctrineMigrationsConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * nelmio_cors?: NelmioCorsConfig, + * api_platform?: ApiPlatformConfig, + * monolog?: MonologConfig, + * webpack_encore?: WebpackEncoreConfig, + * twig_component?: TwigComponentConfig, + * live_component?: LiveComponentConfig, + * stimulus?: StimulusConfig, + * twig_extra?: TwigExtraConfig, + * mercure?: MercureConfig, + * turbo?: TurboConfig, + * react?: ReactConfig, + * vich_uploader?: VichUploaderConfig, + * "when@dev"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * doctrine?: DoctrineConfig, + * doctrine_migrations?: DoctrineMigrationsConfig, + * maker?: MakerConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * nelmio_cors?: NelmioCorsConfig, + * api_platform?: ApiPlatformConfig, + * web_profiler?: WebProfilerConfig, + * zenstruck_foundry?: ZenstruckFoundryConfig, + * monolog?: MonologConfig, + * webpack_encore?: WebpackEncoreConfig, + * twig_component?: TwigComponentConfig, + * live_component?: LiveComponentConfig, + * stimulus?: StimulusConfig, + * twig_extra?: TwigExtraConfig, + * mercure?: MercureConfig, + * turbo?: TurboConfig, + * react?: ReactConfig, + * vich_uploader?: VichUploaderConfig, + * }, + * "when@prod"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * doctrine?: DoctrineConfig, + * doctrine_migrations?: DoctrineMigrationsConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * nelmio_cors?: NelmioCorsConfig, + * api_platform?: ApiPlatformConfig, + * monolog?: MonologConfig, + * webpack_encore?: WebpackEncoreConfig, + * twig_component?: TwigComponentConfig, + * live_component?: LiveComponentConfig, + * stimulus?: StimulusConfig, + * twig_extra?: TwigExtraConfig, + * mercure?: MercureConfig, + * turbo?: TurboConfig, + * react?: ReactConfig, + * vich_uploader?: VichUploaderConfig, + * }, + * "when@test"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * doctrine?: DoctrineConfig, + * doctrine_migrations?: DoctrineMigrationsConfig, + * twig?: TwigConfig, + * security?: SecurityConfig, + * nelmio_cors?: NelmioCorsConfig, + * api_platform?: ApiPlatformConfig, + * web_profiler?: WebProfilerConfig, + * zenstruck_foundry?: ZenstruckFoundryConfig, + * monolog?: MonologConfig, + * webpack_encore?: WebpackEncoreConfig, + * twig_component?: TwigComponentConfig, + * live_component?: LiveComponentConfig, + * stimulus?: StimulusConfig, + * twig_extra?: TwigExtraConfig, + * mercure?: MercureConfig, + * turbo?: TurboConfig, + * dama_doctrine_test?: DamaDoctrineTestConfig, + * react?: ReactConfig, + * vich_uploader?: VichUploaderConfig, + * }, + * ..., + * }> + * } + */ +final class App +{ + /** + * @param ConfigType $config + * + * @psalm-return ConfigType + */ + public static function config(array $config): array + { + /** @var ConfigType $config */ + $config = AppReference::config($config); + + return $config; + } +} + +namespace Symfony\Component\Routing\Loader\Configurator; + +/** + * This class provides array-shapes for configuring the routes of an application. + * + * Example: + * + * ```php + * // config/routes.php + * namespace Symfony\Component\Routing\Loader\Configurator; + * + * return Routes::config([ + * 'controllers' => [ + * 'resource' => 'routing.controllers', + * ], + * ]); + * ``` + * + * @psalm-type RouteConfig = array{ + * path: string|array, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type ImportConfig = array{ + * resource: string, + * type?: string, + * exclude?: string|list, + * prefix?: string|array, + * name_prefix?: string, + * trailing_slash_on_root?: bool, + * controller?: string, + * methods?: string|list, + * requirements?: array, + * defaults?: array, + * options?: array, + * host?: string|array, + * schemes?: string|list, + * condition?: string, + * locale?: string, + * format?: string, + * utf8?: bool, + * stateless?: bool, + * } + * @psalm-type AliasConfig = array{ + * alias: string, + * deprecated?: array{package:string, version:string, message?:string}, + * } + * @psalm-type RoutesConfig = array{ + * "when@dev"?: array, + * "when@prod"?: array, + * "when@test"?: array, + * ... + * } + */ +final class Routes +{ + /** + * @param RoutesConfig $config + * + * @psalm-return RoutesConfig + */ + public static function config(array $config): array + { + return $config; + } +} diff --git a/config/routes/framework.yaml b/config/routes/framework.yaml index 0fc74bb..bc1feac 100644 --- a/config/routes/framework.yaml +++ b/config/routes/framework.yaml @@ -1,4 +1,4 @@ when@dev: _errors: - resource: '@FrameworkBundle/Resources/config/routing/errors.xml' + resource: '@FrameworkBundle/Resources/config/routing/errors.php' prefix: /_error diff --git a/config/routes/web_profiler.yaml b/config/routes/web_profiler.yaml index 8d85319..b3b7b4b 100644 --- a/config/routes/web_profiler.yaml +++ b/config/routes/web_profiler.yaml @@ -1,8 +1,8 @@ when@dev: web_profiler_wdt: - resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' + resource: '@WebProfilerBundle/Resources/config/routing/wdt.php' prefix: /_wdt web_profiler_profiler: - resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' + resource: '@WebProfilerBundle/Resources/config/routing/profiler.php' prefix: /_profiler diff --git a/deploy.php b/deploy.php index cbd6aa3..a72efc5 100644 --- a/deploy.php +++ b/deploy.php @@ -1,4 +1,5 @@ /dev/null 2>&1"); + if (null !== $previousDir) { + $lockUnchanged = test("diff -q $previousDir/composer.lock $releaseDir/composer.lock > /dev/null 2>&1"); $vendorPopulated = test("[ -d $releaseDir/vendor/composer ]"); if ($lockUnchanged && $vendorPopulated) { writeln('deploy:vendors skipped — composer.lock unchanged'); + return; } } @@ -56,23 +58,23 @@ task('deploy:vendors', function () { // 3. Cache npm et webpack persistants entre les releases desc('Build Webpack Encore assets'); task('webpack_encore:build', function () { - $sharedDir = '/srv/mangarr/shared'; + $sharedDir = '/srv/mangarr/shared'; $sharedWebpackCache = "$sharedDir/webpack_cache"; - $sharedNodeModules = "$sharedDir/node_modules"; - $sharedNpmCache = "$sharedDir/npm_cache"; + $sharedNodeModules = "$sharedDir/node_modules"; + $sharedNpmCache = "$sharedDir/npm_cache"; run("mkdir -p $sharedWebpackCache $sharedNodeModules $sharedNpmCache"); - $releaseDir = get('release_path'); + $releaseDir = get('release_path'); $previousDir = get('previous_release'); // null au 1er déploiement // --- COUCHE 1 : skip total si aucun fichier front-end n'a changé --- - if ($previousDir !== null) { + if (null !== $previousDir) { $watchList = ['assets', 'templates', 'package.json', 'package-lock.json', - 'webpack.config.js', 'postcss.config.js', 'tailwind.config.js']; + 'webpack.config.js', 'postcss.config.js', 'tailwind.config.js']; $diffChecks = implode(' && ', array_map( - fn($p) => "diff -rq --no-dereference $previousDir/$p $releaseDir/$p > /dev/null 2>&1", + fn ($p) => "diff -rq --no-dereference $previousDir/$p $releaseDir/$p > /dev/null 2>&1", $watchList )); @@ -81,15 +83,16 @@ task('webpack_encore:build', function () { if ($hasPreviousBuild && test("($diffChecks)")) { run("cp -al $previousDir/public/build $releaseDir/public/build"); writeln('webpack_encore:build skipped — no front-end files changed'); + return; } } // --- COUCHE 2 : skip npm install si package-lock.json inchangé --- $needsNpmInstall = true; - if ($previousDir !== null) { + if (null !== $previousDir) { $lockUnchanged = test("diff -q $previousDir/package-lock.json $releaseDir/package-lock.json > /dev/null 2>&1"); - $nmPopulated = test("[ -d $sharedNodeModules/.bin ]"); + $nmPopulated = test("[ -d $sharedNodeModules/.bin ]"); if ($lockUnchanged && $nmPopulated) { $needsNpmInstall = false; } diff --git a/frankenphp/worker.Caddyfile b/frankenphp/worker.Caddyfile index d384ae4..eaea189 100644 --- a/frankenphp/worker.Caddyfile +++ b/frankenphp/worker.Caddyfile @@ -1,4 +1,3 @@ worker { file ./public/index.php - env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime } diff --git a/migrations/Version20260326165659.php b/migrations/Version20260326165659.php new file mode 100644 index 0000000..29415c1 --- /dev/null +++ b/migrations/Version20260326165659.php @@ -0,0 +1,97 @@ +connection->fetchAllAssociative('SELECT id, genres FROM manga WHERE genres IS NOT NULL'); + foreach ($rows as $row) { + $raw = $row['genres']; + // Skip if already valid JSON + json_decode($raw); + if (json_last_error() === JSON_ERROR_NONE) { + continue; + } + // Unserialize PHP format and re-encode as JSON + $value = @unserialize($raw); + if ($value === false && $raw !== 'b:0;') { + $value = []; + } + $this->connection->executeStatement( + 'UPDATE manga SET genres = :json WHERE id = :id', + ['json' => json_encode($value), 'id' => $row['id']] + ); + } + } + + public function up(Schema $schema): void + { + $this->addSql('COMMENT ON COLUMN api_token.expires_at IS \'\''); + $this->addSql('COMMENT ON COLUMN content_source.health_last_tested_at IS \'\''); + $this->addSql('COMMENT ON COLUMN failed_job.failed_at IS \'\''); + $this->addSql('COMMENT ON COLUMN job.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN job.started_at IS \'\''); + $this->addSql('COMMENT ON COLUMN job.completed_at IS \'\''); + $this->addSql('ALTER TABLE manga ALTER genres TYPE JSON USING genres::json'); + $this->addSql('COMMENT ON COLUMN manga.genres IS \'\''); + $this->addSql('COMMENT ON COLUMN manga.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN manga.last_monitoring_check IS \'\''); + $this->addSql('COMMENT ON COLUMN manga_preferred_sources.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN manga_preferred_sources.updated_at IS \'\''); + $this->addSql('COMMENT ON COLUMN source.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN source.updated_at IS \'\''); + $this->addSql('DROP INDEX idx_75ea56e0e3bd61ce'); + $this->addSql('DROP INDEX idx_75ea56e0fb7336f0'); + $this->addSql('DROP INDEX idx_75ea56e016ba31db'); + $this->addSql('ALTER TABLE messenger_messages ALTER id DROP DEFAULT'); + $this->addSql('ALTER TABLE messenger_messages ALTER id ADD GENERATED BY DEFAULT AS IDENTITY'); + $this->addSql('COMMENT ON COLUMN messenger_messages.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN messenger_messages.available_at IS \'\''); + $this->addSql('COMMENT ON COLUMN messenger_messages.delivered_at IS \'\''); + $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('COMMENT ON COLUMN api_token.expires_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN content_source.health_last_tested_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN failed_job.failed_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN job.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN job.started_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN job.completed_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE manga ALTER genres TYPE TEXT'); + $this->addSql('COMMENT ON COLUMN manga.genres IS \'(DC2Type:array)\''); + $this->addSql('COMMENT ON COLUMN manga.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN manga.last_monitoring_check IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN manga_preferred_sources.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN manga_preferred_sources.updated_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('DROP INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750'); + $this->addSql('ALTER TABLE messenger_messages ALTER id SET DEFAULT nextval(\'messenger_messages_id_seq\'::regclass)'); + $this->addSql('ALTER TABLE messenger_messages ALTER id DROP IDENTITY'); + $this->addSql('COMMENT ON COLUMN messenger_messages.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN messenger_messages.available_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN messenger_messages.delivered_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE INDEX idx_75ea56e0e3bd61ce ON messenger_messages (available_at)'); + $this->addSql('CREATE INDEX idx_75ea56e0fb7336f0 ON messenger_messages (queue_name)'); + $this->addSql('CREATE INDEX idx_75ea56e016ba31db ON messenger_messages (delivered_at)'); + $this->addSql('COMMENT ON COLUMN source.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN source.updated_at IS \'(DC2Type:datetime_immutable)\''); + } +} diff --git a/phparkitect.php b/phparkitect.php index c2d3b72..465d497 100644 --- a/phparkitect.php +++ b/phparkitect.php @@ -11,7 +11,7 @@ use Arkitect\Expression\ForClasses\ResideInOneOfTheseNamespaces; use Arkitect\Rules\Rule; return static function (Config $config): void { - $domainClassSet = ClassSet::fromDir(__DIR__ . '/src/Domain'); + $domainClassSet = ClassSet::fromDir(__DIR__.'/src/Domain'); $businessDomains = ['Manga', 'Reader', 'Scraping', 'Conversion']; // Classes PHP standards et utilitaires @@ -29,7 +29,7 @@ return static function (Config $config): void { // Dépendances externes autorisées $externalDependencies = [ 'Symfony\Component\Messenger', - 'Ramsey\Uuid' + 'Ramsey\Uuid', ]; // Règle pour le namespace cohérent @@ -72,7 +72,7 @@ return static function (Config $config): void { // Interdiction explicite pour l'Application d'accéder à l'Infrastructure $rules[] = Rule::allClasses() ->that(new ResideInOneOfTheseNamespaces("App\Domain\\$domain\Application")) - ->should(new NotDependsOnTheseNamespaces("App\Domain\\$domain\Infrastructure")) + ->should(new NotDependsOnTheseNamespaces(["App\Domain\\$domain\Infrastructure"])) ->because("la couche Application de $domain ne doit jamais dépendre de l'Infrastructure, même au sein de son propre domaine"); } diff --git a/src/Command/SendTestNotificationCommand.php b/src/Command/SendTestNotificationCommand.php index 76fe9bf..7ff54ce 100644 --- a/src/Command/SendTestNotificationCommand.php +++ b/src/Command/SendTestNotificationCommand.php @@ -18,7 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface; class SendTestNotificationCommand extends Command { public function __construct( - private readonly NotificationInterface $notification + private readonly NotificationInterface $notification, ) { parent::__construct(); } @@ -38,14 +38,15 @@ class SendTestNotificationCommand extends Command $allowed = ['info', 'success', 'error', 'warning']; if (!in_array($type, $allowed, true)) { $output->writeln(sprintf('Type invalide "%s". Valeurs acceptées : %s', $type, implode(', ', $allowed))); + return Command::FAILURE; } match ($type) { 'success' => $this->notification->sendSuccess($message), - 'error' => $this->notification->sendError($message), + 'error' => $this->notification->sendError($message), 'warning' => $this->notification->sendWarning($message), - default => $this->notification->sendInfo($message), + default => $this->notification->sendInfo($message), }; $output->writeln(sprintf('[%s] Notification envoyée : %s', strtoupper($type), $message)); diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 22a032e..d67fec9 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -4,7 +4,6 @@ namespace App\Controller; use ApiPlatform\Api\IriConverterInterface; use App\Entity\User; -use Exception; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -13,11 +12,11 @@ use Symfony\Component\Security\Http\Attribute\CurrentUser; class SecurityController extends AbstractController { #[Route('/login', name: 'app_login', methods: ['GET', 'POST'])] - public function login(IriConverterInterface $iriConverter, #[CurrentUser] User $user = null): Response + public function login(IriConverterInterface $iriConverter, #[CurrentUser] ?User $user = null): Response { if (!$user) { return $this->json([ - 'error' => 'Invalid credentials' + 'error' => 'Invalid credentials', ], 401); } @@ -27,11 +26,11 @@ class SecurityController extends AbstractController } /** - * @throws Exception + * @throws \Exception */ #[Route('/logout', name: 'app_logout', methods: ['GET'])] public function logout(): void { - throw new Exception('This method can be blank.'); + throw new \Exception('This method can be blank.'); } } diff --git a/src/Domain/Conversion/Application/Command/ConvertFileCommand.php b/src/Domain/Conversion/Application/Command/ConvertFileCommand.php index 75e64c7..16e62bc 100644 --- a/src/Domain/Conversion/Application/Command/ConvertFileCommand.php +++ b/src/Domain/Conversion/Application/Command/ConvertFileCommand.php @@ -7,7 +7,7 @@ final readonly class ConvertFileCommand public function __construct( public string $filePath, public string $originalFilename, - public int $fileSize + public int $fileSize, ) { } } diff --git a/src/Domain/Conversion/Application/CommandHandler/ConvertFileCommandHandler.php b/src/Domain/Conversion/Application/CommandHandler/ConvertFileCommandHandler.php index 1279589..3054f1b 100644 --- a/src/Domain/Conversion/Application/CommandHandler/ConvertFileCommandHandler.php +++ b/src/Domain/Conversion/Application/CommandHandler/ConvertFileCommandHandler.php @@ -13,7 +13,7 @@ final readonly class ConvertFileCommandHandler private const MAX_FILE_SIZE = 150 * 1024 * 1024; // 150MB public function __construct( - private ConversionServiceInterface $conversionService + private ConversionServiceInterface $conversionService, ) { } diff --git a/src/Domain/Conversion/Application/Response/ConversionResponse.php b/src/Domain/Conversion/Application/Response/ConversionResponse.php index 220287b..079f475 100644 --- a/src/Domain/Conversion/Application/Response/ConversionResponse.php +++ b/src/Domain/Conversion/Application/Response/ConversionResponse.php @@ -10,7 +10,7 @@ final readonly class ConversionResponse public string $convertedFilePath, public string $outputFilename, public int $originalFileSize, - public int $convertedFileSize + public int $convertedFileSize, ) { } diff --git a/src/Domain/Conversion/Domain/Exception/ConversionException.php b/src/Domain/Conversion/Domain/Exception/ConversionException.php index d9d847a..cb852ea 100644 --- a/src/Domain/Conversion/Domain/Exception/ConversionException.php +++ b/src/Domain/Conversion/Domain/Exception/ConversionException.php @@ -2,9 +2,7 @@ namespace App\Domain\Conversion\Domain\Exception; -use RuntimeException; - -class ConversionException extends RuntimeException +class ConversionException extends \RuntimeException { public static function fileNotFound(string $filePath): self { diff --git a/src/Domain/Conversion/Domain/Model/ConversionRequest.php b/src/Domain/Conversion/Domain/Model/ConversionRequest.php index ed7f817..95e71f6 100644 --- a/src/Domain/Conversion/Domain/Model/ConversionRequest.php +++ b/src/Domain/Conversion/Domain/Model/ConversionRequest.php @@ -7,7 +7,7 @@ final readonly class ConversionRequest public function __construct( private string $filePath, private string $originalFilename, - private int $fileSize + private int $fileSize, ) { } @@ -29,6 +29,7 @@ final readonly class ConversionRequest public function getOutputFilename(): string { $pathInfo = pathinfo($this->originalFilename, PATHINFO_FILENAME); - return $pathInfo . '.cbz'; + + return $pathInfo.'.cbz'; } } diff --git a/src/Domain/Conversion/Domain/Model/ConversionResult.php b/src/Domain/Conversion/Domain/Model/ConversionResult.php index 8c94a15..dbae627 100644 --- a/src/Domain/Conversion/Domain/Model/ConversionResult.php +++ b/src/Domain/Conversion/Domain/Model/ConversionResult.php @@ -8,7 +8,7 @@ final readonly class ConversionResult private string $convertedFilePath, private string $outputFilename, private int $originalFileSize, - private int $convertedFileSize + private int $convertedFileSize, ) { } diff --git a/src/Domain/Conversion/Infrastructure/ApiPlatform/Controller/ConvertFileController.php b/src/Domain/Conversion/Infrastructure/ApiPlatform/Controller/ConvertFileController.php index 7517aa5..e7a951c 100644 --- a/src/Domain/Conversion/Infrastructure/ApiPlatform/Controller/ConvertFileController.php +++ b/src/Domain/Conversion/Infrastructure/ApiPlatform/Controller/ConvertFileController.php @@ -5,18 +5,16 @@ namespace App\Domain\Conversion\Infrastructure\ApiPlatform\Controller; use App\Domain\Conversion\Application\Command\ConvertFileCommand; use App\Domain\Conversion\Application\CommandHandler\ConvertFileCommandHandler; use App\Domain\Conversion\Domain\Exception\ConversionException; -use App\Domain\Conversion\Infrastructure\ApiPlatform\Resource\ConvertFileResource; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; #[AsController] final class ConvertFileController extends AbstractController { public function __construct( - private readonly ConvertFileCommandHandler $commandHandler + private readonly ConvertFileCommandHandler $commandHandler, ) { } @@ -25,7 +23,7 @@ final class ConvertFileController extends AbstractController $uploadedFile = $request->files->get('file'); if (!$uploadedFile) { return $this->json([ - ['propertyPath' => 'file', 'message' => 'Please upload a file'] + ['propertyPath' => 'file', 'message' => 'Please upload a file'], ], 422); } @@ -58,7 +56,6 @@ final class ConvertFileController extends AbstractController 'Content-Disposition' => sprintf('attachment; filename=%s', $response->outputFilename), ] ); - } catch (ConversionException $e) { return $this->json(['error' => $e->getMessage()], 400); } @@ -72,8 +69,9 @@ final class ConvertFileController extends AbstractController if (!$uploadedFile->isValid()) { $errors[] = [ 'propertyPath' => 'file', - 'message' => 'The uploaded file is not valid: ' . $uploadedFile->getErrorMessage() + 'message' => 'The uploaded file is not valid: '.$uploadedFile->getErrorMessage(), ]; + return $errors; } @@ -82,7 +80,7 @@ final class ConvertFileController extends AbstractController if ($uploadedFile->getSize() > $maxSize) { $errors[] = [ 'propertyPath' => 'file', - 'message' => 'The uploaded file is too large. Allowed size is 150MB.' + 'message' => 'The uploaded file is too large. Allowed size is 150MB.', ]; } @@ -93,7 +91,7 @@ final class ConvertFileController extends AbstractController if (!in_array($extension, $allowedExtensions)) { $errors[] = [ 'propertyPath' => 'file', - 'message' => 'Please upload a valid CBR or CBZ file' + 'message' => 'Please upload a valid CBR or CBZ file', ]; } diff --git a/src/Domain/Conversion/Infrastructure/ApiPlatform/Resource/ConvertFileResource.php b/src/Domain/Conversion/Infrastructure/ApiPlatform/Resource/ConvertFileResource.php index 8dd5b34..6ba838a 100644 --- a/src/Domain/Conversion/Infrastructure/ApiPlatform/Resource/ConvertFileResource.php +++ b/src/Domain/Conversion/Infrastructure/ApiPlatform/Resource/ConvertFileResource.php @@ -4,10 +4,10 @@ namespace App\Domain\Conversion\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; -use ApiPlatform\OpenApi\Model; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\RequestBody; use App\Domain\Conversion\Infrastructure\ApiPlatform\Controller\ConvertFileController; use Symfony\Component\HttpFoundation\File\File; -use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( shortName: 'Conversion', @@ -16,11 +16,11 @@ use Symfony\Component\Validator\Constraints as Assert; uriTemplate: '/conversions/convert', controller: ConvertFileController::class, deserialize: false, - openapiContext: [ - 'summary' => 'Convert comic book file to CBZ', - 'description' => 'Converts a CBR or CBZ file to CBZ format and returns the converted file for download', - 'requestBody' => [ - 'content' => [ + openapi: new Operation( + summary: 'Convert comic book file to CBZ', + description: 'Converts a CBR or CBZ file to CBZ format and returns the converted file for download', + requestBody: new RequestBody( + content: new \ArrayObject([ 'multipart/form-data' => [ 'schema' => [ 'type' => 'object', @@ -29,28 +29,28 @@ use Symfony\Component\Validator\Constraints as Assert; 'file' => [ 'type' => 'string', 'format' => 'binary', - 'description' => 'Comic book file to convert (CBR, CBZ, max 150MB)' - ] - ] - ] - ] - ] - ], - 'responses' => [ + 'description' => 'Comic book file to convert (CBR, CBZ, max 150MB)', + ], + ], + ], + ], + ]) + ), + responses: [ '200' => [ 'description' => 'File converted successfully', 'content' => [ 'application/x-cbz' => [ 'schema' => [ 'type' => 'string', - 'format' => 'binary' - ] - ] - ] - ] + 'format' => 'binary', + ], + ], + ], + ], ] - ] - ) + ) + ), ] )] class ConvertFileResource diff --git a/src/Domain/Conversion/Infrastructure/Service/ConversionService.php b/src/Domain/Conversion/Infrastructure/Service/ConversionService.php index 2609cc8..0ddfddb 100644 --- a/src/Domain/Conversion/Infrastructure/Service/ConversionService.php +++ b/src/Domain/Conversion/Infrastructure/Service/ConversionService.php @@ -16,7 +16,7 @@ final class ConversionService implements ConversionServiceInterface public function __construct(string $projectDir) { - $this->tempDir = $projectDir . '/public/tmp'; + $this->tempDir = $projectDir.'/public/tmp'; $this->filesystem = new Filesystem(); } @@ -40,10 +40,10 @@ final class ConversionService implements ConversionServiceInterface private function convertCbrToCbz(string $cbrPath): string { - $tempDir = $this->tempDir . '/' . uniqid('cbr_conversion_'); + $tempDir = $this->tempDir.'/'.uniqid('cbr_conversion_'); $this->filesystem->mkdir($tempDir); - $extractDir = $tempDir . '/extract'; + $extractDir = $tempDir.'/extract'; $this->filesystem->mkdir($extractDir); // Essayer d'extraire avec unrar-free @@ -56,16 +56,16 @@ final class ConversionService implements ConversionServiceInterface $process->run(); if (!$process->isSuccessful()) { - throw new \RuntimeException("Extraction failed: " . $process->getErrorOutput()); + throw new \RuntimeException('Extraction failed: '.$process->getErrorOutput()); } } // Créer le CBZ - $cbzFileName = pathinfo($cbrPath, PATHINFO_FILENAME) . '.cbz'; - $cbzPath = $this->tempDir . '/' . $cbzFileName; + $cbzFileName = pathinfo($cbrPath, PATHINFO_FILENAME).'.cbz'; + $cbzPath = $this->tempDir.'/'.$cbzFileName; $zip = new \ZipArchive(); - if ($zip->open($cbzPath, \ZipArchive::CREATE) !== true) { - throw new \RuntimeException("Cannot create ZIP file"); + if (true !== $zip->open($cbzPath, \ZipArchive::CREATE)) { + throw new \RuntimeException('Cannot create ZIP file'); } $files = new \RecursiveIteratorIterator( diff --git a/src/Domain/Manga/Application/Command/ChapterEditData.php b/src/Domain/Manga/Application/Command/ChapterEditData.php index f73c749..75dd3dc 100644 --- a/src/Domain/Manga/Application/Command/ChapterEditData.php +++ b/src/Domain/Manga/Application/Command/ChapterEditData.php @@ -7,7 +7,7 @@ readonly class ChapterEditData public function __construct( public string $id, public ?string $title = null, - public ?int $volume = null + public ?int $volume = null, ) { } } diff --git a/src/Domain/Manga/Application/Command/CheckMonitoredMangas.php b/src/Domain/Manga/Application/Command/CheckMonitoredMangas.php index d0ff19a..28e3d67 100644 --- a/src/Domain/Manga/Application/Command/CheckMonitoredMangas.php +++ b/src/Domain/Manga/Application/Command/CheckMonitoredMangas.php @@ -2,12 +2,10 @@ namespace App\Domain\Manga\Application\Command; -use DateTimeImmutable; - readonly class CheckMonitoredMangas { public function __construct( - public ?DateTimeImmutable $since = null + public ?\DateTimeImmutable $since = null, ) { } } diff --git a/src/Domain/Manga/Application/Command/CreateManga.php b/src/Domain/Manga/Application/Command/CreateManga.php index ab8858e..aafbb19 100644 --- a/src/Domain/Manga/Application/Command/CreateManga.php +++ b/src/Domain/Manga/Application/Command/CreateManga.php @@ -14,7 +14,7 @@ readonly class CreateManga public string $status, public ?string $externalId, public ?string $imageUrl, - public ?float $rating + public ?float $rating, ) { } } diff --git a/src/Domain/Manga/Application/Command/CreateMangaFromMangadex.php b/src/Domain/Manga/Application/Command/CreateMangaFromMangadex.php index 7d3b17a..e870c44 100644 --- a/src/Domain/Manga/Application/Command/CreateMangaFromMangadex.php +++ b/src/Domain/Manga/Application/Command/CreateMangaFromMangadex.php @@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Command; readonly class CreateMangaFromMangadex { public function __construct( - public string $externalId + public string $externalId, ) { } } diff --git a/src/Domain/Manga/Application/Command/DeleteCbz.php b/src/Domain/Manga/Application/Command/DeleteCbz.php index d483802..7c388de 100644 --- a/src/Domain/Manga/Application/Command/DeleteCbz.php +++ b/src/Domain/Manga/Application/Command/DeleteCbz.php @@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface; readonly class DeleteCbz implements CommandInterface { public function __construct( - public string $chapterId + public string $chapterId, ) { } } diff --git a/src/Domain/Manga/Application/Command/DeleteChapter.php b/src/Domain/Manga/Application/Command/DeleteChapter.php index 46f8acf..b500de6 100644 --- a/src/Domain/Manga/Application/Command/DeleteChapter.php +++ b/src/Domain/Manga/Application/Command/DeleteChapter.php @@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface; readonly class DeleteChapter implements CommandInterface { public function __construct( - public string $chapterId + public string $chapterId, ) { } } diff --git a/src/Domain/Manga/Application/Command/DeleteManga.php b/src/Domain/Manga/Application/Command/DeleteManga.php index 3e3912f..b791658 100644 --- a/src/Domain/Manga/Application/Command/DeleteManga.php +++ b/src/Domain/Manga/Application/Command/DeleteManga.php @@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface; readonly class DeleteManga implements CommandInterface { public function __construct( - public string $mangaId + public string $mangaId, ) { } } diff --git a/src/Domain/Manga/Application/Command/EditManga.php b/src/Domain/Manga/Application/Command/EditManga.php index 41a4608..a6669ab 100644 --- a/src/Domain/Manga/Application/Command/EditManga.php +++ b/src/Domain/Manga/Application/Command/EditManga.php @@ -13,7 +13,7 @@ readonly class EditManga public ?array $genres = null, public ?string $status = null, public ?float $rating = null, - public ?array $alternativeSlugs = null + public ?array $alternativeSlugs = null, ) { } } diff --git a/src/Domain/Manga/Application/Command/EditMultipleChapters.php b/src/Domain/Manga/Application/Command/EditMultipleChapters.php index 2cdf80b..116eb0b 100644 --- a/src/Domain/Manga/Application/Command/EditMultipleChapters.php +++ b/src/Domain/Manga/Application/Command/EditMultipleChapters.php @@ -8,7 +8,7 @@ readonly class EditMultipleChapters * @param array $chapters */ public function __construct( - public array $chapters + public array $chapters, ) { } } diff --git a/src/Domain/Manga/Application/Command/FetchMangaChapters.php b/src/Domain/Manga/Application/Command/FetchMangaChapters.php index bbfe31c..4e78ef4 100644 --- a/src/Domain/Manga/Application/Command/FetchMangaChapters.php +++ b/src/Domain/Manga/Application/Command/FetchMangaChapters.php @@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaId; readonly class FetchMangaChapters { public function __construct( - public MangaId $mangaId + public MangaId $mangaId, ) { } } diff --git a/src/Domain/Manga/Application/Command/ImportChapter.php b/src/Domain/Manga/Application/Command/ImportChapter.php index cfc4ca8..32ed14c 100644 --- a/src/Domain/Manga/Application/Command/ImportChapter.php +++ b/src/Domain/Manga/Application/Command/ImportChapter.php @@ -7,7 +7,7 @@ readonly class ImportChapter public function __construct( public string $mangaId, public float $chapterNumber, - public string $fileBinary + public string $fileBinary, ) { } } diff --git a/src/Domain/Manga/Application/Command/ImportVolume.php b/src/Domain/Manga/Application/Command/ImportVolume.php index 424e616..8c60bea 100644 --- a/src/Domain/Manga/Application/Command/ImportVolume.php +++ b/src/Domain/Manga/Application/Command/ImportVolume.php @@ -7,7 +7,7 @@ readonly class ImportVolume public function __construct( public string $mangaId, public int $volumeNumber, - public string $fileBinary + public string $fileBinary, ) { } } diff --git a/src/Domain/Manga/Application/Command/RefreshMangaChapters.php b/src/Domain/Manga/Application/Command/RefreshMangaChapters.php index c99f3c9..fbaeb46 100644 --- a/src/Domain/Manga/Application/Command/RefreshMangaChapters.php +++ b/src/Domain/Manga/Application/Command/RefreshMangaChapters.php @@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaId; readonly class RefreshMangaChapters { public function __construct( - public MangaId $mangaId + public MangaId $mangaId, ) { } } diff --git a/src/Domain/Manga/Application/Command/ToggleMangaMonitoring.php b/src/Domain/Manga/Application/Command/ToggleMangaMonitoring.php index 3aa44bf..7e7fe38 100644 --- a/src/Domain/Manga/Application/Command/ToggleMangaMonitoring.php +++ b/src/Domain/Manga/Application/Command/ToggleMangaMonitoring.php @@ -8,7 +8,7 @@ readonly class ToggleMangaMonitoring { public function __construct( public MangaId $mangaId, - public bool $enabled + public bool $enabled, ) { } } diff --git a/src/Domain/Manga/Application/CommandHandler/CheckMonitoredMangasHandler.php b/src/Domain/Manga/Application/CommandHandler/CheckMonitoredMangasHandler.php index 35bdfbd..abb1b04 100644 --- a/src/Domain/Manga/Application/CommandHandler/CheckMonitoredMangasHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/CheckMonitoredMangasHandler.php @@ -6,14 +6,13 @@ use App\Domain\Manga\Application\Command\CheckMonitoredMangas; use App\Domain\Manga\Application\Command\RefreshMangaChapters; use App\Domain\Manga\Application\Query\MonitoringCriteria; use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface; -use DateTimeImmutable; use Symfony\Component\Messenger\MessageBusInterface; readonly class CheckMonitoredMangasHandler { public function __construct( private MangaRepositoryInterface $mangaRepository, - private MessageBusInterface $commandBus + private MessageBusInterface $commandBus, ) { } @@ -21,7 +20,7 @@ readonly class CheckMonitoredMangasHandler { $criteria = new MonitoringCriteria( enabled: true, - lastCheckBefore: $command->since ?? new DateTimeImmutable('-1 hour') + lastCheckBefore: $command->since ?? new \DateTimeImmutable('-1 hour') ); $monitoredMangas = $this->mangaRepository->findByMonitoringCriteria($criteria); diff --git a/src/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandler.php b/src/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandler.php index 6d4f7f1..f68b700 100644 --- a/src/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandler.php @@ -3,7 +3,6 @@ namespace App\Domain\Manga\Application\CommandHandler; use App\Domain\Manga\Application\Command\CreateMangaFromMangadex; -use App\Domain\Manga\Application\Response\CreateMangaResponse; use App\Domain\Manga\Domain\Contract\Provider\MangaProviderInterface; use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface; use App\Domain\Manga\Domain\Contract\Service\ImageProcessorInterface; @@ -19,7 +18,7 @@ readonly class CreateMangaFromMangadexHandler private MangaProviderInterface $mangaProvider, private MangaRepositoryInterface $mangaRepository, private ImageProcessorInterface $imageProcessor, - private EventDispatcherInterface $eventDispatcher + private EventDispatcherInterface $eventDispatcher, ) { } @@ -27,7 +26,7 @@ readonly class CreateMangaFromMangadexHandler { $manga = $this->mangaProvider->findByExternalId(new ExternalId($command->externalId)); - if ($manga === null) { + if (null === $manga) { throw new MangaNotFoundException('Manga not found on Mangadex'); } @@ -41,7 +40,7 @@ readonly class CreateMangaFromMangadexHandler // Met à jour le manga avec les nouveaux chemins d'images $manga->updateImageUrls(new ImageUrls($fullImagePath, $thumbnailPath)); } catch (\Exception $e) { - throw new \RuntimeException('Erreur lors du traitement de l\'image : ' . $e->getMessage()); + throw new \RuntimeException('Erreur lors du traitement de l\'image : '.$e->getMessage()); } $this->mangaRepository->save($manga); diff --git a/src/Domain/Manga/Application/CommandHandler/CreateMangaHandler.php b/src/Domain/Manga/Application/CommandHandler/CreateMangaHandler.php index 74f5e8f..ddd464a 100644 --- a/src/Domain/Manga/Application/CommandHandler/CreateMangaHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/CreateMangaHandler.php @@ -20,7 +20,7 @@ readonly class CreateMangaHandler public function __construct( private MangaRepositoryInterface $mangaRepository, private ImageProcessorInterface $imageProcessor, - private MessageBusInterface $messageBus + private MessageBusInterface $messageBus, ) { } @@ -48,7 +48,7 @@ readonly class CreateMangaHandler $thumbnailPath = $this->imageProcessor->createThumbnail($fullImagePath); $manga->updateImageUrls(new ImageUrls($fullImagePath, $thumbnailPath)); } catch (\Exception $e) { - throw new \RuntimeException('Erreur lors du traitement de l\'image : ' . $e->getMessage()); + throw new \RuntimeException('Erreur lors du traitement de l\'image : '.$e->getMessage()); } } diff --git a/src/Domain/Manga/Application/CommandHandler/DeleteCbzHandler.php b/src/Domain/Manga/Application/CommandHandler/DeleteCbzHandler.php index 517f9c1..6c282f7 100644 --- a/src/Domain/Manga/Application/CommandHandler/DeleteCbzHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/DeleteCbzHandler.php @@ -5,8 +5,8 @@ namespace App\Domain\Manga\Application\CommandHandler; use App\Domain\Manga\Application\Command\DeleteCbz; use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface; use App\Domain\Manga\Domain\Contract\Service\FileServiceInterface; -use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; use App\Domain\Manga\Domain\Exception\CbzFileNotFoundException; +use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; use App\Domain\Shared\Domain\Contract\CommandHandlerInterface; use App\Domain\Shared\Domain\Contract\CommandInterface; @@ -14,7 +14,7 @@ readonly class DeleteCbzHandler implements CommandHandlerInterface { public function __construct( private MangaRepositoryInterface $mangaRepository, - private FileServiceInterface $fileService + private FileServiceInterface $fileService, ) { } diff --git a/src/Domain/Manga/Application/CommandHandler/DeleteChapterHandler.php b/src/Domain/Manga/Application/CommandHandler/DeleteChapterHandler.php index 51d4f58..3ac4499 100644 --- a/src/Domain/Manga/Application/CommandHandler/DeleteChapterHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/DeleteChapterHandler.php @@ -11,7 +11,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface; readonly class DeleteChapterHandler implements CommandHandlerInterface { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } diff --git a/src/Domain/Manga/Application/CommandHandler/DeleteMangaHandler.php b/src/Domain/Manga/Application/CommandHandler/DeleteMangaHandler.php index 521b08a..8d3e642 100644 --- a/src/Domain/Manga/Application/CommandHandler/DeleteMangaHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/DeleteMangaHandler.php @@ -11,7 +11,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface; readonly class DeleteMangaHandler implements CommandHandlerInterface { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } diff --git a/src/Domain/Manga/Application/CommandHandler/EditMangaHandler.php b/src/Domain/Manga/Application/CommandHandler/EditMangaHandler.php index b6004c4..41a039a 100644 --- a/src/Domain/Manga/Application/CommandHandler/EditMangaHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/EditMangaHandler.php @@ -10,7 +10,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle; readonly class EditMangaHandler { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } @@ -23,35 +23,35 @@ readonly class EditMangaHandler } // Update only provided fields (partial update) - if ($command->title !== null) { + if (null !== $command->title) { $manga->updateTitle(new MangaTitle($command->title)); } - if ($command->description !== null) { + if (null !== $command->description) { $manga->updateDescription($command->description); } - if ($command->author !== null) { + if (null !== $command->author) { $manga->updateAuthor($command->author); } - if ($command->publicationYear !== null) { + if (null !== $command->publicationYear) { $manga->updatePublicationYear($command->publicationYear); } - if ($command->genres !== null) { + if (null !== $command->genres) { $manga->updateGenres($command->genres); } - if ($command->status !== null) { + if (null !== $command->status) { $manga->updateStatus($command->status); } - if ($command->rating !== null) { + if (null !== $command->rating) { $manga->setRating($command->rating); } - if ($command->alternativeSlugs !== null) { + if (null !== $command->alternativeSlugs) { $manga->updateAlternativeSlugs($command->alternativeSlugs); } diff --git a/src/Domain/Manga/Application/CommandHandler/EditMultipleChaptersHandler.php b/src/Domain/Manga/Application/CommandHandler/EditMultipleChaptersHandler.php index 5e4daf8..f230796 100644 --- a/src/Domain/Manga/Application/CommandHandler/EditMultipleChaptersHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/EditMultipleChaptersHandler.php @@ -9,7 +9,7 @@ use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; readonly class EditMultipleChaptersHandler { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } @@ -24,11 +24,11 @@ readonly class EditMultipleChaptersHandler $manga = $this->mangaRepository->findById($chapter->getMangaId()->getValue()); - if ($chapterData->title !== null) { + if (null !== $chapterData->title) { $manga->updateChapterTitle($chapter, $chapterData->title); } - if ($chapterData->volume !== null) { + if (null !== $chapterData->volume) { $manga->updateChapterVolume($chapter, $chapterData->volume); } diff --git a/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php b/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php index e37804e..1e7abe6 100644 --- a/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/FetchMangaChaptersHandler.php @@ -12,7 +12,7 @@ readonly class FetchMangaChaptersHandler { public function __construct( private MangaRepositoryInterface $mangaRepository, - private ChapterSynchronizationServiceInterface $chapterSynchronizationService + private ChapterSynchronizationServiceInterface $chapterSynchronizationService, ) { } @@ -20,12 +20,12 @@ readonly class FetchMangaChaptersHandler { $manga = $this->mangaRepository->findById($command->mangaId->getValue()); - if ($manga === null) { + if (null === $manga) { throw new MangaNotFoundException(); } - if ($manga->getExternalId() === null) { - throw new MangadexApiException("Manga has no external_id"); + if (null === $manga->getExternalId()) { + throw new MangadexApiException('Manga has no external_id'); } // Synchronisation initiale (pas d'événements) diff --git a/src/Domain/Manga/Application/CommandHandler/ImportChapterHandler.php b/src/Domain/Manga/Application/CommandHandler/ImportChapterHandler.php index 73a4a60..7681bec 100644 --- a/src/Domain/Manga/Application/CommandHandler/ImportChapterHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/ImportChapterHandler.php @@ -4,15 +4,15 @@ namespace App\Domain\Manga\Application\CommandHandler; use App\Domain\Manga\Application\Command\ImportChapter; use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface; -use App\Domain\Manga\Domain\Exception\MangaNotFoundException; use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; +use App\Domain\Manga\Domain\Exception\MangaNotFoundException; use App\Domain\Shared\Domain\Contract\ImageStorageInterface; readonly class ImportChapterHandler { public function __construct( private MangaRepositoryInterface $mangaRepository, - private ImageStorageInterface $imageStorage + private ImageStorageInterface $imageStorage, ) { } @@ -55,6 +55,6 @@ readonly class ImportChapterHandler { $zipMagicNumber = "\x50\x4b\x03\x04"; // PK\x03\x04 - return strpos($fileBinary, $zipMagicNumber) === 0; + return 0 === strpos($fileBinary, $zipMagicNumber); } } diff --git a/src/Domain/Manga/Application/CommandHandler/ImportVolumeHandler.php b/src/Domain/Manga/Application/CommandHandler/ImportVolumeHandler.php index 3b6b1cb..a65af3a 100644 --- a/src/Domain/Manga/Application/CommandHandler/ImportVolumeHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/ImportVolumeHandler.php @@ -11,7 +11,7 @@ readonly class ImportVolumeHandler { public function __construct( private MangaRepositoryInterface $mangaRepository, - private ImageStorageInterface $imageStorage + private ImageStorageInterface $imageStorage, ) { } @@ -35,9 +35,7 @@ readonly class ImportVolumeHandler ); if (empty($chapters)) { - throw new \InvalidArgumentException( - "No chapters found for manga {$command->mangaId} in volume {$command->volumeNumber}" - ); + throw new \InvalidArgumentException("No chapters found for manga {$command->mangaId} in volume {$command->volumeNumber}"); } // 4. Extract CBZ into individual images storage (shared directory for all volume chapters) @@ -56,6 +54,6 @@ readonly class ImportVolumeHandler { $zipMagicNumber = "\x50\x4b\x03\x04"; // PK\x03\x04 - return strpos($fileBinary, $zipMagicNumber) === 0; + return 0 === strpos($fileBinary, $zipMagicNumber); } } diff --git a/src/Domain/Manga/Application/CommandHandler/RefreshMangaChaptersHandler.php b/src/Domain/Manga/Application/CommandHandler/RefreshMangaChaptersHandler.php index c5b4751..4e58337 100644 --- a/src/Domain/Manga/Application/CommandHandler/RefreshMangaChaptersHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/RefreshMangaChaptersHandler.php @@ -7,7 +7,6 @@ use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface; use App\Domain\Manga\Domain\Contract\Service\ChapterSynchronizationServiceInterface; use App\Domain\Manga\Domain\Event\ChapterReadyForScraping; use App\Domain\Manga\Domain\Model\ValueObject\ChapterId; -use DateTimeImmutable; use Symfony\Component\Messenger\MessageBusInterface; readonly class RefreshMangaChaptersHandler @@ -15,7 +14,7 @@ readonly class RefreshMangaChaptersHandler public function __construct( private MangaRepositoryInterface $mangaRepository, private ChapterSynchronizationServiceInterface $chapterSynchronizationService, - private MessageBusInterface $eventBus + private MessageBusInterface $eventBus, ) { } @@ -23,7 +22,7 @@ readonly class RefreshMangaChaptersHandler { $manga = $this->mangaRepository->findById($command->mangaId->getValue()); - if ($manga === null) { + if (null === $manga) { throw new \RuntimeException('Manga not found'); } @@ -31,7 +30,7 @@ readonly class RefreshMangaChaptersHandler $newChapterIds = $this->chapterSynchronizationService->synchronizeChapters($manga); // Mise à jour de la date de monitoring - $manga->updateLastMonitoringCheck(new DateTimeImmutable()); + $manga->updateLastMonitoringCheck(new \DateTimeImmutable()); $this->mangaRepository->save($manga); // Événement de scraping pour chaque nouveau chapitre diff --git a/src/Domain/Manga/Application/CommandHandler/ToggleMangaMonitoringHandler.php b/src/Domain/Manga/Application/CommandHandler/ToggleMangaMonitoringHandler.php index f0c3cc1..de7a68b 100644 --- a/src/Domain/Manga/Application/CommandHandler/ToggleMangaMonitoringHandler.php +++ b/src/Domain/Manga/Application/CommandHandler/ToggleMangaMonitoringHandler.php @@ -9,7 +9,7 @@ use App\Domain\Manga\Domain\Exception\MangaNotFoundException; readonly class ToggleMangaMonitoringHandler { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } diff --git a/src/Domain/Manga/Application/EventListener/MangaCreatedEventListener.php b/src/Domain/Manga/Application/EventListener/MangaCreatedEventListener.php index 7a5d85d..9766880 100644 --- a/src/Domain/Manga/Application/EventListener/MangaCreatedEventListener.php +++ b/src/Domain/Manga/Application/EventListener/MangaCreatedEventListener.php @@ -10,7 +10,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaId; readonly class MangaCreatedEventListener { public function __construct( - private FetchMangaChaptersHandler $fetchMangaChaptersHandler + private FetchMangaChaptersHandler $fetchMangaChaptersHandler, ) { } diff --git a/src/Domain/Manga/Application/EventListener/VolumeImportedEventListener.php b/src/Domain/Manga/Application/EventListener/VolumeImportedEventListener.php index 435ccd2..66f5c4c 100644 --- a/src/Domain/Manga/Application/EventListener/VolumeImportedEventListener.php +++ b/src/Domain/Manga/Application/EventListener/VolumeImportedEventListener.php @@ -23,7 +23,7 @@ readonly class VolumeImportedEventListener } $chapters = $this->mangaRepository->findChaptersByMangaIdAndVolume($manga->getId()->getValue(), (int) $event->volume); - if ($chapters === []) { + if ([] === $chapters) { return; } diff --git a/src/Domain/Manga/Application/Query/DownloadCbz.php b/src/Domain/Manga/Application/Query/DownloadCbz.php index fa93b1e..fa6d56b 100644 --- a/src/Domain/Manga/Application/Query/DownloadCbz.php +++ b/src/Domain/Manga/Application/Query/DownloadCbz.php @@ -7,7 +7,7 @@ use App\Domain\Shared\Domain\Contract\QueryInterface; readonly class DownloadCbz implements QueryInterface { public function __construct( - public string $chapterId + public string $chapterId, ) { } } diff --git a/src/Domain/Manga/Application/Query/DownloadVolume.php b/src/Domain/Manga/Application/Query/DownloadVolume.php index 05d7df8..b913b98 100644 --- a/src/Domain/Manga/Application/Query/DownloadVolume.php +++ b/src/Domain/Manga/Application/Query/DownloadVolume.php @@ -8,7 +8,7 @@ readonly class DownloadVolume implements QueryInterface { public function __construct( public string $mangaId, - public int $volume + public int $volume, ) { } } diff --git a/src/Domain/Manga/Application/Query/FindMangaMatchByFilename.php b/src/Domain/Manga/Application/Query/FindMangaMatchByFilename.php index f922747..6e19edc 100644 --- a/src/Domain/Manga/Application/Query/FindMangaMatchByFilename.php +++ b/src/Domain/Manga/Application/Query/FindMangaMatchByFilename.php @@ -7,7 +7,7 @@ namespace App\Domain\Manga\Application\Query; readonly class FindMangaMatchByFilename { public function __construct( - public string $filename + public string $filename, ) { } } diff --git a/src/Domain/Manga/Application/Query/GetMangaById.php b/src/Domain/Manga/Application/Query/GetMangaById.php index a8ee3e4..5e62de5 100644 --- a/src/Domain/Manga/Application/Query/GetMangaById.php +++ b/src/Domain/Manga/Application/Query/GetMangaById.php @@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Query; readonly class GetMangaById { public function __construct( - public string $id + public string $id, ) { } } diff --git a/src/Domain/Manga/Application/Query/GetMangaBySlug.php b/src/Domain/Manga/Application/Query/GetMangaBySlug.php index fe90776..5d42229 100644 --- a/src/Domain/Manga/Application/Query/GetMangaBySlug.php +++ b/src/Domain/Manga/Application/Query/GetMangaBySlug.php @@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Query; readonly class GetMangaBySlug { public function __construct( - public string $slug + public string $slug, ) { } } diff --git a/src/Domain/Manga/Application/Query/GetMangaChapters.php b/src/Domain/Manga/Application/Query/GetMangaChapters.php index eab76fe..9553673 100644 --- a/src/Domain/Manga/Application/Query/GetMangaChapters.php +++ b/src/Domain/Manga/Application/Query/GetMangaChapters.php @@ -8,7 +8,7 @@ readonly class GetMangaChapters public string $mangaId, public ?int $page = 1, public ?int $limit = 20, - public ?string $sortOrder = 'desc' + public ?string $sortOrder = 'desc', ) { } } diff --git a/src/Domain/Manga/Application/Query/GetMangaList.php b/src/Domain/Manga/Application/Query/GetMangaList.php index b4b7494..96c5883 100644 --- a/src/Domain/Manga/Application/Query/GetMangaList.php +++ b/src/Domain/Manga/Application/Query/GetMangaList.php @@ -8,7 +8,7 @@ readonly class GetMangaList public ?int $page = 1, public ?int $limit = 20, public ?string $sortBy = 'title', - public ?string $sortOrder = 'asc' + public ?string $sortOrder = 'asc', ) { } } diff --git a/src/Domain/Manga/Application/Query/MonitoringCriteria.php b/src/Domain/Manga/Application/Query/MonitoringCriteria.php index 0105fa3..f843a76 100644 --- a/src/Domain/Manga/Application/Query/MonitoringCriteria.php +++ b/src/Domain/Manga/Application/Query/MonitoringCriteria.php @@ -2,13 +2,11 @@ namespace App\Domain\Manga\Application\Query; -use DateTimeImmutable; - readonly class MonitoringCriteria { public function __construct( public bool $enabled, - public ?DateTimeImmutable $lastCheckBefore = null + public ?\DateTimeImmutable $lastCheckBefore = null, ) { } } diff --git a/src/Domain/Manga/Application/Query/SearchLocalManga.php b/src/Domain/Manga/Application/Query/SearchLocalManga.php index 7bd0cf7..15c0753 100644 --- a/src/Domain/Manga/Application/Query/SearchLocalManga.php +++ b/src/Domain/Manga/Application/Query/SearchLocalManga.php @@ -7,7 +7,7 @@ readonly class SearchLocalManga public function __construct( public string $query, public int $page = 1, - public int $limit = 20 + public int $limit = 20, ) { } } diff --git a/src/Domain/Manga/Application/Query/SearchManga.php b/src/Domain/Manga/Application/Query/SearchManga.php index 6670252..afcb82f 100644 --- a/src/Domain/Manga/Application/Query/SearchManga.php +++ b/src/Domain/Manga/Application/Query/SearchManga.php @@ -5,7 +5,7 @@ namespace App\Domain\Manga\Application\Query; readonly class SearchManga { public function __construct( - public string $title + public string $title, ) { } } diff --git a/src/Domain/Manga/Application/QueryHandler/DiscoverMangaHandler.php b/src/Domain/Manga/Application/QueryHandler/DiscoverMangaHandler.php index 8937d96..4770e0c 100644 --- a/src/Domain/Manga/Application/QueryHandler/DiscoverMangaHandler.php +++ b/src/Domain/Manga/Application/QueryHandler/DiscoverMangaHandler.php @@ -13,7 +13,7 @@ readonly class DiscoverMangaHandler { public function __construct( private MangaRepositoryInterface $mangaRepository, - private MangaProviderInterface $mangaProvider + private MangaProviderInterface $mangaProvider, ) { } @@ -41,7 +41,7 @@ readonly class DiscoverMangaHandler $recommendations = array_values(array_filter( $collection->getItems(), - fn (Manga $m) => $m->getExternalId() === null + fn (Manga $m) => null === $m->getExternalId() || !in_array($m->getExternalId()->getValue(), $ownedExternalIds, true) )); diff --git a/src/Domain/Manga/Application/QueryHandler/DownloadCbzHandler.php b/src/Domain/Manga/Application/QueryHandler/DownloadCbzHandler.php index b213efa..9d548c7 100644 --- a/src/Domain/Manga/Application/QueryHandler/DownloadCbzHandler.php +++ b/src/Domain/Manga/Application/QueryHandler/DownloadCbzHandler.php @@ -7,8 +7,8 @@ use App\Domain\Manga\Application\Response\DownloadResponse; use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface; use App\Domain\Manga\Domain\Contract\Service\FileServiceInterface; use App\Domain\Manga\Domain\Exception\CbzFileNotFoundException; -use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; use App\Domain\Manga\Domain\Exception\ChapterNotAvailableException; +use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; use App\Domain\Shared\Domain\Contract\QueryHandlerInterface; use App\Domain\Shared\Domain\Contract\QueryInterface; use App\Domain\Shared\Domain\Contract\ResponseInterface; @@ -17,7 +17,7 @@ readonly class DownloadCbzHandler implements QueryHandlerInterface { public function __construct( private MangaRepositoryInterface $mangaRepository, - private FileServiceInterface $fileService + private FileServiceInterface $fileService, ) { } diff --git a/src/Domain/Manga/Application/QueryHandler/DownloadVolumeHandler.php b/src/Domain/Manga/Application/QueryHandler/DownloadVolumeHandler.php index 1ef1069..f53c05b 100644 --- a/src/Domain/Manga/Application/QueryHandler/DownloadVolumeHandler.php +++ b/src/Domain/Manga/Application/QueryHandler/DownloadVolumeHandler.php @@ -16,7 +16,7 @@ readonly class DownloadVolumeHandler implements QueryHandlerInterface { public function __construct( private MangaRepositoryInterface $mangaRepository, - private FileServiceInterface $fileService + private FileServiceInterface $fileService, ) { } diff --git a/src/Domain/Manga/Application/QueryHandler/FindMangaMatchByFilenameHandler.php b/src/Domain/Manga/Application/QueryHandler/FindMangaMatchByFilenameHandler.php index 3d4e36a..316a60a 100644 --- a/src/Domain/Manga/Application/QueryHandler/FindMangaMatchByFilenameHandler.php +++ b/src/Domain/Manga/Application/QueryHandler/FindMangaMatchByFilenameHandler.php @@ -15,7 +15,7 @@ readonly class FindMangaMatchByFilenameHandler { public function __construct( private FilenameAnalyzerInterface $filenameAnalyzer, - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } @@ -70,7 +70,7 @@ readonly class FindMangaMatchByFilenameHandler /** * Calcule un score de correspondance entre le manga et le titre recherché - * Score plus élevé = meilleure correspondance + * Score plus élevé = meilleure correspondance. */ private function calculateMatchScore(Manga $manga, string $searchedTitle): int { @@ -97,12 +97,12 @@ readonly class FindMangaMatchByFilenameHandler } // Le titre du manga contient le terme recherché - if (stripos($mangaTitle, $searchedTitle) !== false) { + if (false !== stripos($mangaTitle, $searchedTitle)) { $score += 50; } // Le terme recherché contient le titre du manga - if (stripos($searchedTitle, $mangaTitle) !== false) { + if (false !== stripos($searchedTitle, $mangaTitle)) { $score += 40; } diff --git a/src/Domain/Manga/Application/QueryHandler/GetMangaByIdHandler.php b/src/Domain/Manga/Application/QueryHandler/GetMangaByIdHandler.php index 6f0d2d3..cb79e5d 100644 --- a/src/Domain/Manga/Application/QueryHandler/GetMangaByIdHandler.php +++ b/src/Domain/Manga/Application/QueryHandler/GetMangaByIdHandler.php @@ -10,7 +10,7 @@ use App\Domain\Manga\Domain\Exception\MangaNotFoundException; readonly class GetMangaByIdHandler { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } diff --git a/src/Domain/Manga/Application/QueryHandler/GetMangaBySlugHandler.php b/src/Domain/Manga/Application/QueryHandler/GetMangaBySlugHandler.php index 407e06c..80299f4 100644 --- a/src/Domain/Manga/Application/QueryHandler/GetMangaBySlugHandler.php +++ b/src/Domain/Manga/Application/QueryHandler/GetMangaBySlugHandler.php @@ -11,7 +11,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug; readonly class GetMangaBySlugHandler { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } diff --git a/src/Domain/Manga/Application/QueryHandler/GetMangaChaptersHandler.php b/src/Domain/Manga/Application/QueryHandler/GetMangaChaptersHandler.php index 06101b2..a36cfc2 100644 --- a/src/Domain/Manga/Application/QueryHandler/GetMangaChaptersHandler.php +++ b/src/Domain/Manga/Application/QueryHandler/GetMangaChaptersHandler.php @@ -12,7 +12,7 @@ use App\Domain\Manga\Domain\Model\Chapter; readonly class GetMangaChaptersHandler { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } @@ -30,7 +30,7 @@ readonly class GetMangaChaptersHandler $grouped = $this->groupChapters($allChapters); - if ($query->sortOrder === 'desc') { + if ('desc' === $query->sortOrder) { usort($grouped, fn (ChapterResponse $a, ChapterResponse $b) => $b->number <=> $a->number); } @@ -58,7 +58,7 @@ readonly class GetMangaChaptersHandler $pagesDir = $chapter->getPagesDirectory(); $volume = $chapter->getVolume(); - if ($pagesDir !== null && $volume !== null) { + if (null !== $pagesDir && null !== $volume) { if ($pagesDir === $currentPagesDir && $volume === $currentVolume) { $currentGroup[] = $chapter; } else { @@ -104,7 +104,7 @@ readonly class GetMangaChaptersHandler $max = max($numbers); $fmt = fn (float $n) => $n == (int) $n ? (string) (int) $n : (string) $n; - $range = count($group) > 1 ? $fmt($min) . '-' . $fmt($max) : $fmt($min); + $range = count($group) > 1 ? $fmt($min).'-'.$fmt($max) : $fmt($min); return new ChapterResponse( id: $first->getId(), diff --git a/src/Domain/Manga/Application/QueryHandler/GetMangaListHandler.php b/src/Domain/Manga/Application/QueryHandler/GetMangaListHandler.php index 7f6648c..d70f7e3 100644 --- a/src/Domain/Manga/Application/QueryHandler/GetMangaListHandler.php +++ b/src/Domain/Manga/Application/QueryHandler/GetMangaListHandler.php @@ -3,13 +3,13 @@ namespace App\Domain\Manga\Application\QueryHandler; use App\Domain\Manga\Application\Query\GetMangaList; -use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface; use App\Domain\Manga\Application\Response\MangaListResponse; +use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface; readonly class GetMangaListHandler { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } @@ -28,7 +28,7 @@ readonly class GetMangaListHandler foreach ($mangas as $manga) { $id = $manga->getId()->getValue(); $chapterCounts[$id] = [ - 'total' => $this->mangaRepository->countChapters($id), + 'total' => $this->mangaRepository->countChapters($id), 'scraped' => $this->mangaRepository->countAvailableChapters($id), ]; } diff --git a/src/Domain/Manga/Application/QueryHandler/SearchLocalMangaHandler.php b/src/Domain/Manga/Application/QueryHandler/SearchLocalMangaHandler.php index 281d632..31dbcf9 100644 --- a/src/Domain/Manga/Application/QueryHandler/SearchLocalMangaHandler.php +++ b/src/Domain/Manga/Application/QueryHandler/SearchLocalMangaHandler.php @@ -11,7 +11,7 @@ use App\Domain\Manga\Domain\Model\Manga; readonly class SearchLocalMangaHandler { public function __construct( - private MangaRepositoryInterface $repository + private MangaRepositoryInterface $repository, ) { } diff --git a/src/Domain/Manga/Application/QueryHandler/SearchMangaHandler.php b/src/Domain/Manga/Application/QueryHandler/SearchMangaHandler.php index 69b35fd..f243b77 100644 --- a/src/Domain/Manga/Application/QueryHandler/SearchMangaHandler.php +++ b/src/Domain/Manga/Application/QueryHandler/SearchMangaHandler.php @@ -11,7 +11,7 @@ use App\Domain\Manga\Domain\Model\Manga; readonly class SearchMangaHandler { public function __construct( - private MangaProviderInterface $mangaProvider + private MangaProviderInterface $mangaProvider, ) { } @@ -19,7 +19,6 @@ readonly class SearchMangaHandler { $mangaCollection = $this->mangaProvider->search($query->title); - return new MangaSearchResponse( array_map( fn (Manga $manga, int $index) => new MangaSearchItem( diff --git a/src/Domain/Manga/Application/Response/ChapterListResponse.php b/src/Domain/Manga/Application/Response/ChapterListResponse.php index 3172e82..9943be9 100644 --- a/src/Domain/Manga/Application/Response/ChapterListResponse.php +++ b/src/Domain/Manga/Application/Response/ChapterListResponse.php @@ -8,7 +8,7 @@ readonly class ChapterListResponse public array $chapters, public int $total, public int $page, - public int $limit + public int $limit, ) { } diff --git a/src/Domain/Manga/Application/Response/DownloadResponse.php b/src/Domain/Manga/Application/Response/DownloadResponse.php index f83274a..e63a110 100644 --- a/src/Domain/Manga/Application/Response/DownloadResponse.php +++ b/src/Domain/Manga/Application/Response/DownloadResponse.php @@ -8,7 +8,7 @@ use Symfony\Component\HttpFoundation\Response; readonly class DownloadResponse implements ResponseInterface { public function __construct( - public Response $httpResponse + public Response $httpResponse, ) { } } diff --git a/src/Domain/Manga/Application/Response/MangaListResponse.php b/src/Domain/Manga/Application/Response/MangaListResponse.php index b5e2d6e..8be8824 100644 --- a/src/Domain/Manga/Application/Response/MangaListResponse.php +++ b/src/Domain/Manga/Application/Response/MangaListResponse.php @@ -9,7 +9,7 @@ readonly class MangaListResponse public int $total, public int $page, public int $limit, - public array $chapterCounts = [] + public array $chapterCounts = [], ) { } diff --git a/src/Domain/Manga/Application/Response/MangaMatchItem.php b/src/Domain/Manga/Application/Response/MangaMatchItem.php index b47b1ac..53187ec 100644 --- a/src/Domain/Manga/Application/Response/MangaMatchItem.php +++ b/src/Domain/Manga/Application/Response/MangaMatchItem.php @@ -14,7 +14,7 @@ readonly class MangaMatchItem public ?string $thumbnailUrl, public int $matchScore, public ?float $chapterNumber = null, - public ?float $volumeNumber = null + public ?float $volumeNumber = null, ) { } } diff --git a/src/Domain/Manga/Application/Response/MangaMatchResponse.php b/src/Domain/Manga/Application/Response/MangaMatchResponse.php index 3314987..606e5f2 100644 --- a/src/Domain/Manga/Application/Response/MangaMatchResponse.php +++ b/src/Domain/Manga/Application/Response/MangaMatchResponse.php @@ -13,7 +13,7 @@ readonly class MangaMatchResponse public array $matches, public ?float $chapterNumber, public ?float $volumeNumber, - public array $possibleTitles + public array $possibleTitles, ) { } diff --git a/src/Domain/Manga/Application/Response/MangaResponse.php b/src/Domain/Manga/Application/Response/MangaResponse.php index 3c7314e..bd85ea1 100644 --- a/src/Domain/Manga/Application/Response/MangaResponse.php +++ b/src/Domain/Manga/Application/Response/MangaResponse.php @@ -18,7 +18,7 @@ readonly class MangaResponse public ?string $imageUrl, public ?string $thumbnailUrl, public ?float $rating, - public bool $monitored + public bool $monitored, ) { } } diff --git a/src/Domain/Manga/Application/Response/MangaSearchItem.php b/src/Domain/Manga/Application/Response/MangaSearchItem.php index a21c2ee..3a45cec 100644 --- a/src/Domain/Manga/Application/Response/MangaSearchItem.php +++ b/src/Domain/Manga/Application/Response/MangaSearchItem.php @@ -16,7 +16,7 @@ readonly class MangaSearchItem public string $status, public ?string $imageUrl, public ?string $thumbnailUrl, - public ?float $rating + public ?float $rating, ) { } } diff --git a/src/Domain/Manga/Application/Response/SearchLocalMangaResponse.php b/src/Domain/Manga/Application/Response/SearchLocalMangaResponse.php index 1af9fb9..783694d 100644 --- a/src/Domain/Manga/Application/Response/SearchLocalMangaResponse.php +++ b/src/Domain/Manga/Application/Response/SearchLocalMangaResponse.php @@ -11,7 +11,7 @@ readonly class SearchLocalMangaResponse public array $items, public int $total, public int $page, - public int $limit + public int $limit, ) { } diff --git a/src/Domain/Manga/Domain/Contract/Client/MangadexClientInterface.php b/src/Domain/Manga/Domain/Contract/Client/MangadexClientInterface.php index cbf736b..003c0bd 100644 --- a/src/Domain/Manga/Domain/Contract/Client/MangadexClientInterface.php +++ b/src/Domain/Manga/Domain/Contract/Client/MangadexClientInterface.php @@ -7,12 +7,12 @@ use App\Domain\Manga\Domain\Exception\MangadexAuthenticationException; interface MangadexClientInterface { /** - * @throws \App\Domain\Manga\Domain\Exception\MangadexAuthenticationException + * @throws MangadexAuthenticationException */ public function authenticate(): void; /** - * @throws \App\Domain\Manga\Domain\Exception\MangadexAuthenticationException + * @throws MangadexAuthenticationException */ public function refreshToken(): void; @@ -39,6 +39,7 @@ interface MangadexClientInterface /** * @param string[] $mangaIds + * * @return array{ * statistics: array */ public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array; @@ -61,5 +75,4 @@ interface MangaRepositoryInterface * @return Chapter[] */ public function findVisibleChaptersWithPagesByMangaIdAndVolume(string $mangaId, int $volume): array; - } diff --git a/src/Domain/Manga/Domain/Contract/Service/ChapterSynchronizationServiceInterface.php b/src/Domain/Manga/Domain/Contract/Service/ChapterSynchronizationServiceInterface.php index 8e45cfa..ff44389 100644 --- a/src/Domain/Manga/Domain/Contract/Service/ChapterSynchronizationServiceInterface.php +++ b/src/Domain/Manga/Domain/Contract/Service/ChapterSynchronizationServiceInterface.php @@ -7,7 +7,8 @@ use App\Domain\Manga\Domain\Model\Manga; interface ChapterSynchronizationServiceInterface { /** - * Synchronise les chapitres d'un manga depuis la source externe + * Synchronise les chapitres d'un manga depuis la source externe. + * * @return string[] IDs des nouveaux chapitres ajoutés */ public function synchronizeChapters(Manga $manga): array; diff --git a/src/Domain/Manga/Domain/Contract/Service/FileServiceInterface.php b/src/Domain/Manga/Domain/Contract/Service/FileServiceInterface.php index 014a201..fbc222b 100644 --- a/src/Domain/Manga/Domain/Contract/Service/FileServiceInterface.php +++ b/src/Domain/Manga/Domain/Contract/Service/FileServiceInterface.php @@ -7,23 +7,24 @@ use Symfony\Component\HttpFoundation\Response; interface FileServiceInterface { /** - * Télécharge un fichier CBZ + * Télécharge un fichier CBZ. */ public function downloadCbz(string $filePath, string $filename): Response; /** - * Crée un fichier ZIP contenant plusieurs CBZ + * Crée un fichier ZIP contenant plusieurs CBZ. + * * @param array $cbzPaths */ public function createVolumeCbz(array $cbzPaths, string $volumeName): Response; /** - * Supprime un fichier CBZ du système de fichiers + * Supprime un fichier CBZ du système de fichiers. */ public function deleteCbzFile(string $filePath): bool; /** - * Vérifie si un fichier CBZ existe + * Vérifie si un fichier CBZ existe. */ public function cbzExists(string $filePath): bool; } diff --git a/src/Domain/Manga/Domain/Event/ChapterReadyForScraping.php b/src/Domain/Manga/Domain/Event/ChapterReadyForScraping.php index fc627df..5c03db7 100644 --- a/src/Domain/Manga/Domain/Event/ChapterReadyForScraping.php +++ b/src/Domain/Manga/Domain/Event/ChapterReadyForScraping.php @@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Model\ValueObject\ChapterId; readonly class ChapterReadyForScraping { public function __construct( - public ChapterId $chapterId + public ChapterId $chapterId, ) { } } diff --git a/src/Domain/Manga/Domain/Event/MangaCreated.php b/src/Domain/Manga/Domain/Event/MangaCreated.php index e713be2..27defbc 100644 --- a/src/Domain/Manga/Domain/Event/MangaCreated.php +++ b/src/Domain/Manga/Domain/Event/MangaCreated.php @@ -6,7 +6,7 @@ readonly class MangaCreated { public function __construct( public string $mangaId, - public string $externalId + public string $externalId, ) { } } diff --git a/src/Domain/Manga/Domain/Exception/CbzFileNotFoundException.php b/src/Domain/Manga/Domain/Exception/CbzFileNotFoundException.php index ad0962d..da969d9 100644 --- a/src/Domain/Manga/Domain/Exception/CbzFileNotFoundException.php +++ b/src/Domain/Manga/Domain/Exception/CbzFileNotFoundException.php @@ -2,9 +2,7 @@ namespace App\Domain\Manga\Domain\Exception; -use DomainException; - -class CbzFileNotFoundException extends DomainException +class CbzFileNotFoundException extends \DomainException { public function __construct(string $filePath) { diff --git a/src/Domain/Manga/Domain/Exception/ChapterNotAvailableException.php b/src/Domain/Manga/Domain/Exception/ChapterNotAvailableException.php index 9b34590..1a56342 100644 --- a/src/Domain/Manga/Domain/Exception/ChapterNotAvailableException.php +++ b/src/Domain/Manga/Domain/Exception/ChapterNotAvailableException.php @@ -2,9 +2,7 @@ namespace App\Domain\Manga\Domain\Exception; -use DomainException; - -class ChapterNotAvailableException extends DomainException +class ChapterNotAvailableException extends \DomainException { public function __construct(int $chapterId) { diff --git a/src/Domain/Manga/Domain/Exception/ChapterNotFoundException.php b/src/Domain/Manga/Domain/Exception/ChapterNotFoundException.php index 93faf8b..90d032d 100644 --- a/src/Domain/Manga/Domain/Exception/ChapterNotFoundException.php +++ b/src/Domain/Manga/Domain/Exception/ChapterNotFoundException.php @@ -2,9 +2,7 @@ namespace App\Domain\Manga\Domain\Exception; -use DomainException; - -class ChapterNotFoundException extends DomainException +class ChapterNotFoundException extends \DomainException { public function __construct(string $chapterId) { diff --git a/src/Domain/Manga/Domain/Exception/VolumeNotFoundException.php b/src/Domain/Manga/Domain/Exception/VolumeNotFoundException.php index 9facb31..7af487d 100644 --- a/src/Domain/Manga/Domain/Exception/VolumeNotFoundException.php +++ b/src/Domain/Manga/Domain/Exception/VolumeNotFoundException.php @@ -2,9 +2,7 @@ namespace App\Domain\Manga\Domain\Exception; -use DomainException; - -class VolumeNotFoundException extends DomainException +class VolumeNotFoundException extends \DomainException { public function __construct(string $mangaId, int $volume) { diff --git a/src/Domain/Manga/Domain/Model/AnalyzedFilename.php b/src/Domain/Manga/Domain/Model/AnalyzedFilename.php index 83d64cb..ece1758 100644 --- a/src/Domain/Manga/Domain/Model/AnalyzedFilename.php +++ b/src/Domain/Manga/Domain/Model/AnalyzedFilename.php @@ -13,7 +13,7 @@ readonly class AnalyzedFilename public function __construct( private MangaTitle $title, private ?ChapterNumber $chapterNumber = null, - private ?VolumeNumber $volumeNumber = null + private ?VolumeNumber $volumeNumber = null, ) { } @@ -34,11 +34,11 @@ readonly class AnalyzedFilename public function hasChapterNumber(): bool { - return $this->chapterNumber !== null; + return null !== $this->chapterNumber; } public function hasVolumeNumber(): bool { - return $this->volumeNumber !== null; + return null !== $this->volumeNumber; } } diff --git a/src/Domain/Manga/Domain/Model/Chapter.php b/src/Domain/Manga/Domain/Model/Chapter.php index ae313a5..3ad91a9 100644 --- a/src/Domain/Manga/Domain/Model/Chapter.php +++ b/src/Domain/Manga/Domain/Model/Chapter.php @@ -16,7 +16,7 @@ class Chapter private bool $isVisible, private ?string $pagesDirectory = null, private int $pageCount = 0, - private \DateTimeImmutable $createdAt = new \DateTimeImmutable() + private \DateTimeImmutable $createdAt = new \DateTimeImmutable(), ) { } @@ -52,7 +52,7 @@ class Chapter public function isAvailable(): bool { - return $this->pagesDirectory !== null; + return null !== $this->pagesDirectory; } public function getPagesDirectory(): ?string diff --git a/src/Domain/Manga/Domain/Model/Manga.php b/src/Domain/Manga/Domain/Model/Manga.php index 00a6cd5..b89ecf3 100644 --- a/src/Domain/Manga/Domain/Model/Manga.php +++ b/src/Domain/Manga/Domain/Model/Manga.php @@ -9,7 +9,6 @@ use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug; use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle; use App\Domain\Manga\Domain\Model\ValueObject\MonitoringStatus; use App\Domain\Shared\Domain\Model\AggregateRoot; -use DateTimeImmutable; final class Manga extends AggregateRoot { @@ -23,22 +22,22 @@ final class Manga extends AggregateRoot private array $chaptersToDelete = []; public function __construct( - private MangaId $id, - private MangaTitle $title, - private MangaSlug $slug, - private string $description, - private string $author, - private int $publicationYear, - private array $genres, - private string $status, + private MangaId $id, + private MangaTitle $title, + private MangaSlug $slug, + private string $description, + private string $author, + private int $publicationYear, + private array $genres, + private string $status, private ?ExternalId $externalId = null, - private ?string $imageUrl = null, - private ?float $rating = null, - private ?ImageUrls $imageUrls = null, - private array $alternativeSlugs = [], - private ?DateTimeImmutable $createdAt = null, + private ?string $imageUrl = null, + private ?float $rating = null, + private ?ImageUrls $imageUrls = null, + private array $alternativeSlugs = [], + private ?\DateTimeImmutable $createdAt = null, private ?MonitoringStatus $monitoringStatus = null, - private ?DateTimeImmutable $lastMonitoringCheck = null, + private ?\DateTimeImmutable $lastMonitoringCheck = null, ) { $this->monitoringStatus = $this->monitoringStatus ?? MonitoringStatus::disabled(); } @@ -158,7 +157,7 @@ final class Manga extends AggregateRoot $this->alternativeSlugs = $alternativeSlugs; } - public function getCreatedAt(): ?DateTimeImmutable + public function getCreatedAt(): ?\DateTimeImmutable { return $this->createdAt; } @@ -181,7 +180,7 @@ final class Manga extends AggregateRoot public function enableMonitoring(): void { $this->monitoringStatus = MonitoringStatus::enabled(); - $this->lastMonitoringCheck = new DateTimeImmutable(); + $this->lastMonitoringCheck = new \DateTimeImmutable(); } public function disableMonitoring(): void @@ -190,12 +189,12 @@ final class Manga extends AggregateRoot $this->lastMonitoringCheck = null; } - public function getLastMonitoringCheck(): ?DateTimeImmutable + public function getLastMonitoringCheck(): ?\DateTimeImmutable { return $this->lastMonitoringCheck; } - public function updateLastMonitoringCheck(DateTimeImmutable $lastMonitoringCheck): void + public function updateLastMonitoringCheck(\DateTimeImmutable $lastMonitoringCheck): void { $this->lastMonitoringCheck = $lastMonitoringCheck; } diff --git a/src/Domain/Manga/Domain/Model/ValueObject/ChapterId.php b/src/Domain/Manga/Domain/Model/ValueObject/ChapterId.php index d9199de..00a15e6 100644 --- a/src/Domain/Manga/Domain/Model/ValueObject/ChapterId.php +++ b/src/Domain/Manga/Domain/Model/ValueObject/ChapterId.php @@ -5,7 +5,7 @@ namespace App\Domain\Manga\Domain\Model\ValueObject; readonly class ChapterId { public function __construct( - private string $value + private string $value, ) { } diff --git a/src/Domain/Manga/Domain/Model/ValueObject/ChapterNumber.php b/src/Domain/Manga/Domain/Model/ValueObject/ChapterNumber.php index 1c116cc..ce9296e 100644 --- a/src/Domain/Manga/Domain/Model/ValueObject/ChapterNumber.php +++ b/src/Domain/Manga/Domain/Model/ValueObject/ChapterNumber.php @@ -4,15 +4,13 @@ declare(strict_types=1); namespace App\Domain\Manga\Domain\Model\ValueObject; -use InvalidArgumentException; - readonly class ChapterNumber { public function __construct( - private float $value + private float $value, ) { if ($value < 0) { - throw new InvalidArgumentException('Chapter number cannot be negative'); + throw new \InvalidArgumentException('Chapter number cannot be negative'); } } diff --git a/src/Domain/Manga/Domain/Model/ValueObject/ExternalId.php b/src/Domain/Manga/Domain/Model/ValueObject/ExternalId.php index 6d059b1..356166b 100644 --- a/src/Domain/Manga/Domain/Model/ValueObject/ExternalId.php +++ b/src/Domain/Manga/Domain/Model/ValueObject/ExternalId.php @@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Exception\InvalidExternalIdException; readonly class ExternalId { public function __construct( - private string $value + private string $value, ) { if (empty($value)) { throw new InvalidExternalIdException('External ID cannot be empty'); diff --git a/src/Domain/Manga/Domain/Model/ValueObject/ImageUrls.php b/src/Domain/Manga/Domain/Model/ValueObject/ImageUrls.php index d69e78e..e1aa476 100644 --- a/src/Domain/Manga/Domain/Model/ValueObject/ImageUrls.php +++ b/src/Domain/Manga/Domain/Model/ValueObject/ImageUrls.php @@ -6,7 +6,7 @@ readonly class ImageUrls { public function __construct( private string $full, - private string $thumbnail + private string $thumbnail, ) { } diff --git a/src/Domain/Manga/Domain/Model/ValueObject/MangaId.php b/src/Domain/Manga/Domain/Model/ValueObject/MangaId.php index 7568ab3..17404d6 100644 --- a/src/Domain/Manga/Domain/Model/ValueObject/MangaId.php +++ b/src/Domain/Manga/Domain/Model/ValueObject/MangaId.php @@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Exception\InvalidMangaIdException; readonly class MangaId { public function __construct( - private string $value + private string $value, ) { if (empty($value)) { throw new InvalidMangaIdException('Manga ID cannot be empty'); diff --git a/src/Domain/Manga/Domain/Model/ValueObject/MangaSlug.php b/src/Domain/Manga/Domain/Model/ValueObject/MangaSlug.php index 7597921..c93f8c1 100644 --- a/src/Domain/Manga/Domain/Model/ValueObject/MangaSlug.php +++ b/src/Domain/Manga/Domain/Model/ValueObject/MangaSlug.php @@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Exception\InvalidMangaSlugException; readonly class MangaSlug { public function __construct( - private string $value + private string $value, ) { if (empty(trim($value))) { throw new InvalidMangaSlugException('Manga slug cannot be empty'); diff --git a/src/Domain/Manga/Domain/Model/ValueObject/MangaTitle.php b/src/Domain/Manga/Domain/Model/ValueObject/MangaTitle.php index 70942de..ed4966c 100644 --- a/src/Domain/Manga/Domain/Model/ValueObject/MangaTitle.php +++ b/src/Domain/Manga/Domain/Model/ValueObject/MangaTitle.php @@ -7,7 +7,7 @@ use App\Domain\Manga\Domain\Exception\InvalidMangaTitleException; readonly class MangaTitle { public function __construct( - private string $value + private string $value, ) { if (empty(trim($value))) { throw new InvalidMangaTitleException('Manga title cannot be empty'); diff --git a/src/Domain/Manga/Domain/Model/ValueObject/MonitoringStatus.php b/src/Domain/Manga/Domain/Model/ValueObject/MonitoringStatus.php index 9317ac0..1ea83f0 100644 --- a/src/Domain/Manga/Domain/Model/ValueObject/MonitoringStatus.php +++ b/src/Domain/Manga/Domain/Model/ValueObject/MonitoringStatus.php @@ -5,7 +5,7 @@ namespace App\Domain\Manga\Domain\Model\ValueObject; readonly class MonitoringStatus { public function __construct( - private bool $enabled + private bool $enabled, ) { } diff --git a/src/Domain/Manga/Domain/Model/ValueObject/VolumeNumber.php b/src/Domain/Manga/Domain/Model/ValueObject/VolumeNumber.php index 10516e3..5720943 100644 --- a/src/Domain/Manga/Domain/Model/ValueObject/VolumeNumber.php +++ b/src/Domain/Manga/Domain/Model/ValueObject/VolumeNumber.php @@ -4,15 +4,13 @@ declare(strict_types=1); namespace App\Domain\Manga\Domain\Model\ValueObject; -use InvalidArgumentException; - readonly class VolumeNumber { public function __construct( - private float $value + private float $value, ) { if ($value < 0) { - throw new InvalidArgumentException('Volume number cannot be negative'); + throw new \InvalidArgumentException('Volume number cannot be negative'); } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Controller/ImportChapterController.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Controller/ImportChapterController.php index 686ec91..e8a1e92 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Controller/ImportChapterController.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Controller/ImportChapterController.php @@ -6,18 +6,16 @@ use App\Domain\Manga\Application\Command\ImportChapter; use App\Domain\Manga\Application\CommandHandler\ImportChapterHandler; use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; use App\Domain\Manga\Domain\Exception\MangaNotFoundException; -use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\ImportChapterResource; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; #[AsController] final class ImportChapterController extends AbstractController { public function __construct( - private readonly ImportChapterHandler $commandHandler + private readonly ImportChapterHandler $commandHandler, ) { } @@ -31,19 +29,19 @@ final class ImportChapterController extends AbstractController // Validate required fields if (!$mangaId) { return $this->json([ - ['propertyPath' => 'mangaId', 'message' => 'mangaId is required'] + ['propertyPath' => 'mangaId', 'message' => 'mangaId is required'], ], 422); } if (!$chapterNumber) { return $this->json([ - ['propertyPath' => 'chapterNumber', 'message' => 'chapterNumber is required'] + ['propertyPath' => 'chapterNumber', 'message' => 'chapterNumber is required'], ], 422); } if (!$uploadedFile) { return $this->json([ - ['propertyPath' => 'file', 'message' => 'Please upload a file'] + ['propertyPath' => 'file', 'message' => 'Please upload a file'], ], 422); } @@ -56,9 +54,9 @@ final class ImportChapterController extends AbstractController try { // Read file binary content $fileBinary = file_get_contents($uploadedFile->getPathname()); - if ($fileBinary === false) { + if (false === $fileBinary) { return $this->json([ - ['propertyPath' => 'file', 'message' => 'Failed to read the uploaded file'] + ['propertyPath' => 'file', 'message' => 'Failed to read the uploaded file'], ], 400); } @@ -75,31 +73,27 @@ final class ImportChapterController extends AbstractController return $this->json([ 'message' => 'Chapter imported successfully', 'mangaId' => $mangaId, - 'chapterNumber' => $chapterNumber + 'chapterNumber' => $chapterNumber, ], 200); - } catch (MangaNotFoundException $e) { return $this->json([ 'error' => 'Manga not found', - 'details' => $e->getMessage() + 'details' => $e->getMessage(), ], 404); - } catch (ChapterNotFoundException $e) { return $this->json([ 'error' => 'Chapter not found', - 'details' => $e->getMessage() + 'details' => $e->getMessage(), ], 404); - } catch (\InvalidArgumentException $e) { return $this->json([ 'error' => 'Invalid file', - 'details' => $e->getMessage() + 'details' => $e->getMessage(), ], 400); - } catch (\Exception $e) { return $this->json([ 'error' => 'Import failed', - 'details' => $e->getMessage() + 'details' => $e->getMessage(), ], 500); } } @@ -112,8 +106,9 @@ final class ImportChapterController extends AbstractController if (!$uploadedFile->isValid()) { $errors[] = [ 'propertyPath' => 'file', - 'message' => 'The uploaded file is not valid: ' . $uploadedFile->getErrorMessage() + 'message' => 'The uploaded file is not valid: '.$uploadedFile->getErrorMessage(), ]; + return $errors; } @@ -122,7 +117,7 @@ final class ImportChapterController extends AbstractController if ($uploadedFile->getSize() > $maxSize) { $errors[] = [ 'propertyPath' => 'file', - 'message' => 'The uploaded file is too large. Allowed size is 500MB.' + 'message' => 'The uploaded file is too large. Allowed size is 500MB.', ]; } @@ -133,7 +128,7 @@ final class ImportChapterController extends AbstractController if (!in_array($extension, $allowedExtensions)) { $errors[] = [ 'propertyPath' => 'file', - 'message' => 'Please upload a valid CBZ file' + 'message' => 'Please upload a valid CBZ file', ]; } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Controller/ImportVolumeController.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Controller/ImportVolumeController.php index 4f8073d..67973c5 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Controller/ImportVolumeController.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Controller/ImportVolumeController.php @@ -5,7 +5,6 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Controller; use App\Domain\Manga\Application\Command\ImportVolume; use App\Domain\Manga\Application\CommandHandler\ImportVolumeHandler; use App\Domain\Manga\Domain\Exception\MangaNotFoundException; -use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\ImportVolumeResource; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -15,7 +14,7 @@ use Symfony\Component\HttpKernel\Attribute\AsController; final class ImportVolumeController extends AbstractController { public function __construct( - private readonly ImportVolumeHandler $commandHandler + private readonly ImportVolumeHandler $commandHandler, ) { } @@ -29,19 +28,19 @@ final class ImportVolumeController extends AbstractController // Validate required fields if (!$mangaId) { return $this->json([ - ['propertyPath' => 'mangaId', 'message' => 'mangaId is required'] + ['propertyPath' => 'mangaId', 'message' => 'mangaId is required'], ], 422); } if (!$volumeNumber) { return $this->json([ - ['propertyPath' => 'volumeNumber', 'message' => 'volumeNumber is required'] + ['propertyPath' => 'volumeNumber', 'message' => 'volumeNumber is required'], ], 422); } if (!$uploadedFile) { return $this->json([ - ['propertyPath' => 'file', 'message' => 'Please upload a file'] + ['propertyPath' => 'file', 'message' => 'Please upload a file'], ], 422); } @@ -54,9 +53,9 @@ final class ImportVolumeController extends AbstractController try { // Read file binary content $fileBinary = file_get_contents($uploadedFile->getPathname()); - if ($fileBinary === false) { + if (false === $fileBinary) { return $this->json([ - ['propertyPath' => 'file', 'message' => 'Failed to read the uploaded file'] + ['propertyPath' => 'file', 'message' => 'Failed to read the uploaded file'], ], 400); } @@ -73,26 +72,24 @@ final class ImportVolumeController extends AbstractController return $this->json([ 'message' => 'Volume imported successfully', 'mangaId' => $mangaId, - 'volumeNumber' => (int) $volumeNumber + 'volumeNumber' => (int) $volumeNumber, ], 200); - } catch (MangaNotFoundException $e) { return $this->json([ 'error' => 'Manga not found', - 'details' => $e->getMessage() + 'details' => $e->getMessage(), ], 404); - } catch (\InvalidArgumentException $e) { $statusCode = str_contains($e->getMessage(), 'not found') ? 404 : 400; + return $this->json([ 'error' => 'Invalid request', - 'details' => $e->getMessage() + 'details' => $e->getMessage(), ], $statusCode); - } catch (\Exception $e) { return $this->json([ 'error' => 'Import failed', - 'details' => $e->getMessage() + 'details' => $e->getMessage(), ], 500); } } @@ -105,8 +102,9 @@ final class ImportVolumeController extends AbstractController if (!$uploadedFile->isValid()) { $errors[] = [ 'propertyPath' => 'file', - 'message' => 'The uploaded file is not valid: ' . $uploadedFile->getErrorMessage() + 'message' => 'The uploaded file is not valid: '.$uploadedFile->getErrorMessage(), ]; + return $errors; } @@ -115,7 +113,7 @@ final class ImportVolumeController extends AbstractController if ($uploadedFile->getSize() > $maxSize) { $errors[] = [ 'propertyPath' => 'file', - 'message' => 'The uploaded file is too large. Allowed size is 500MB.' + 'message' => 'The uploaded file is too large. Allowed size is 500MB.', ]; } @@ -126,7 +124,7 @@ final class ImportVolumeController extends AbstractController if (!in_array($extension, $allowedExtensions)) { $errors[] = [ 'propertyPath' => 'file', - 'message' => 'Please upload a valid CBZ file' + 'message' => 'Please upload a valid CBZ file', ]; } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/ChapterCollection.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/ChapterCollection.php index bb47f0b..8cd6f8e 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/ChapterCollection.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/ChapterCollection.php @@ -11,7 +11,7 @@ readonly class ChapterCollection public int $page, public int $limit, public bool $hasNextPage, - public bool $hasPreviousPage + public bool $hasPreviousPage, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/FilenameMatchCollection.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/FilenameMatchCollection.php index dc5dbb2..d21b13b 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/FilenameMatchCollection.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/FilenameMatchCollection.php @@ -8,13 +8,13 @@ readonly class FilenameMatchCollection { /** * @param FilenameMatchItem[] $matches - * @param string[] $possibleTitles + * @param string[] $possibleTitles */ public function __construct( public array $matches, public ?float $chapterNumber, public ?int $volumeNumber, - public array $possibleTitles + public array $possibleTitles, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/FilenameMatchItem.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/FilenameMatchItem.php index 80025ad..e862abd 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/FilenameMatchItem.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/FilenameMatchItem.php @@ -14,7 +14,7 @@ readonly class FilenameMatchItem public ?string $thumbnailUrl, public int $matchScore, public ?float $chapterNumber, - public ?float $volumeNumber + public ?float $volumeNumber, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaCollection.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaCollection.php index bc6639c..e390191 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaCollection.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaCollection.php @@ -2,8 +2,6 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Dto; -use ApiPlatform\Metadata\ApiProperty; - readonly class MangaCollection { public function __construct( @@ -13,7 +11,7 @@ readonly class MangaCollection public int $page, public int $limit, public bool $hasNextPage, - public bool $hasPreviousPage + public bool $hasPreviousPage, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaDetail.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaDetail.php index efa7fb7..e42551a 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaDetail.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaDetail.php @@ -21,7 +21,7 @@ readonly class MangaDetail public ?string $imageUrl, public ?string $thumbnailUrl, public ?float $rating, - public bool $monitored + public bool $monitored, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaListItem.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaListItem.php index c8f6998..f4406ae 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaListItem.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaListItem.php @@ -3,7 +3,6 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Dto; use ApiPlatform\Metadata\ApiProperty; -use DateTimeImmutable; readonly class MangaListItem { @@ -20,7 +19,7 @@ readonly class MangaListItem public array $genres, public string $status, public ?float $rating, - public DateTimeImmutable $createdAt, + public \DateTimeImmutable $createdAt, public bool $monitored = false, public int $chaptersTotal = 0, public int $chaptersScraped = 0, diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaSearchCollection.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaSearchCollection.php index 96b0f8f..eac24da 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaSearchCollection.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaSearchCollection.php @@ -6,7 +6,7 @@ readonly class MangaSearchCollection { public function __construct( /** @var MangaSearchItem[] */ - public array $items + public array $items, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaSearchItem.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaSearchItem.php index fd2b955..29e1ba3 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaSearchItem.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Dto/MangaSearchItem.php @@ -18,7 +18,7 @@ readonly class MangaSearchItem public string $status, public ?string $imageUrl, public ?string $thumbnailUrl, - public ?float $rating + public ?float $rating, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaDirectlyResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaDirectlyResource.php index 3903fb0..ddb4540 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaDirectlyResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaDirectlyResource.php @@ -4,6 +4,7 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\CreateMangaDirectlyProcessor; use Symfony\Component\Validator\Constraints as Assert; @@ -13,11 +14,11 @@ use Symfony\Component\Validator\Constraints as Assert; new Post( uriTemplate: '/mangas/create', processor: CreateMangaDirectlyProcessor::class, - openapiContext: [ - 'summary' => 'Create a new manga directly', - 'description' => 'Creates a new manga with provided data' - ] - ) + openapi: new Operation( + summary: 'Create a new manga directly', + description: 'Creates a new manga with provided data' + ) + ), ] )] class CreateMangaDirectlyResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaResource.php index d10d629..0bbd820 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/CreateMangaResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\RequestBody; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\CreateMangaProcessor; use Symfony\Component\Validator\Constraints as Assert; @@ -13,11 +15,11 @@ use Symfony\Component\Validator\Constraints as Assert; new Post( uriTemplate: '/mangas/create-from-mangadex', processor: CreateMangaProcessor::class, - openapiContext: [ - 'summary' => 'Create a new manga from Mangadex', - 'description' => 'Creates a new manga by fetching its data from Mangadex using an external ID', - 'requestBody' => [ - 'content' => [ + openapi: new Operation( + summary: 'Create a new manga from Mangadex', + description: 'Creates a new manga by fetching its data from Mangadex using an external ID', + requestBody: new RequestBody( + content: new \ArrayObject([ 'application/json' => [ 'schema' => [ 'type' => 'object', @@ -25,15 +27,15 @@ use Symfony\Component\Validator\Constraints as Assert; 'properties' => [ 'externalId' => [ 'type' => 'string', - 'description' => 'The Mangadex ID of the manga' - ] - ] - ] - ] - ] - ] - ] - ) + 'description' => 'The Mangadex ID of the manga', + ], + ], + ], + ], + ]) + ) + ) + ), ] )] class CreateMangaResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteCbzResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteCbzResource.php index cbd61f7..fee61ad 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteCbzResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteCbzResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\DeleteCbzProcessor; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteCbzProvider; @@ -15,36 +17,34 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteCbzProvider provider: DeleteCbzProvider::class, processor: DeleteCbzProcessor::class, name: 'delete_cbz', - openapiContext: [ - 'summary' => 'Delete chapter CBZ file', - 'description' => 'Removes the CBZ file for a specific chapter and updates the chapter accordingly', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'schema' => [ - 'type' => 'string' - ], - 'description' => 'The chapter ID' - ] + openapi: new Operation( + summary: 'Delete chapter CBZ file', + description: 'Removes the CBZ file for a specific chapter and updates the chapter accordingly', + parameters: [ + new Parameter( + name: 'id', + in: 'path', + required: true, + schema: ['type' => 'string'], + description: 'The chapter ID' + ), ], - 'responses' => [ + responses: [ '204' => [ - 'description' => 'CBZ file successfully deleted' + 'description' => 'CBZ file successfully deleted', ], '404' => [ - 'description' => 'Chapter or CBZ file not found' - ] + 'description' => 'Chapter or CBZ file not found', + ], ] - ] - ) + ) + ), ] )] class DeleteCbzResource { public function __construct( - public string $id + public string $id, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteChapterResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteChapterResource.php index 735731d..5b84b9c 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteChapterResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteChapterResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\DeleteChapterProcessor; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteChapterProvider; @@ -15,36 +17,34 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteChapterProv provider: DeleteChapterProvider::class, processor: DeleteChapterProcessor::class, name: 'delete_chapter', - openapiContext: [ - 'summary' => 'Delete a chapter (soft delete)', - 'description' => 'Marks a chapter as deleted by setting its visibility to false', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'schema' => [ - 'type' => 'string' - ], - 'description' => 'The chapter ID' - ] + openapi: new Operation( + summary: 'Delete a chapter (soft delete)', + description: 'Marks a chapter as deleted by setting its visibility to false', + parameters: [ + new Parameter( + name: 'id', + in: 'path', + required: true, + schema: ['type' => 'string'], + description: 'The chapter ID' + ), ], - 'responses' => [ + responses: [ '204' => [ - 'description' => 'Chapter successfully deleted' + 'description' => 'Chapter successfully deleted', ], '404' => [ - 'description' => 'Chapter not found' - ] + 'description' => 'Chapter not found', + ], ] - ] - ) + ) + ), ] )] class DeleteChapterResource { public function __construct( - public string $id + public string $id, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteMangaResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteMangaResource.php index c7eb556..48839dd 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteMangaResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DeleteMangaResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\DeleteMangaProcessor; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteMangaProvider; @@ -15,36 +17,34 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DeleteMangaProvid provider: DeleteMangaProvider::class, processor: DeleteMangaProcessor::class, name: 'delete_manga', - openapiContext: [ - 'summary' => 'Delete a manga', - 'description' => 'Permanently deletes a manga and all its associated chapters', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'schema' => [ - 'type' => 'string' - ], - 'description' => 'The manga ID' - ] + openapi: new Operation( + summary: 'Delete a manga', + description: 'Permanently deletes a manga and all its associated chapters', + parameters: [ + new Parameter( + name: 'id', + in: 'path', + required: true, + schema: ['type' => 'string'], + description: 'The manga ID' + ), ], - 'responses' => [ + responses: [ '204' => [ - 'description' => 'Manga successfully deleted' + 'description' => 'Manga successfully deleted', ], '404' => [ - 'description' => 'Manga not found' - ] + 'description' => 'Manga not found', + ], ] - ] - ) + ) + ), ] )] class DeleteMangaResource { public function __construct( - public string $id + public string $id, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DownloadCbzResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DownloadCbzResource.php index dfc7a0b..762e073 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DownloadCbzResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DownloadCbzResource.php @@ -14,13 +14,13 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DownloadCbzProvid provider: DownloadCbzProvider::class, output: false, name: 'download_chapter_cbz' - ) + ), ] )] class DownloadCbzResource { public function __construct( - public string $id + public string $id, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DownloadVolumeResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DownloadVolumeResource.php index a922543..e5606a8 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DownloadVolumeResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/DownloadVolumeResource.php @@ -14,14 +14,14 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DownloadVolumePro provider: DownloadVolumeProvider::class, output: false, name: 'download_manga_volume' - ) + ), ] )] class DownloadVolumeResource { public function __construct( public string $id, - public int $volume + public int $volume, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/EditMangaResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/EditMangaResource.php index 292842c..1275c39 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/EditMangaResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/EditMangaResource.php @@ -4,6 +4,7 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Put; +use ApiPlatform\OpenApi\Model\Operation; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\EditMangaProcessor; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProvider; use Symfony\Component\Validator\Constraints as Assert; @@ -15,11 +16,11 @@ use Symfony\Component\Validator\Constraints as Assert; uriTemplate: '/mangas/{id}/edit', processor: EditMangaProcessor::class, provider: GetMangaStateProvider::class, - openapiContext: [ - 'summary' => 'Edit an existing manga', - 'description' => 'Updates an existing manga with provided data (partial update supported)' - ] - ) + openapi: new Operation( + summary: 'Edit an existing manga', + description: 'Updates an existing manga with provided data (partial update supported)' + ) + ), ] )] class EditMangaResource @@ -49,7 +50,7 @@ class EditMangaResource #[Assert\Type(type: 'array', message: 'Les slugs alternatifs doivent être une liste')] #[Assert\All([ new Assert\Type('string'), - new Assert\Regex(pattern: '/^[a-z0-9-]+$/', message: 'Chaque slug alternatif ne peut contenir que des lettres minuscules, des chiffres et des tirets') + new Assert\Regex(pattern: '/^[a-z0-9-]+$/', message: 'Chaque slug alternatif ne peut contenir que des lettres minuscules, des chiffres et des tirets'), ])] public ?array $alternativeSlugs = null; } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/EditMultipleChaptersResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/EditMultipleChaptersResource.php index 537dae1..7c966fe 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/EditMultipleChaptersResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/EditMultipleChaptersResource.php @@ -4,6 +4,7 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\EditMultipleChaptersProcessor; use Symfony\Component\Validator\Constraints as Assert; @@ -15,11 +16,11 @@ use Symfony\Component\Validator\Constraints as Assert; processor: EditMultipleChaptersProcessor::class, input: EditMultipleChaptersResource::class, status: 200, - openapiContext: [ - 'summary' => 'Edit multiple chapters', - 'description' => 'Updates title and/or volume for multiple chapters in a single request' - ] - ) + openapi: new Operation( + summary: 'Edit multiple chapters', + description: 'Updates title and/or volume for multiple chapters in a single request' + ) + ), ] )] class EditMultipleChaptersResource @@ -27,7 +28,7 @@ class EditMultipleChaptersResource public function __construct( #[Assert\NotBlank(message: 'La liste des chapitres est obligatoire')] #[Assert\Count(min: 1, minMessage: 'Vous devez spécifier au moins un chapitre')] - public readonly array $chapters + public readonly array $chapters, ) { } } @@ -37,7 +38,7 @@ readonly class ChapterEditData public function __construct( public string $id, public ?string $title = null, - public ?int $volume = null + public ?int $volume = null, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FetchMangaChaptersResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FetchMangaChaptersResource.php index d17fd15..130404f 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FetchMangaChaptersResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FetchMangaChaptersResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\RequestBody; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\FetchMangaChaptersProcessor; use Symfony\Component\Validator\Constraints as Assert; @@ -15,13 +17,12 @@ use Symfony\Component\Validator\Constraints as Assert; processor: FetchMangaChaptersProcessor::class, status: 202, description: 'Déclenche la récupération des chapitres d\'un manga', - openapiContext: [ - 'summary' => 'Récupérer les chapitres d\'un manga', - 'description' => 'Lance le processus de récupération des chapitres depuis la source externe pour un manga donné', - 'requestBody' => [ - 'description' => 'Données requises pour récupérer les chapitres', - 'required' => true, - 'content' => [ + openapi: new Operation( + summary: 'Récupérer les chapitres d\'un manga', + description: 'Lance le processus de récupération des chapitres depuis la source externe pour un manga donné', + requestBody: new RequestBody( + description: 'Données requises pour récupérer les chapitres', + content: new \ArrayObject([ 'application/json' => [ 'schema' => [ 'type' => 'object', @@ -31,23 +32,24 @@ use Symfony\Component\Validator\Constraints as Assert; // 'format' => 'uuid', 'description' => 'L\'identifiant unique du manga', // 'example' => '123e4567-e89b-12d3-a456-426614174000' - ] + ], ], - 'required' => ['mangaId'] - ] - ] - ] - ], - 'responses' => [ + 'required' => ['mangaId'], + ], + ], + ]), + required: true + ), + responses: [ '202' => [ - 'description' => 'Demande de récupération acceptée et mise en file d\'attente' + 'description' => 'Demande de récupération acceptée et mise en file d\'attente', ], '422' => [ - 'description' => 'Données de validation invalides' - ] + 'description' => 'Données de validation invalides', + ], ] - ] - ) + ) + ), ] )] class FetchMangaChaptersResource @@ -55,7 +57,7 @@ class FetchMangaChaptersResource public function __construct( #[Assert\NotBlank(message: 'L\'identifiant du manga est obligatoire')] // #[Assert\Uuid(message: 'L\'identifiant du manga doit être un UUID valide')] - public string $mangaId + public string $mangaId, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FindMangaMatchByFilenameResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FindMangaMatchByFilenameResource.php index 6e6fbdb..93a8d11 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FindMangaMatchByFilenameResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/FindMangaMatchByFilenameResource.php @@ -6,7 +6,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; -use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\FilenameMatchCollection; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\FindMangaMatchByFilenameStateProvider; #[ApiResource( @@ -15,22 +16,19 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\FindMangaMatchByF new Get( uriTemplate: '/manga-matches', provider: FindMangaMatchByFilenameStateProvider::class, - openapiContext: [ - 'summary' => 'Trouve des correspondances de manga à partir d\'un nom de fichier', - 'description' => 'Analyse un nom de fichier (cbz/cbr) et trouve les mangas correspondants dans la base de données. Extrait automatiquement le titre, le numéro de chapitre et le numéro de volume du nom de fichier.', - 'parameters' => [ - [ - 'name' => 'filename', - 'in' => 'query', - 'required' => true, - 'schema' => [ - 'type' => 'string', - 'example' => 'one-piece_vol108_ch1094.cbz' - ], - 'description' => 'Nom du fichier à analyser (avec ou sans extension .cbz/.cbr)' - ] + openapi: new Operation( + summary: 'Trouve des correspondances de manga à partir d\'un nom de fichier', + description: 'Analyse un nom de fichier (cbz/cbr) et trouve les mangas correspondants dans la base de données. Extrait automatiquement le titre, le numéro de chapitre et le numéro de volume du nom de fichier.', + parameters: [ + new Parameter( + name: 'filename', + in: 'query', + required: true, + schema: ['type' => 'string', 'example' => 'one-piece_vol108_ch1094.cbz'], + description: 'Nom du fichier à analyser (avec ou sans extension .cbz/.cbr)' + ), ], - 'responses' => [ + responses: [ '200' => [ 'description' => 'Correspondances trouvées', 'content' => [ @@ -50,31 +48,31 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\FindMangaMatchByF 'alternativeSlugs' => [ 'type' => 'array', 'items' => ['type' => 'string'], - 'description' => 'Slugs alternatifs' + 'description' => 'Slugs alternatifs', ], 'thumbnailUrl' => ['type' => 'string', 'nullable' => true, 'description' => 'URL de la miniature'], 'matchScore' => ['type' => 'integer', 'description' => 'Score de correspondance (plus élevé = meilleure correspondance)'], 'chapterNumber' => ['type' => 'number', 'nullable' => true, 'description' => 'Numéro de chapitre extrait'], - 'volumeNumber' => ['type' => 'number', 'nullable' => true, 'description' => 'Numéro de volume extrait'] - ] - ] + 'volumeNumber' => ['type' => 'number', 'nullable' => true, 'description' => 'Numéro de volume extrait'], + ], + ], ], 'chapterNumber' => [ 'type' => 'number', 'nullable' => true, - 'description' => 'Numéro de chapitre extrait du nom de fichier' + 'description' => 'Numéro de chapitre extrait du nom de fichier', ], 'volumeNumber' => [ 'type' => 'number', 'nullable' => true, - 'description' => 'Numéro de volume extrait du nom de fichier' + 'description' => 'Numéro de volume extrait du nom de fichier', ], 'possibleTitles' => [ 'type' => 'array', 'items' => ['type' => 'string'], - 'description' => 'Variantes de titres générées à partir du nom de fichier' - ] - ] + 'description' => 'Variantes de titres générées à partir du nom de fichier', + ], + ], ], 'example' => [ 'matches' => [ @@ -86,19 +84,19 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\FindMangaMatchByF 'thumbnailUrl' => 'https://example.com/thumb.jpg', 'matchScore' => 100, 'chapterNumber' => 1094.0, - 'volumeNumber' => 108 - ] + 'volumeNumber' => 108, + ], ], - ] - ] - ] + ], + ], + ], ], '400' => [ - 'description' => 'Nom de fichier manquant ou invalide' - ] + 'description' => 'Nom de fichier manquant ou invalide', + ], ] - ] - ) + ) + ), ] )] class FindMangaMatchByFilenameResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/GetMangaBySlugResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/GetMangaBySlugResource.php index 43380ea..a1d0ed3 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/GetMangaBySlugResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/GetMangaBySlugResource.php @@ -15,13 +15,13 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaBySlugSta provider: GetMangaBySlugStateProvider::class, output: MangaDetail::class, name: 'get_manga_by_slug' - ) + ), ] )] class GetMangaBySlugResource { public function __construct( - public string $slug + public string $slug, ) { } } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ImportChapterResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ImportChapterResource.php index 7f6d639..0e57cfe 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ImportChapterResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ImportChapterResource.php @@ -4,9 +4,10 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\RequestBody; use App\Domain\Manga\Infrastructure\ApiPlatform\Controller\ImportChapterController; use Symfony\Component\HttpFoundation\File\File; -use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( shortName: 'ImportChapter', @@ -15,11 +16,11 @@ use Symfony\Component\Validator\Constraints as Assert; uriTemplate: '/chapters/import', controller: ImportChapterController::class, deserialize: false, - openapiContext: [ - 'summary' => 'Import a chapter from CBZ file', - 'description' => 'Imports a CBZ file for an existing chapter and stores it', - 'requestBody' => [ - 'content' => [ + openapi: new Operation( + summary: 'Import a chapter from CBZ file', + description: 'Imports a CBZ file for an existing chapter and stores it', + requestBody: new RequestBody( + content: new \ArrayObject([ 'multipart/form-data' => [ 'schema' => [ 'type' => 'object', @@ -28,23 +29,23 @@ use Symfony\Component\Validator\Constraints as Assert; 'mangaId' => [ 'type' => 'string', 'format' => 'uuid', - 'description' => 'The manga UUID' + 'description' => 'The manga UUID', ], 'chapterNumber' => [ 'type' => 'number', - 'description' => 'The chapter number (e.g., 1.5)' + 'description' => 'The chapter number (e.g., 1.5)', ], 'file' => [ 'type' => 'string', 'format' => 'binary', - 'description' => 'CBZ file to import (max 500MB)' - ] - ] - ] - ] - ] - ], - 'responses' => [ + 'description' => 'CBZ file to import (max 500MB)', + ], + ], + ], + ], + ]) + ), + responses: [ '200' => [ 'description' => 'Chapter imported successfully', 'content' => [ @@ -53,15 +54,15 @@ use Symfony\Component\Validator\Constraints as Assert; 'type' => 'object', 'properties' => [ 'message' => ['type' => 'string'], - 'chapterId' => ['type' => 'string'] - ] - ] - ] - ] - ] + 'chapterId' => ['type' => 'string'], + ], + ], + ], + ], + ], ] - ] - ) + ) + ), ] )] class ImportChapterResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ImportVolumeResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ImportVolumeResource.php index cf3c865..bf629cb 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ImportVolumeResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ImportVolumeResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\RequestBody; use App\Domain\Manga\Infrastructure\ApiPlatform\Controller\ImportVolumeController; use Symfony\Component\HttpFoundation\File\File; @@ -14,11 +16,11 @@ use Symfony\Component\HttpFoundation\File\File; uriTemplate: '/volumes/import', controller: ImportVolumeController::class, deserialize: false, - openapiContext: [ - 'summary' => 'Import a volume from CBZ file', - 'description' => 'Imports a CBZ file for an existing volume and updates all chapters', - 'requestBody' => [ - 'content' => [ + openapi: new Operation( + summary: 'Import a volume from CBZ file', + description: 'Imports a CBZ file for an existing volume and updates all chapters', + requestBody: new RequestBody( + content: new \ArrayObject([ 'multipart/form-data' => [ 'schema' => [ 'type' => 'object', @@ -27,23 +29,23 @@ use Symfony\Component\HttpFoundation\File\File; 'mangaId' => [ 'type' => 'string', 'format' => 'uuid', - 'description' => 'The manga UUID' + 'description' => 'The manga UUID', ], 'volumeNumber' => [ 'type' => 'integer', - 'description' => 'The volume number' + 'description' => 'The volume number', ], 'file' => [ 'type' => 'string', 'format' => 'binary', - 'description' => 'CBZ file to import (max 500MB)' - ] - ] - ] - ] - ] - ], - 'responses' => [ + 'description' => 'CBZ file to import (max 500MB)', + ], + ], + ], + ], + ]) + ), + responses: [ '200' => [ 'description' => 'Volume imported successfully', 'content' => [ @@ -53,15 +55,15 @@ use Symfony\Component\HttpFoundation\File\File; 'properties' => [ 'message' => ['type' => 'string'], 'mangaId' => ['type' => 'string'], - 'volumeNumber' => ['type' => 'integer'] - ] - ] - ] - ] - ] + 'volumeNumber' => ['type' => 'integer'], + ], + ], + ], + ], + ], ] - ] - ) + ) + ), ] )] class ImportVolumeResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaChaptersResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaChaptersResource.php index 89be042..2cd8879 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaChaptersResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaChaptersResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\ChapterCollection; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaChaptersStateProvider; @@ -14,51 +16,39 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaChaptersS uriTemplate: '/mangas/{id}/chapters', provider: GetMangaChaptersStateProvider::class, output: ChapterCollection::class, - openapiContext: [ - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'schema' => [ - 'type' => 'string' - ], - 'description' => 'The manga identifier' - ], - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1 - ], - 'description' => 'The page number' - ], - [ - 'name' => 'limit', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 20 - ], - 'description' => 'Number of items per page' - ], - [ - 'name' => 'sortOrder', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'string', - 'enum' => ['asc', 'desc'], - 'default' => 'desc' - ], - 'description' => 'Sort order for chapters' - ] + openapi: new Operation( + parameters: [ + new Parameter( + name: 'id', + in: 'path', + required: true, + schema: ['type' => 'string'], + description: 'The manga identifier' + ), + new Parameter( + name: 'page', + in: 'query', + required: false, + schema: ['type' => 'integer', 'default' => 1], + description: 'The page number' + ), + new Parameter( + name: 'limit', + in: 'query', + required: false, + schema: ['type' => 'integer', 'default' => 20], + description: 'Number of items per page' + ), + new Parameter( + name: 'sortOrder', + in: 'query', + required: false, + schema: ['type' => 'string', 'enum' => ['asc', 'desc'], 'default' => 'desc'], + description: 'Sort order for chapters' + ), ] - ] - ) + ) + ), ] )] class MangaChaptersResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaDiscoverResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaDiscoverResource.php index 2c4cabf..8132153 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaDiscoverResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaDiscoverResource.php @@ -14,7 +14,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\DiscoverMangaStat uriTemplate: '/manga-discover', output: MangaSearchCollection::class, provider: DiscoverMangaStateProvider::class - ) + ), ] )] class MangaDiscoverResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaListResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaListResource.php index 04601ed..9697cb4 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaListResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaListResource.php @@ -3,7 +3,6 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaCollection; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaListStateProvider; @@ -15,7 +14,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaListState uriTemplate: '/mangas', provider: GetMangaListStateProvider::class, output: MangaCollection::class - ) + ), ] )] class MangaListResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaResource.php index e1e504f..f2f5482 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaDetail; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProvider; @@ -14,20 +16,18 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProv uriTemplate: '/mangas/by-id/{id}', provider: GetMangaStateProvider::class, output: MangaDetail::class, - openapiContext: [ - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'schema' => [ - 'type' => 'string' - ], - 'description' => 'The manga identifier' - ] + openapi: new Operation( + parameters: [ + new Parameter( + name: 'id', + in: 'path', + required: true, + schema: ['type' => 'string'], + description: 'The manga identifier' + ), ] - ] - ) + ) + ), ] )] class MangaResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaSearchResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaSearchResource.php index 008368a..de0d49d 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaSearchResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/MangaSearchResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaSearchCollection; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\SearchMangaStateProvider; @@ -12,22 +14,20 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\SearchMangaStateP operations: [ new Get( uriTemplate: '/mangadex-search', - openapiContext: [ - 'parameters' => [ - [ - 'name' => 'title', - 'in' => 'query', - 'required' => true, - 'schema' => [ - 'type' => 'string' - ], - 'description' => 'The title to search for' - ] + openapi: new Operation( + parameters: [ + new Parameter( + name: 'title', + in: 'query', + required: true, + schema: ['type' => 'string'], + description: 'The title to search for' + ), ] - ], + ), output: MangaSearchCollection::class, provider: SearchMangaStateProvider::class - ) + ), ] )] class MangaSearchResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/RefreshMangaChaptersResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/RefreshMangaChaptersResource.php index cb3fe4f..fa70545a 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/RefreshMangaChaptersResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/RefreshMangaChaptersResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\RefreshMangaChaptersProcessor; #[ApiResource( @@ -14,28 +16,28 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\RefreshMangaChap processor: RefreshMangaChaptersProcessor::class, status: 202, description: 'Déclenche la synchronisation et le scraping des nouveaux chapitres d\'un manga', - openapiContext: [ - 'summary' => 'Rafraîchir les chapitres d\'un manga', - 'description' => 'Lance la synchronisation incrémentale avec scraping automatique des nouveaux chapitres', - 'parameters' => [ - [ - 'name' => 'mangaId', - 'in' => 'path', - 'required' => true, - 'schema' => ['type' => 'string'], - 'description' => 'L\'identifiant unique du manga' - ] + openapi: new Operation( + summary: 'Rafraîchir les chapitres d\'un manga', + description: 'Lance la synchronisation incrémentale avec scraping automatique des nouveaux chapitres', + parameters: [ + new Parameter( + name: 'mangaId', + in: 'path', + required: true, + schema: ['type' => 'string'], + description: 'L\'identifiant unique du manga' + ), ], - 'responses' => [ + responses: [ '202' => [ - 'description' => 'Demande de refresh acceptée et mise en file d\'attente' + 'description' => 'Demande de refresh acceptée et mise en file d\'attente', ], '404' => [ - 'description' => 'Manga non trouvé' - ] + 'description' => 'Manga non trouvé', + ], ] - ] - ) + ) + ), ] )] class RefreshMangaChaptersResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/SearchLocalMangaResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/SearchLocalMangaResource.php index 1a6a5af..f00fa82 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/SearchLocalMangaResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/SearchLocalMangaResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaSearchCollection; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\SearchLocalMangaStateProvider; @@ -15,45 +17,33 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\SearchLocalMangaS provider: SearchLocalMangaStateProvider::class, output: MangaSearchCollection::class, status: 200, - openapiContext: [ - 'summary' => 'Recherche des mangas dans la bibliothèque locale', - 'description' => 'Recherche des mangas par titre, slug ou auteur (minimum 3 caractères)', - 'parameters' => [ - [ - 'name' => 'q', - 'in' => 'query', - 'required' => true, - 'schema' => [ - 'type' => 'string', - 'minLength' => 3 - ], - 'description' => 'Terme de recherche (minimum 3 caractères)' - ], - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 1, - 'minimum' => 1 - ], - 'description' => 'Numéro de page' - ], - [ - 'name' => 'limit', - 'in' => 'query', - 'required' => false, - 'schema' => [ - 'type' => 'integer', - 'default' => 20, - 'minimum' => 1, - 'maximum' => 50 - ], - 'description' => 'Nombre de résultats par page' - ] + openapi: new Operation( + summary: 'Recherche des mangas dans la bibliothèque locale', + description: 'Recherche des mangas par titre, slug ou auteur (minimum 3 caractères)', + parameters: [ + new Parameter( + name: 'q', + in: 'query', + required: true, + schema: ['type' => 'string', 'minLength' => 3], + description: 'Terme de recherche (minimum 3 caractères)' + ), + new Parameter( + name: 'page', + in: 'query', + required: false, + schema: ['type' => 'integer', 'default' => 1, 'minimum' => 1], + description: 'Numéro de page' + ), + new Parameter( + name: 'limit', + in: 'query', + required: false, + schema: ['type' => 'integer', 'default' => 20, 'minimum' => 1, 'maximum' => 50], + description: 'Nombre de résultats par page' + ), ], - 'responses' => [ + responses: [ '200' => [ 'description' => 'Résultats de la recherche', 'content' => [ @@ -64,20 +54,20 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\SearchLocalMangaS 'items' => [ 'type' => 'array', 'items' => [ - '$ref' => '#/components/schemas/MangaSearchItem' - ] - ] - ] - ] - ] - ] + '$ref' => '#/components/schemas/MangaSearchItem', + ], + ], + ], + ], + ], + ], ], '400' => [ - 'description' => 'Paramètres de recherche invalides' - ] + 'description' => 'Paramètres de recherche invalides', + ], ] - ] - ) + ) + ), ] )] class SearchLocalMangaResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ToggleMonitoringResource.php b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ToggleMonitoringResource.php index fcbfc7f..20525a7 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ToggleMonitoringResource.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/Resource/ToggleMonitoringResource.php @@ -4,6 +4,9 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; +use ApiPlatform\OpenApi\Model\RequestBody; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\ToggleMonitoringProcessor; use Symfony\Component\Validator\Constraints as Assert; @@ -16,22 +19,21 @@ use Symfony\Component\Validator\Constraints as Assert; read: false, status: 204, description: 'Active ou désactive le monitoring automatique d\'un manga', - openapiContext: [ - 'summary' => 'Activer/Désactiver le monitoring d\'un manga', - 'description' => 'Active ou désactive le monitoring automatique pour recevoir les nouveaux chapitres', - 'parameters' => [ - [ - 'name' => 'mangaId', - 'in' => 'path', - 'required' => true, - 'schema' => ['type' => 'string'], - 'description' => 'L\'identifiant unique du manga' - ] + openapi: new Operation( + summary: 'Activer/Désactiver le monitoring d\'un manga', + description: 'Active ou désactive le monitoring automatique pour recevoir les nouveaux chapitres', + parameters: [ + new Parameter( + name: 'mangaId', + in: 'path', + required: true, + schema: ['type' => 'string'], + description: 'L\'identifiant unique du manga' + ), ], - 'requestBody' => [ - 'description' => 'État du monitoring à appliquer', - 'required' => true, - 'content' => [ + requestBody: new RequestBody( + description: 'État du monitoring à appliquer', + content: new \ArrayObject([ 'application/json' => [ 'schema' => [ 'type' => 'object', @@ -39,27 +41,28 @@ use Symfony\Component\Validator\Constraints as Assert; 'enabled' => [ 'type' => 'boolean', 'description' => 'True pour activer le monitoring, false pour le désactiver', - 'example' => true - ] + 'example' => true, + ], ], - 'required' => ['enabled'] - ] - ] - ] - ], - 'responses' => [ + 'required' => ['enabled'], + ], + ], + ]), + required: true + ), + responses: [ '204' => [ - 'description' => 'Monitoring modifié avec succès' + 'description' => 'Monitoring modifié avec succès', ], '404' => [ - 'description' => 'Manga non trouvé' + 'description' => 'Manga non trouvé', ], '422' => [ - 'description' => 'Données de validation invalides' - ] + 'description' => 'Données de validation invalides', + ], ] - ] - ) + ) + ), ] )] class ToggleMonitoringResource diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaDirectlyProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaDirectlyProcessor.php index 6e8a0a8..b951fe2 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaDirectlyProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaDirectlyProcessor.php @@ -11,7 +11,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\CreateMangaDirectlyReso readonly class CreateMangaDirectlyProcessor implements ProcessorInterface { public function __construct( - private CreateMangaHandler $handler + private CreateMangaHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaProcessor.php index bb6813c..43e8c9f 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/CreateMangaProcessor.php @@ -12,7 +12,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class CreateMangaProcessor implements ProcessorInterface { public function __construct( - private CreateMangaFromMangadexHandler $handler + private CreateMangaFromMangadexHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteCbzProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteCbzProcessor.php index af5c985..a85fec4 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteCbzProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteCbzProcessor.php @@ -11,7 +11,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\DeleteCbzResource; readonly class DeleteCbzProcessor implements ProcessorInterface { public function __construct( - private DeleteCbzHandler $handler + private DeleteCbzHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteChapterProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteChapterProcessor.php index 2ebfcac..c0c0509 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteChapterProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteChapterProcessor.php @@ -12,7 +12,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DeleteChapterProcessor implements ProcessorInterface { public function __construct( - private DeleteChapterHandler $handler + private DeleteChapterHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteMangaProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteMangaProcessor.php index 77defac..19988c9 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteMangaProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/DeleteMangaProcessor.php @@ -13,7 +13,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DeleteMangaProcessor implements ProcessorInterface { public function __construct( - private DeleteMangaHandler $handler + private DeleteMangaHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/EditMangaProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/EditMangaProcessor.php index be76584..12c3d74 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/EditMangaProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/EditMangaProcessor.php @@ -13,7 +13,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class EditMangaProcessor implements ProcessorInterface { public function __construct( - private EditMangaHandler $handler + private EditMangaHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/EditMultipleChaptersProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/EditMultipleChaptersProcessor.php index 95c9957..933983a 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/EditMultipleChaptersProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/EditMultipleChaptersProcessor.php @@ -4,8 +4,8 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; -use App\Domain\Manga\Application\Command\EditMultipleChapters; use App\Domain\Manga\Application\Command\ChapterEditData; +use App\Domain\Manga\Application\Command\EditMultipleChapters; use App\Domain\Manga\Application\CommandHandler\EditMultipleChaptersHandler; use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\EditMultipleChaptersResource; @@ -15,7 +15,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class EditMultipleChaptersProcessor implements ProcessorInterface { public function __construct( - private EditMultipleChaptersHandler $handler + private EditMultipleChaptersHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/FetchMangaChaptersProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/FetchMangaChaptersProcessor.php index 59dfe7b..14a9099 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/FetchMangaChaptersProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/FetchMangaChaptersProcessor.php @@ -12,7 +12,7 @@ use Symfony\Component\Messenger\MessageBusInterface; readonly class FetchMangaChaptersProcessor implements ProcessorInterface { public function __construct( - private MessageBusInterface $messageBus + private MessageBusInterface $messageBus, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/RefreshMangaChaptersProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/RefreshMangaChaptersProcessor.php index 62a4683..cf5e5f4 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/RefreshMangaChaptersProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/RefreshMangaChaptersProcessor.php @@ -14,7 +14,7 @@ readonly class RefreshMangaChaptersProcessor implements ProcessorInterface { public function __construct( private MessageBusInterface $commandBus, - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/ToggleMonitoringProcessor.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/ToggleMonitoringProcessor.php index 6886073..3fa0e0b 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/ToggleMonitoringProcessor.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Processor/ToggleMonitoringProcessor.php @@ -14,7 +14,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class ToggleMonitoringProcessor implements ProcessorInterface { public function __construct( - private ToggleMangaMonitoringHandler $handler + private ToggleMangaMonitoringHandler $handler, ) { } @@ -31,7 +31,7 @@ readonly class ToggleMonitoringProcessor implements ProcessorInterface } // La validation Symfony s'assure que enabled est un booléen valide - if ($data->enabled === null) { + if (null === $data->enabled) { throw new \InvalidArgumentException('Enabled field is required'); } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteCbzProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteCbzProvider.php index 5423d23..89516d0 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteCbzProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteCbzProvider.php @@ -5,15 +5,15 @@ namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface; -use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; use App\Domain\Manga\Domain\Exception\CbzFileNotFoundException; +use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\DeleteCbzResource; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DeleteCbzProvider implements ProviderInterface { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } @@ -37,7 +37,6 @@ readonly class DeleteCbzProvider implements ProviderInterface } return new DeleteCbzResource($chapterId); - } catch (ChapterNotFoundException $e) { throw new NotFoundHttpException('Chapter not found'); } catch (CbzFileNotFoundException $e) { diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteChapterProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteChapterProvider.php index aada1d3..4210481 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteChapterProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteChapterProvider.php @@ -12,7 +12,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DeleteChapterProvider implements ProviderInterface { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } @@ -32,7 +32,6 @@ readonly class DeleteChapterProvider implements ProviderInterface } return new DeleteChapterResource($chapterId); - } catch (ChapterNotFoundException $e) { throw new NotFoundHttpException('Chapter not found'); } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteMangaProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteMangaProvider.php index 55198ec..cad1395 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteMangaProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DeleteMangaProvider.php @@ -12,7 +12,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DeleteMangaProvider implements ProviderInterface { public function __construct( - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } @@ -32,7 +32,6 @@ readonly class DeleteMangaProvider implements ProviderInterface } return new DeleteMangaResource($mangaId); - } catch (MangaNotFoundException $e) { throw new NotFoundHttpException('Manga not found'); } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DownloadCbzProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DownloadCbzProvider.php index 15f9e3c..3c28be7 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DownloadCbzProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DownloadCbzProvider.php @@ -6,15 +6,15 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use App\Domain\Manga\Application\Query\DownloadCbz; use App\Domain\Manga\Application\QueryHandler\DownloadCbzHandler; -use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; use App\Domain\Manga\Domain\Exception\ChapterNotAvailableException; +use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DownloadCbzProvider implements ProviderInterface { public function __construct( - private DownloadCbzHandler $handler + private DownloadCbzHandler $handler, ) { } @@ -28,6 +28,7 @@ readonly class DownloadCbzProvider implements ProviderInterface try { $downloadResponse = $this->handler->handle($query); + return $downloadResponse->httpResponse; } catch (ChapterNotAvailableException|ChapterNotFoundException $e) { throw new NotFoundHttpException($e->getMessage()); diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DownloadVolumeProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DownloadVolumeProvider.php index 83d9420..212af88 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DownloadVolumeProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/DownloadVolumeProvider.php @@ -14,7 +14,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DownloadVolumeProvider implements ProviderInterface { public function __construct( - private DownloadVolumeHandler $handler + private DownloadVolumeHandler $handler, ) { } @@ -28,6 +28,7 @@ readonly class DownloadVolumeProvider implements ProviderInterface try { $downloadResponse = $this->handler->handle($query); + return $downloadResponse->httpResponse; } catch (MangaNotFoundException|VolumeNotFoundException $e) { throw new NotFoundHttpException($e->getMessage()); diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/FindMangaMatchByFilenameStateProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/FindMangaMatchByFilenameStateProvider.php index 2b4a49a..8f8577c 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/FindMangaMatchByFilenameStateProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/FindMangaMatchByFilenameStateProvider.php @@ -15,7 +15,7 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; readonly class FindMangaMatchByFilenameStateProvider implements ProviderInterface { public function __construct( - private FindMangaMatchByFilenameHandler $handler + private FindMangaMatchByFilenameHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaBySlugStateProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaBySlugStateProvider.php index e67cfd1..3c4e7ee 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaBySlugStateProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaBySlugStateProvider.php @@ -11,7 +11,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaDetail; readonly class GetMangaBySlugStateProvider implements ProviderInterface { public function __construct( - private GetMangaBySlugHandler $handler + private GetMangaBySlugHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaChaptersStateProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaChaptersStateProvider.php index c329d3b..5e89c57 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaChaptersStateProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaChaptersStateProvider.php @@ -13,7 +13,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\ChapterListItem; readonly class GetMangaChaptersStateProvider implements ProviderInterface { public function __construct( - private GetMangaChaptersHandler $handler + private GetMangaChaptersHandler $handler, ) { } @@ -53,7 +53,7 @@ readonly class GetMangaChaptersStateProvider implements ProviderInterface title: $chapter->title, volume: $chapter->volume, isVisible: $chapter->isVisible, - isAvailable: $chapter->pagesDirectory !== null, + isAvailable: null !== $chapter->pagesDirectory, createdAt: $chapter->createdAt, isVolumeGroup: $chapter->isVolumeGroup, volumeChaptersRange: $chapter->volumeChaptersRange, diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaListStateProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaListStateProvider.php index 35d6d22..2f5793e 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaListStateProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaListStateProvider.php @@ -13,7 +13,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaListItem; readonly class GetMangaListStateProvider implements ProviderInterface { public function __construct( - private GetMangaListHandler $handler + private GetMangaListHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaStateProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaStateProvider.php index f3b3aad..51ceb4f 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaStateProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/GetMangaStateProvider.php @@ -11,7 +11,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaDetail; readonly class GetMangaStateProvider implements ProviderInterface { public function __construct( - private GetMangaByIdHandler $handler + private GetMangaByIdHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/SearchLocalMangaStateProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/SearchLocalMangaStateProvider.php index 8e89cfb..1031895 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/SearchLocalMangaStateProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/SearchLocalMangaStateProvider.php @@ -13,7 +13,7 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; readonly class SearchLocalMangaStateProvider implements ProviderInterface { public function __construct( - private SearchLocalMangaHandler $handler + private SearchLocalMangaHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/SearchMangaStateProvider.php b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/SearchMangaStateProvider.php index 8782462..15790f1 100644 --- a/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/SearchMangaStateProvider.php +++ b/src/Domain/Manga/Infrastructure/ApiPlatform/State/Provider/SearchMangaStateProvider.php @@ -12,7 +12,7 @@ use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaSearchItem; readonly class SearchMangaStateProvider implements ProviderInterface { public function __construct( - private SearchMangaHandler $handler + private SearchMangaHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/Client/MangadexClient.php b/src/Domain/Manga/Infrastructure/Client/MangadexClient.php index 7e437e6..70470a7 100644 --- a/src/Domain/Manga/Infrastructure/Client/MangadexClient.php +++ b/src/Domain/Manga/Infrastructure/Client/MangadexClient.php @@ -11,7 +11,7 @@ class MangadexClient implements MangadexClientInterface { private const API_URL = 'https://api.mangadex.org'; private const AUTH_URL = 'https://auth.mangadex.org/realms/mangadex/protocol/openid-connect/token'; - private const EXCLUDED_TAGS = ['0234a31e-a729-4e28-9d6a-3f87c4966b9e' , 'b13b2a48-c720-44a9-9c77-39c9979373fb', '891cf039-b895-47f0-9229-bef4c96eccd4']; + private const EXCLUDED_TAGS = ['0234a31e-a729-4e28-9d6a-3f87c4966b9e', 'b13b2a48-c720-44a9-9c77-39c9979373fb', '891cf039-b895-47f0-9229-bef4c96eccd4']; private ?string $accessToken = null; private ?string $refreshToken = null; @@ -21,7 +21,7 @@ class MangadexClient implements MangadexClientInterface private string $clientId, private string $clientSecret, private string $username, - private string $password + private string $password, ) { } @@ -35,7 +35,7 @@ class MangadexClient implements MangadexClientInterface 'password' => $this->password, 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, - ] + ], ]); $data = $response->toArray(); @@ -47,10 +47,7 @@ class MangadexClient implements MangadexClientInterface $this->accessToken = $data['access_token']; $this->refreshToken = $data['refresh_token']; } catch (\Exception $e) { - throw new MangadexAuthenticationException( - 'Failed to authenticate with Mangadex: ' . $e->getMessage(), - $e - ); + throw new MangadexAuthenticationException('Failed to authenticate with Mangadex: '.$e->getMessage(), $e); } } @@ -67,7 +64,7 @@ class MangadexClient implements MangadexClientInterface 'refresh_token' => $this->refreshToken, 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, - ] + ], ]); $data = $response->toArray(); @@ -79,10 +76,7 @@ class MangadexClient implements MangadexClientInterface $this->accessToken = $data['access_token']; $this->refreshToken = $data['refresh_token'] ?? $this->refreshToken; } catch (\Exception $e) { - throw new MangadexAuthenticationException( - 'Failed to refresh token: ' . $e->getMessage(), - $e - ); + throw new MangadexAuthenticationException('Failed to refresh token: '.$e->getMessage(), $e); } } @@ -106,7 +100,7 @@ class MangadexClient implements MangadexClientInterface public function getMangaFeed(string $mangaId, int $offset = 0, int $limit = 500, string $order = 'asc'): array { - return $this->get('/manga/' . $mangaId . '/feed', [ + return $this->get('/manga/'.$mangaId.'/feed', [ 'limit' => $limit, // 'translatedLanguage' => ['en'], 'includeUnavailable' => 1, @@ -117,13 +111,13 @@ class MangadexClient implements MangadexClientInterface public function getMangaAggregate(string $mangaId): array { - return $this->get('/manga/' . $mangaId . '/aggregate'); + return $this->get('/manga/'.$mangaId.'/aggregate'); } public function getManga(string $mangaId): array { - return $this->get('/manga/' . $mangaId, [ - 'includes' => ['cover_art', 'author'] + return $this->get('/manga/'.$mangaId, [ + 'includes' => ['cover_art', 'author'], ]); } @@ -132,12 +126,12 @@ class MangadexClient implements MangadexClientInterface // L'endpoint retourne des objets manga_recommendation avec des relationships // vers les manga (sans détails). Il faut d'abord récupérer les IDs, puis // fetcher les manga en batch avec leurs détails complets. - $recommendations = $this->get('/manga/' . $mangaId . '/recommendation'); + $recommendations = $this->get('/manga/'.$mangaId.'/recommendation'); $recommendedIds = []; foreach ($recommendations['data'] ?? [] as $item) { foreach ($item['relationships'] ?? [] as $rel) { - if ($rel['type'] === 'manga' && $rel['id'] !== $mangaId) { + if ('manga' === $rel['type'] && $rel['id'] !== $mangaId) { $recommendedIds[] = $rel['id']; } } @@ -148,11 +142,11 @@ class MangadexClient implements MangadexClientInterface } return $this->get('/manga', [ - 'ids' => $recommendedIds, - 'includes' => ['cover_art', 'author'], + 'ids' => $recommendedIds, + 'includes' => ['cover_art', 'author'], 'contentRating' => ['safe', 'suggestive', 'erotica'], - 'excludedTags' => self::EXCLUDED_TAGS, - 'limit' => count($recommendedIds), + 'excludedTags' => self::EXCLUDED_TAGS, + 'limit' => count($recommendedIds), ]); } @@ -163,21 +157,21 @@ class MangadexClient implements MangadexClientInterface $this->authenticate(); } - $response = $this->client->request('GET', self::API_URL . $endpoint, [ + $response = $this->client->request('GET', self::API_URL.$endpoint, [ 'query' => $params, 'headers' => [ - 'Authorization' => 'Bearer ' . $this->accessToken - ] + 'Authorization' => 'Bearer '.$this->accessToken, + ], ]); // Handle 401 (Unauthorized) by refreshing the token and retrying - if ($response->getStatusCode() === 401) { + if (401 === $response->getStatusCode()) { $this->refreshToken(); - $response = $this->client->request('GET', self::API_URL . $endpoint, [ + $response = $this->client->request('GET', self::API_URL.$endpoint, [ 'query' => $params, 'headers' => [ - 'Authorization' => 'Bearer ' . $this->accessToken - ] + 'Authorization' => 'Bearer '.$this->accessToken, + ], ]); } @@ -185,10 +179,7 @@ class MangadexClient implements MangadexClientInterface } catch (MangadexAuthenticationException $e) { throw $e; } catch (\Exception $e) { - throw new MangadexApiException( - sprintf('Failed to fetch data from Mangadex: %s', $e->getMessage()), - $e - ); + throw new MangadexApiException(sprintf('Failed to fetch data from Mangadex: %s', $e->getMessage()), $e); } } } diff --git a/src/Domain/Manga/Infrastructure/CommandHandler/SymfonyFetchMangaChaptersHandler.php b/src/Domain/Manga/Infrastructure/CommandHandler/SymfonyFetchMangaChaptersHandler.php index ebeedc9..6499577 100644 --- a/src/Domain/Manga/Infrastructure/CommandHandler/SymfonyFetchMangaChaptersHandler.php +++ b/src/Domain/Manga/Infrastructure/CommandHandler/SymfonyFetchMangaChaptersHandler.php @@ -10,7 +10,7 @@ use Symfony\Component\Messenger\Attribute\AsMessageHandler; readonly class SymfonyFetchMangaChaptersHandler { public function __construct( - private FetchMangaChaptersHandler $handler + private FetchMangaChaptersHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/CommandHandler/SymfonyRefreshMangaChaptersHandler.php b/src/Domain/Manga/Infrastructure/CommandHandler/SymfonyRefreshMangaChaptersHandler.php index d752d9a..b170c15 100644 --- a/src/Domain/Manga/Infrastructure/CommandHandler/SymfonyRefreshMangaChaptersHandler.php +++ b/src/Domain/Manga/Infrastructure/CommandHandler/SymfonyRefreshMangaChaptersHandler.php @@ -10,7 +10,7 @@ use Symfony\Component\Messenger\Attribute\AsMessageHandler; readonly class SymfonyRefreshMangaChaptersHandler { public function __construct( - private RefreshMangaChaptersHandler $handler + private RefreshMangaChaptersHandler $handler, ) { } diff --git a/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php b/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php index 21707ca..b276201 100644 --- a/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php +++ b/src/Domain/Manga/Infrastructure/Persistence/LegacyMangaRepository.php @@ -4,24 +4,23 @@ namespace App\Domain\Manga\Infrastructure\Persistence; use App\Domain\Manga\Application\Query\MonitoringCriteria; use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface; +use App\Domain\Manga\Domain\Model\Chapter; use App\Domain\Manga\Domain\Model\Manga as DomainManga; +use App\Domain\Manga\Domain\Model\ValueObject\ChapterId; use App\Domain\Manga\Domain\Model\ValueObject\ExternalId; use App\Domain\Manga\Domain\Model\ValueObject\ImageUrls; use App\Domain\Manga\Domain\Model\ValueObject\MangaId; use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug; use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle; use App\Domain\Manga\Domain\Model\ValueObject\MonitoringStatus; +use App\Entity\Chapter as EntityChapter; use App\Entity\Manga as EntityManga; use Doctrine\ORM\EntityManagerInterface; -use App\Domain\Manga\Domain\Model\Chapter; -use App\Domain\Manga\Domain\Model\ValueObject\ChapterId; -use App\Entity\Chapter as EntityChapter; -use DateTime; readonly class LegacyMangaRepository implements MangaRepositoryInterface { public function __construct( - private EntityManagerInterface $entityManager + private EntityManagerInterface $entityManager, ) { } @@ -105,7 +104,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface $entity->setExternalId($manga->getExternalId()->getValue()); } - if ($manga->getRating() !== null) { + if (null !== $manga->getRating()) { $entity->setRating($manga->getRating()); } @@ -226,7 +225,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface public function findByExternalId(ExternalId $externalId): ?DomainManga { $entity = $this->entityManager->getRepository(EntityManga::class)->findOneBy([ - 'externalId' => $externalId->getValue() + 'externalId' => $externalId->getValue(), ]); return $entity ? $this->toDomain($entity) : null; @@ -308,12 +307,12 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface $offset = ($page - 1) * $limit; // Utiliser une requête native pour supporter la recherche dans le champ JSON AlternativeSlugs - $sql = "SELECT m.* FROM manga m + $sql = 'SELECT m.* FROM manga m WHERE m.title LIKE :query OR m.slug LIKE :query OR CAST(m.alternative_slugs AS TEXT) LIKE :query ORDER BY m.title ASC - LIMIT :limit OFFSET :offset"; + LIMIT :limit OFFSET :offset'; $rsm = new \Doctrine\ORM\Query\ResultSetMapping(); $rsm->addEntityResult(EntityManga::class, 'm'); @@ -335,7 +334,7 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface $rsm->addFieldResult('m', 'alternative_slugs', 'AlternativeSlugs'); $nativeQuery = $this->entityManager->createNativeQuery($sql, $rsm); - $nativeQuery->setParameter('query', '%' . $query . '%'); + $nativeQuery->setParameter('query', '%'.$query.'%'); $nativeQuery->setParameter('limit', $limit); $nativeQuery->setParameter('offset', $offset); @@ -348,22 +347,22 @@ readonly class LegacyMangaRepository implements MangaRepositoryInterface public function countSearch(string $query): int { // Utiliser une requête native pour supporter la recherche dans le champ JSON AlternativeSlugs - $sql = "SELECT COUNT(m.id) FROM manga m + $sql = 'SELECT COUNT(m.id) FROM manga m WHERE m.title LIKE :query OR m.slug LIKE :query OR m.author LIKE :query OR m.description LIKE :query - OR CAST(m.alternative_slugs AS TEXT) LIKE :query"; + OR CAST(m.alternative_slugs AS TEXT) LIKE :query'; $conn = $this->entityManager->getConnection(); - $stmt = $conn->prepare($sql); - $result = $stmt->executeQuery(['query' => '%' . $query . '%']); + $result = $conn->executeQuery($sql, ['query' => '%'.$query.'%']); return (int) $result->fetchOne(); } /** * @param float[] $chapterNumbers + * * @return array */ public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array diff --git a/src/Domain/Manga/Infrastructure/Provider/MangadexProvider.php b/src/Domain/Manga/Infrastructure/Provider/MangadexProvider.php index 860a121..e447014 100644 --- a/src/Domain/Manga/Infrastructure/Provider/MangadexProvider.php +++ b/src/Domain/Manga/Infrastructure/Provider/MangadexProvider.php @@ -17,7 +17,7 @@ readonly class MangadexProvider implements MangaProviderInterface { public function __construct( private MangadexClientInterface $client, - private SluggerInterface $slugger + private SluggerInterface $slugger, ) { } @@ -39,6 +39,7 @@ readonly class MangadexProvider implements MangaProviderInterface /** * @param array $results + * * @return Manga[] */ private function createMangasFromResults(array $results): array @@ -46,7 +47,7 @@ readonly class MangadexProvider implements MangaProviderInterface $mangas = []; foreach ($results as $result) { $manga = $this->createMangaFromResult($result); - if ($manga !== null) { + if (null !== $manga) { $mangas[] = $manga; } } @@ -77,10 +78,10 @@ readonly class MangadexProvider implements MangaProviderInterface $author = ''; $imageUrl = null; foreach ($result['relationships'] as $relationship) { - if ($relationship['type'] === 'author') { + if ('author' === $relationship['type']) { $author = $relationship['attributes']['name']; } - if ($relationship['type'] === 'cover_art') { + if ('cover_art' === $relationship['type']) { $imageUrl = sprintf( 'https://uploads.mangadex.org/covers/%s/%s.512.jpg', $result['id'], @@ -171,7 +172,7 @@ readonly class MangadexProvider implements MangaProviderInterface // Trier : votes décroissants (multi-sources = plus pertinent), puis position croissante (score API) uksort($resultsById, function (string $a, string $b) use ($votes, $firstPosition): int { $voteDiff = $votes[$b] - $votes[$a]; - if ($voteDiff !== 0) { + if (0 !== $voteDiff) { return $voteDiff; } diff --git a/src/Domain/Manga/Infrastructure/Scheduler/MonitoringSchedule.php b/src/Domain/Manga/Infrastructure/Scheduler/MonitoringSchedule.php index d3d145b..6081e51 100644 --- a/src/Domain/Manga/Infrastructure/Scheduler/MonitoringSchedule.php +++ b/src/Domain/Manga/Infrastructure/Scheduler/MonitoringSchedule.php @@ -3,7 +3,6 @@ namespace App\Domain\Manga\Infrastructure\Scheduler; use App\Domain\Manga\Application\Command\CheckMonitoredMangas; -use DateTimeImmutable; use Symfony\Component\Scheduler\Attribute\AsSchedule; use Symfony\Component\Scheduler\RecurringMessage; use Symfony\Component\Scheduler\Schedule; @@ -14,7 +13,7 @@ use Symfony\Contracts\Cache\CacheInterface; class MonitoringSchedule implements ScheduleProviderInterface { public function __construct( - private CacheInterface $cache + private CacheInterface $cache, ) { } @@ -23,7 +22,7 @@ class MonitoringSchedule implements ScheduleProviderInterface return (new Schedule())->add( // Toutes les 2 heures, vérifie les mangas qui n'ont pas été vérifiés depuis 2 heures RecurringMessage::every('2 hours', new CheckMonitoredMangas( - new DateTimeImmutable('-2 hours') + new \DateTimeImmutable('-2 hours') )) )->stateful($this->cache); } diff --git a/src/Domain/Manga/Infrastructure/Service/FileService.php b/src/Domain/Manga/Infrastructure/Service/FileService.php index 32d3834..688119d 100644 --- a/src/Domain/Manga/Infrastructure/Service/FileService.php +++ b/src/Domain/Manga/Infrastructure/Service/FileService.php @@ -4,14 +4,14 @@ namespace App\Domain\Manga\Infrastructure\Service; use App\Domain\Manga\Domain\Contract\Service\FileServiceInterface; use App\Domain\Manga\Domain\Exception\CbzFileNotFoundException; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; readonly class FileService implements FileServiceInterface { public function __construct( - private string $cbzStoragePath = '/app/public/cbz' + private string $cbzStoragePath = '/app/public/cbz', ) { } @@ -33,10 +33,10 @@ readonly class FileService implements FileServiceInterface public function createVolumeCbz(array $cbzPaths, string $volumeName): Response { - $tempCbzPath = sys_get_temp_dir() . '/' . $volumeName . '.cbz'; + $tempCbzPath = sys_get_temp_dir().'/'.$volumeName.'.cbz'; $cbz = new \ZipArchive(); - if ($cbz->open($tempCbzPath, \ZipArchive::CREATE) !== true) { + if (true !== $cbz->open($tempCbzPath, \ZipArchive::CREATE)) { throw new \RuntimeException('Cannot create CBZ file'); } @@ -48,23 +48,23 @@ readonly class FileService implements FileServiceInterface } $sourceCbz = new \ZipArchive(); - if ($sourceCbz->open($cbzPath) !== true) { + if (true !== $sourceCbz->open($cbzPath)) { continue; // Skip if we can't open the CBZ } // Extract all images from the current CBZ - for ($i = 0; $i < $sourceCbz->numFiles; $i++) { + for ($i = 0; $i < $sourceCbz->numFiles; ++$i) { $fileName = $sourceCbz->getNameIndex($i); $fileInfo = $sourceCbz->statIndex($i); // Skip directories and non-image files - if ($fileInfo['size'] === 0 || !$this->isImageFile($fileName)) { + if (0 === $fileInfo['size'] || !$this->isImageFile($fileName)) { continue; } // Get the file content $imageContent = $sourceCbz->getFromIndex($i); - if ($imageContent === false) { + if (false === $imageContent) { continue; } @@ -77,7 +77,7 @@ readonly class FileService implements FileServiceInterface // Add the image to the volume CBZ $cbz->addFromString($newFileName, $imageContent); - $imageCounter++; + ++$imageCounter; } $sourceCbz->close(); @@ -88,7 +88,7 @@ readonly class FileService implements FileServiceInterface $response = new BinaryFileResponse($tempCbzPath); $response->setContentDisposition( ResponseHeaderBag::DISPOSITION_ATTACHMENT, - $volumeName . '.cbz' + $volumeName.'.cbz' ); $response->headers->set('Content-Type', 'application/x-cbz'); diff --git a/src/Domain/Manga/Infrastructure/Service/FilenameAnalyzer.php b/src/Domain/Manga/Infrastructure/Service/FilenameAnalyzer.php index ae02a4b..4563fe8 100644 --- a/src/Domain/Manga/Infrastructure/Service/FilenameAnalyzer.php +++ b/src/Domain/Manga/Infrastructure/Service/FilenameAnalyzer.php @@ -26,8 +26,8 @@ readonly class FilenameAnalyzer implements FilenameAnalyzerInterface return new AnalyzedFilename( title: new MangaTitle($cleanedTitle), - chapterNumber: $chapterNumber !== null ? new ChapterNumber($chapterNumber) : null, - volumeNumber: $volumeNumber !== null ? new VolumeNumber((float) $volumeNumber) : null + chapterNumber: null !== $chapterNumber ? new ChapterNumber($chapterNumber) : null, + volumeNumber: null !== $volumeNumber ? new VolumeNumber((float) $volumeNumber) : null ); } diff --git a/src/Domain/Manga/Infrastructure/Service/ImageProcessor.php b/src/Domain/Manga/Infrastructure/Service/ImageProcessor.php index 2bf5171..22f7568 100644 --- a/src/Domain/Manga/Infrastructure/Service/ImageProcessor.php +++ b/src/Domain/Manga/Infrastructure/Service/ImageProcessor.php @@ -3,17 +3,16 @@ namespace App\Domain\Manga\Infrastructure\Service; use App\Domain\Manga\Domain\Contract\Service\ImageProcessorInterface; +use GuzzleHttp\Client; use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\ImageManager; -use GuzzleHttp\Client; use Ramsey\Uuid\Uuid; -use Symfony\Component\HttpFoundation\File\Exception\FileException; class ImageProcessor implements ImageProcessorInterface { private const string BASE_PATH = '/images'; - private const string FULL_IMAGES_DIR = self::BASE_PATH . '/full'; - private const string THUMBNAILS_DIR = self::BASE_PATH . '/thumbnails'; + private const string FULL_IMAGES_DIR = self::BASE_PATH.'/full'; + private const string THUMBNAILS_DIR = self::BASE_PATH.'/thumbnails'; private const int THUMBNAIL_WIDTH = 300; private const int THUMBNAIL_HEIGHT = 440; private const int FULL_IMAGE_QUALITY = 90; @@ -23,7 +22,7 @@ class ImageProcessor implements ImageProcessorInterface public function __construct( private readonly string $publicDir, - private readonly Client $httpClient + private readonly Client $httpClient, ) { $this->imageManager = new ImageManager(new Driver()); $this->ensureDirectoriesExist(); @@ -34,50 +33,50 @@ class ImageProcessor implements ImageProcessorInterface try { $response = $this->httpClient->get($imageUrl); - if ($response->getStatusCode() !== 200) { + if (200 !== $response->getStatusCode()) { throw new \RuntimeException('Échec du téléchargement de l\'image'); } $uuid = Uuid::uuid4()->toString(); $extension = pathinfo($imageUrl, PATHINFO_EXTENSION) ?: 'jpg'; $filename = sprintf('%s.%s', $uuid, $extension); - $fullPath = $this->publicDir . self::FULL_IMAGES_DIR . '/' . $filename; + $fullPath = $this->publicDir.self::FULL_IMAGES_DIR.'/'.$filename; $image = $this->imageManager->read($response->getBody()->getContents()); $image->save($fullPath, quality: self::FULL_IMAGE_QUALITY); - return self::FULL_IMAGES_DIR . '/' . $filename; + return self::FULL_IMAGES_DIR.'/'.$filename; } catch (\Exception $e) { - throw new \RuntimeException('Erreur lors du téléchargement de l\'image : ' . $e->getMessage()); + throw new \RuntimeException('Erreur lors du téléchargement de l\'image : '.$e->getMessage()); } } public function createThumbnail(string $originalImagePath): string { try { - $fullPath = $this->publicDir . $originalImagePath; + $fullPath = $this->publicDir.$originalImagePath; if (!file_exists($fullPath)) { throw new \RuntimeException('Image originale non trouvée'); } $filename = pathinfo($originalImagePath, PATHINFO_BASENAME); - $thumbnailPath = $this->publicDir . self::THUMBNAILS_DIR . '/' . $filename; + $thumbnailPath = $this->publicDir.self::THUMBNAILS_DIR.'/'.$filename; $thumbnail = $this->imageManager->read($fullPath); $thumbnail->cover(self::THUMBNAIL_WIDTH, self::THUMBNAIL_HEIGHT); $thumbnail->save($thumbnailPath, quality: self::THUMBNAIL_QUALITY); - return self::THUMBNAILS_DIR . '/' . $filename; + return self::THUMBNAILS_DIR.'/'.$filename; } catch (\Exception $e) { - throw new \RuntimeException('Erreur lors de la création de la miniature : ' . $e->getMessage()); + throw new \RuntimeException('Erreur lors de la création de la miniature : '.$e->getMessage()); } } private function ensureDirectoriesExist(): void { $directories = [ - $this->publicDir . self::FULL_IMAGES_DIR, - $this->publicDir . self::THUMBNAILS_DIR, + $this->publicDir.self::FULL_IMAGES_DIR, + $this->publicDir.self::THUMBNAILS_DIR, ]; foreach ($directories as $directory) { diff --git a/src/Domain/Manga/Infrastructure/Service/MangadxChapterSynchronizationService.php b/src/Domain/Manga/Infrastructure/Service/MangadxChapterSynchronizationService.php index 05641e2..a935770 100644 --- a/src/Domain/Manga/Infrastructure/Service/MangadxChapterSynchronizationService.php +++ b/src/Domain/Manga/Infrastructure/Service/MangadxChapterSynchronizationService.php @@ -14,13 +14,13 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz { public function __construct( private MangadexClientInterface $mangadxClient, - private MangaRepositoryInterface $mangaRepository + private MangaRepositoryInterface $mangaRepository, ) { } public function synchronizeChapters(Manga $manga): array { - if ($manga->getExternalId() === null) { + if (null === $manga->getExternalId()) { throw new \RuntimeException('Manga has no external ID'); } @@ -57,10 +57,10 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz // Si c'est le premier chapitre avec ce numéro qu'on rencontre $shouldReplaceChapter = true; $chapterNumbers[] = $chapterNumber; - } elseif ($language === 'fr') { + } elseif ('fr' === $language) { // Le français est toujours prioritaire $shouldReplaceChapter = true; - } elseif ($language === 'en' && $chapterLanguages[(string) $chapterNumber] !== 'fr') { + } elseif ('en' === $language && 'fr' !== $chapterLanguages[(string) $chapterNumber]) { // L'anglais est prioritaire sur les autres langues, sauf le français $shouldReplaceChapter = true; } @@ -116,13 +116,13 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz private function harmonizeVolumes(array &$chaptersByNumber): void { // Trie les chapitres par numéro pour faciliter la recherche des adjacents - uksort($chaptersByNumber, fn ($a, $b) => (float)$a <=> (float)$b); + uksort($chaptersByNumber, fn ($a, $b) => (float) $a <=> (float) $b); $chapterNumbers = array_keys($chaptersByNumber); $count = count($chapterNumbers); // Première passe : harmonisation locale (chapitres adjacents) - for ($i = 1; $i < $count - 1; $i++) { + for ($i = 1; $i < $count - 1; ++$i) { $prevChapterNum = $chapterNumbers[$i - 1]; $currentChapterNum = $chapterNumbers[$i]; $nextChapterNum = $chapterNumbers[$i + 1]; @@ -136,7 +136,7 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz $nextVolume = $nextChapter->getVolume(); // Règle 1: Si précédent et suivant sont null, alors actuel aussi - if ($prevVolume === null && $nextVolume === null && $currentVolume !== null) { + if (null === $prevVolume && null === $nextVolume && null !== $currentVolume) { $chaptersByNumber[$currentChapterNum] = new Chapter( new ChapterId($currentChapter->getId()), $currentChapter->getMangaId(), @@ -150,7 +150,7 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz ); } // Règle 2: Si précédent et suivant ont le même volume, alors actuel aussi - elseif ($prevVolume !== null && $prevVolume === $nextVolume && $currentVolume !== $prevVolume) { + elseif (null !== $prevVolume && $prevVolume === $nextVolume && $currentVolume !== $prevVolume) { $chaptersByNumber[$currentChapterNum] = new Chapter( new ChapterId($currentChapter->getId()), $currentChapter->getMangaId(), @@ -170,25 +170,25 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz } /** - * Remplit les "trous" de volumes manquants dans une séquence + * Remplit les "trous" de volumes manquants dans une séquence. */ private function fillVolumeGaps(array &$chaptersByNumber, array $chapterNumbers): void { $count = count($chapterNumbers); - for ($i = 0; $i < $count; $i++) { + for ($i = 0; $i < $count; ++$i) { $currentChapterNum = $chapterNumbers[$i]; $currentChapter = $chaptersByNumber[$currentChapterNum]; - if ($currentChapter->getVolume() !== null) { + if (null !== $currentChapter->getVolume()) { continue; // Ce chapitre a déjà un volume } // Cherche le volume précédent non-null $prevVolume = null; - for ($j = $i - 1; $j >= 0; $j--) { + for ($j = $i - 1; $j >= 0; --$j) { $prevChapter = $chaptersByNumber[$chapterNumbers[$j]]; - if ($prevChapter->getVolume() !== null) { + if (null !== $prevChapter->getVolume()) { $prevVolume = $prevChapter->getVolume(); break; } @@ -196,9 +196,9 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz // Cherche le volume suivant non-null $nextVolume = null; - for ($k = $i + 1; $k < $count; $k++) { + for ($k = $i + 1; $k < $count; ++$k) { $nextChapter = $chaptersByNumber[$chapterNumbers[$k]]; - if ($nextChapter->getVolume() !== null) { + if (null !== $nextChapter->getVolume()) { $nextVolume = $nextChapter->getVolume(); break; } @@ -206,7 +206,7 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz // Priorité au volume précédent : le chapitre appartient à la fin du volume en cours // Couvre les cas : milieu de volume (prev=next), transition entre deux volumes (prev≠next) - if ($prevVolume !== null) { + if (null !== $prevVolume) { $chaptersByNumber[$currentChapterNum] = new Chapter( new ChapterId($currentChapter->getId()), $currentChapter->getMangaId(), @@ -220,7 +220,7 @@ readonly class MangadxChapterSynchronizationService implements ChapterSynchroniz ); } // Sinon utilise le volume suivant (chapitres en début de série) - elseif ($nextVolume !== null) { + elseif (null !== $nextVolume) { $chaptersByNumber[$currentChapterNum] = new Chapter( new ChapterId($currentChapter->getId()), $currentChapter->getMangaId(), diff --git a/src/Domain/Reader/Application/Query/GetChapterContext.php b/src/Domain/Reader/Application/Query/GetChapterContext.php index 4c8e673..dfb37c7 100644 --- a/src/Domain/Reader/Application/Query/GetChapterContext.php +++ b/src/Domain/Reader/Application/Query/GetChapterContext.php @@ -7,9 +7,10 @@ namespace App\Domain\Reader\Application\Query; final class GetChapterContext { public function __construct( - private readonly string $chapterId + private readonly string $chapterId, ) { } + public function getChapterId(): string { return $this->chapterId; diff --git a/src/Domain/Reader/Application/Query/GetChapterPages.php b/src/Domain/Reader/Application/Query/GetChapterPages.php index be5f719..52a7087 100644 --- a/src/Domain/Reader/Application/Query/GetChapterPages.php +++ b/src/Domain/Reader/Application/Query/GetChapterPages.php @@ -8,8 +8,8 @@ final readonly class GetChapterPages { public function __construct( private string $chapterId, - private int $page = 1, - private int $itemsPerPage = 20 + private int $page = 1, + private int $itemsPerPage = 20, ) { } diff --git a/src/Domain/Reader/Application/QueryHandler/GetChapterContextHandler.php b/src/Domain/Reader/Application/QueryHandler/GetChapterContextHandler.php index 120b307..9f649ce 100644 --- a/src/Domain/Reader/Application/QueryHandler/GetChapterContextHandler.php +++ b/src/Domain/Reader/Application/QueryHandler/GetChapterContextHandler.php @@ -12,7 +12,7 @@ use App\Domain\Reader\Domain\ValueObject\ChapterId; final readonly class GetChapterContextHandler { public function __construct( - private ChapterRepositoryInterface $chapterRepository + private ChapterRepositoryInterface $chapterRepository, ) { } diff --git a/src/Domain/Reader/Application/QueryHandler/GetChapterPagesHandler.php b/src/Domain/Reader/Application/QueryHandler/GetChapterPagesHandler.php index a4e7623..72cef35 100644 --- a/src/Domain/Reader/Application/QueryHandler/GetChapterPagesHandler.php +++ b/src/Domain/Reader/Application/QueryHandler/GetChapterPagesHandler.php @@ -13,7 +13,7 @@ use App\Domain\Reader\Domain\ValueObject\ChapterId; final readonly class GetChapterPagesHandler { public function __construct( - private ChapterRepositoryInterface $chapterRepository + private ChapterRepositoryInterface $chapterRepository, ) { } @@ -23,7 +23,7 @@ final readonly class GetChapterPagesHandler $totalItems = $this->chapterRepository->getTotalPagesForChapter($chapterId); - if ($totalItems === 0) { + if (0 === $totalItems) { throw ChapterNotFoundException::forChapter($chapterId); } diff --git a/src/Domain/Reader/Application/Response/ChapterContextResponse.php b/src/Domain/Reader/Application/Response/ChapterContextResponse.php index a0678b1..c250cee 100644 --- a/src/Domain/Reader/Application/Response/ChapterContextResponse.php +++ b/src/Domain/Reader/Application/Response/ChapterContextResponse.php @@ -7,13 +7,13 @@ namespace App\Domain\Reader\Application\Response; final readonly class ChapterContextResponse { public function __construct( - private string $id, - private string $mangaId, - private string $title, - private float $number, - private int $totalPages, + private string $id, + private string $mangaId, + private string $title, + private float $number, + private int $totalPages, private ?string $previousChapterId, - private ?string $nextChapterId + private ?string $nextChapterId, ) { } diff --git a/src/Domain/Reader/Domain/Model/Page.php b/src/Domain/Reader/Domain/Model/Page.php index 34dea5b..5a17ec9 100644 --- a/src/Domain/Reader/Domain/Model/Page.php +++ b/src/Domain/Reader/Domain/Model/Page.php @@ -13,7 +13,7 @@ class Page private readonly PageNumber $pageNumber, private readonly string $url, private readonly int $width, - private readonly int $height + private readonly int $height, ) { } diff --git a/src/Domain/Reader/Domain/ValueObject/ChapterId.php b/src/Domain/Reader/Domain/ValueObject/ChapterId.php index f018ca7..92f0a47 100644 --- a/src/Domain/Reader/Domain/ValueObject/ChapterId.php +++ b/src/Domain/Reader/Domain/ValueObject/ChapterId.php @@ -7,7 +7,7 @@ namespace App\Domain\Reader\Domain\ValueObject; readonly class ChapterId { public function __construct( - private string $value + private string $value, ) { } diff --git a/src/Domain/Reader/Domain/ValueObject/PageNumber.php b/src/Domain/Reader/Domain/ValueObject/PageNumber.php index a957f1e..423badf 100644 --- a/src/Domain/Reader/Domain/ValueObject/PageNumber.php +++ b/src/Domain/Reader/Domain/ValueObject/PageNumber.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace App\Domain\Reader\Domain\ValueObject; -use InvalidArgumentException; - final class PageNumber { private int $value; @@ -13,7 +11,7 @@ final class PageNumber public function __construct(int $value) { if ($value < 1) { - throw new InvalidArgumentException('Le numéro de page doit être supérieur à 0'); + throw new \InvalidArgumentException('Le numéro de page doit être supérieur à 0'); } $this->value = $value; diff --git a/src/Domain/Reader/Infrastructure/ApiPlatform/Resource/ChapterContextResource.php b/src/Domain/Reader/Infrastructure/ApiPlatform/Resource/ChapterContextResource.php index b5c8d1e..4928c35 100644 --- a/src/Domain/Reader/Infrastructure/ApiPlatform/Resource/ChapterContextResource.php +++ b/src/Domain/Reader/Infrastructure/ApiPlatform/Resource/ChapterContextResource.php @@ -6,26 +6,27 @@ namespace App\Domain\Reader\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Reader\Infrastructure\ApiPlatform\State\Provider\ChapterContextProvider; -use Symfony\Component\Serializer\Annotation\Groups; #[ApiResource( shortName: 'Reader', operations: [ new Get( uriTemplate: '/reader/chapter/{chapterId}', - openapiContext: [ - 'summary' => 'Récupère le contexte d\'un chapitre', - 'description' => 'Retourne les métadonnées du chapitre et sa navigation', - 'parameters' => [ - [ - 'name' => 'chapterId', - 'in' => 'path', - 'required' => true, - 'schema' => ['type' => 'string'], - ], + openapi: new Operation( + summary: 'Récupère le contexte d\'un chapitre', + description: 'Retourne les métadonnées du chapitre et sa navigation', + parameters: [ + new Parameter( + name: 'chapterId', + in: 'path', + required: true, + schema: ['type' => 'string'] + ), ], - 'responses' => [ + responses: [ '200' => [ 'description' => 'Contexte du chapitre', 'content' => [ @@ -40,8 +41,8 @@ use Symfony\Component\Serializer\Annotation\Groups; 'type' => 'object', 'properties' => [ 'id' => ['type' => 'string'], - 'title' => ['type' => 'string'] - ] + 'title' => ['type' => 'string'], + ], ], 'navigation' => [ 'type' => 'object', @@ -51,29 +52,29 @@ use Symfony\Component\Serializer\Annotation\Groups; 'nullable' => true, 'properties' => [ 'id' => ['type' => 'string'], - 'number' => ['type' => 'string'] - ] + 'number' => ['type' => 'string'], + ], ], 'next' => [ 'type' => 'object', 'nullable' => true, 'properties' => [ 'id' => ['type' => 'string'], - 'number' => ['type' => 'string'] - ] - ] - ] - ] - ] - ] - ] - ] + 'number' => ['type' => 'string'], + ], + ], + ], + ], + ], + ], + ], + ], ], '404' => [ - 'description' => 'Chapitre non trouvé' - ] + 'description' => 'Chapitre non trouvé', + ], ] - ], + ), provider: ChapterContextProvider::class ), ], diff --git a/src/Domain/Reader/Infrastructure/ApiPlatform/Resource/ChapterPagesResource.php b/src/Domain/Reader/Infrastructure/ApiPlatform/Resource/ChapterPagesResource.php index b048675..5463523 100644 --- a/src/Domain/Reader/Infrastructure/ApiPlatform/Resource/ChapterPagesResource.php +++ b/src/Domain/Reader/Infrastructure/ApiPlatform/Resource/ChapterPagesResource.php @@ -6,38 +6,39 @@ namespace App\Domain\Reader\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Reader\Infrastructure\ApiPlatform\State\Provider\ChapterPagesProvider; -use Symfony\Component\Serializer\Annotation\Groups; #[ApiResource( shortName: 'Reader', operations: [ new Get( uriTemplate: '/reader/chapter/{chapterId}/pages', - openapiContext: [ - 'summary' => 'Récupère les pages d\'un chapitre', - 'description' => 'Retourne une collection paginée des pages du chapitre', - 'parameters' => [ - [ - 'name' => 'chapterId', - 'in' => 'path', - 'required' => true, - 'schema' => ['type' => 'string'], - ], - [ - 'name' => 'page', - 'in' => 'query', - 'required' => false, - 'schema' => ['type' => 'integer', 'default' => 1], - ], - [ - 'name' => 'itemsPerPage', - 'in' => 'query', - 'required' => false, - 'schema' => ['type' => 'integer', 'default' => 20], - ], + openapi: new Operation( + summary: 'Récupère les pages d\'un chapitre', + description: 'Retourne une collection paginée des pages du chapitre', + parameters: [ + new Parameter( + name: 'chapterId', + in: 'path', + required: true, + schema: ['type' => 'string'] + ), + new Parameter( + name: 'page', + in: 'query', + required: false, + schema: ['type' => 'integer', 'default' => 1] + ), + new Parameter( + name: 'itemsPerPage', + in: 'query', + required: false, + schema: ['type' => 'integer', 'default' => 20] + ), ], - 'responses' => [ + responses: [ '200' => [ 'description' => 'Collection paginée des pages du chapitre', 'content' => [ @@ -55,26 +56,26 @@ use Symfony\Component\Serializer\Annotation\Groups; 'type' => 'object', 'properties' => [ 'width' => ['type' => 'integer'], - 'height' => ['type' => 'integer'] - ] - ] - ] - ] + 'height' => ['type' => 'integer'], + ], + ], + ], + ], ], 'totalItems' => ['type' => 'integer'], 'currentPage' => ['type' => 'integer'], 'itemsPerPage' => ['type' => 'integer'], - 'totalPages' => ['type' => 'integer'] - ] - ] - ] - ] + 'totalPages' => ['type' => 'integer'], + ], + ], + ], + ], ], '404' => [ - 'description' => 'Chapitre non trouvé' - ] + 'description' => 'Chapitre non trouvé', + ], ] - ], + ), provider: ChapterPagesProvider::class ), ], diff --git a/src/Domain/Reader/Infrastructure/ApiPlatform/State/Provider/ChapterContextProvider.php b/src/Domain/Reader/Infrastructure/ApiPlatform/State/Provider/ChapterContextProvider.php index 731e73f..464851e 100644 --- a/src/Domain/Reader/Infrastructure/ApiPlatform/State/Provider/ChapterContextProvider.php +++ b/src/Domain/Reader/Infrastructure/ApiPlatform/State/Provider/ChapterContextProvider.php @@ -13,7 +13,7 @@ use App\Domain\Reader\Application\Response\ChapterContextResponse; final readonly class ChapterContextProvider implements ProviderInterface { public function __construct( - private GetChapterContextHandler $handler + private GetChapterContextHandler $handler, ) { } diff --git a/src/Domain/Reader/Infrastructure/ApiPlatform/State/Provider/ChapterPagesProvider.php b/src/Domain/Reader/Infrastructure/ApiPlatform/State/Provider/ChapterPagesProvider.php index 85b5526..b7a8982 100644 --- a/src/Domain/Reader/Infrastructure/ApiPlatform/State/Provider/ChapterPagesProvider.php +++ b/src/Domain/Reader/Infrastructure/ApiPlatform/State/Provider/ChapterPagesProvider.php @@ -13,7 +13,7 @@ use App\Domain\Reader\Application\Response\ChapterPagesResponse; final readonly class ChapterPagesProvider implements ProviderInterface { public function __construct( - private GetChapterPagesHandler $handler + private GetChapterPagesHandler $handler, ) { } diff --git a/src/Domain/Reader/Infrastructure/Persistence/LegacyChapterRepository.php b/src/Domain/Reader/Infrastructure/Persistence/LegacyChapterRepository.php index 2d43528..0d58c18 100644 --- a/src/Domain/Reader/Infrastructure/Persistence/LegacyChapterRepository.php +++ b/src/Domain/Reader/Infrastructure/Persistence/LegacyChapterRepository.php @@ -16,14 +16,14 @@ use Doctrine\ORM\EntityManagerInterface; readonly class LegacyChapterRepository implements ChapterRepositoryInterface { public function __construct( - private EntityManagerInterface $entityManager + private EntityManagerInterface $entityManager, ) { } public function getPagesForChapter(ChapterId $chapterId, int $page = 1, int $itemsPerPage = 20): array { $chapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([ - 'id' => $chapterId->getValue() + 'id' => $chapterId->getValue(), ]); $pagesDirectory = $chapter->getPagesDirectory(); @@ -38,7 +38,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface { /** @var ChapterEntity $chapter */ $chapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([ - 'id' => $chapterId->getValue() + 'id' => $chapterId->getValue(), ]); if (!$chapter) { @@ -65,7 +65,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface public function getTotalPagesForChapter(ChapterId $chapterId): int { $chapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([ - 'id' => $chapterId->getValue() + 'id' => $chapterId->getValue(), ]); if (!$chapter) { @@ -83,7 +83,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface public function getPreviousChapterId(ChapterId $chapterId): ?ChapterId { $currentChapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([ - 'id' => $chapterId->getValue() + 'id' => $chapterId->getValue(), ]); $qb = $this->entityManager->createQueryBuilder(); @@ -95,10 +95,8 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface ->andWhere('c.pagesDirectory IS NOT NULL OR c.cbzPath IS NOT NULL') ->orderBy('c.number', 'DESC') ->setMaxResults(1) - ->setParameters([ - 'manga' => $currentChapter->getManga(), - 'number' => $currentChapter->getNumber() - ]); + ->setParameter('manga', $currentChapter->getManga()) + ->setParameter('number', $currentChapter->getNumber()); $previousChapter = $qb->getQuery()->getOneOrNullResult(); @@ -108,7 +106,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface public function getNextChapterId(ChapterId $chapterId): ?ChapterId { $currentChapter = $this->entityManager->getRepository(ChapterEntity::class)->findOneBy([ - 'id' => $chapterId->getValue() + 'id' => $chapterId->getValue(), ]); $qb = $this->entityManager->createQueryBuilder(); @@ -120,10 +118,8 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface ->andWhere('c.pagesDirectory IS NOT NULL OR c.cbzPath IS NOT NULL') ->orderBy('c.number', 'ASC') ->setMaxResults(1) - ->setParameters([ - 'manga' => $currentChapter->getManga(), - 'number' => $currentChapter->getNumber() - ]); + ->setParameter('manga', $currentChapter->getManga()) + ->setParameter('number', $currentChapter->getNumber()); $nextChapter = $qb->getQuery()->getOneOrNullResult(); @@ -132,7 +128,7 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface private function getImageFiles(string $pagesDirectory): array { - $files = glob($pagesDirectory . '/*.{jpg,jpeg,png,webp,gif}', GLOB_BRACE) ?: []; + $files = glob($pagesDirectory.'/*.{jpg,jpeg,png,webp,gif}', GLOB_BRACE) ?: []; sort($files); return $files; @@ -145,9 +141,9 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface $end = min($start + $itemsPerPage, count($files)); $pages = []; - for ($i = $start; $i < $end; $i++) { + for ($i = $start; $i < $end; ++$i) { $imageSize = @getimagesize($files[$i]); - if ($imageSize === false) { + if (false === $imageSize) { continue; } diff --git a/src/Domain/Scraping/Application/Command/ScrapeChapter.php b/src/Domain/Scraping/Application/Command/ScrapeChapter.php index ccff324..e083804 100644 --- a/src/Domain/Scraping/Application/Command/ScrapeChapter.php +++ b/src/Domain/Scraping/Application/Command/ScrapeChapter.php @@ -6,7 +6,7 @@ readonly class ScrapeChapter { public function __construct( public string $chapterId, - public string $jobId + public string $jobId, ) { } } diff --git a/src/Domain/Scraping/Application/Command/SetMangaPreferredSources.php b/src/Domain/Scraping/Application/Command/SetMangaPreferredSources.php index 3e4d670..93fd402 100644 --- a/src/Domain/Scraping/Application/Command/SetMangaPreferredSources.php +++ b/src/Domain/Scraping/Application/Command/SetMangaPreferredSources.php @@ -6,7 +6,7 @@ readonly class SetMangaPreferredSources { public function __construct( public string $mangaId, - public array $sourceIds + public array $sourceIds, ) { if (empty($mangaId)) { throw new \InvalidArgumentException('Manga ID cannot be empty'); diff --git a/src/Domain/Scraping/Application/CommandHandler/CheckAllScrapersHealthHandler.php b/src/Domain/Scraping/Application/CommandHandler/CheckAllScrapersHealthHandler.php index c52b955..0e63c14 100644 --- a/src/Domain/Scraping/Application/CommandHandler/CheckAllScrapersHealthHandler.php +++ b/src/Domain/Scraping/Application/CommandHandler/CheckAllScrapersHealthHandler.php @@ -23,7 +23,7 @@ readonly class CheckAllScrapersHealthHandler $sources = $this->contentSourceForHealthCheckRepo->getAll(); foreach ($sources as $source) { - if ($source->testSlug === null || $source->testChapterNumber === null) { + if (null === $source->testSlug || null === $source->testChapterNumber) { $this->logger->warning('ContentSource {id} has no test config, skipping health check.', ['id' => $source->id]); continue; } diff --git a/src/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandler.php b/src/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandler.php index ca35b5d..afd66c9 100644 --- a/src/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandler.php +++ b/src/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandler.php @@ -6,17 +6,17 @@ use App\Domain\Scraping\Application\Command\ScrapeChapter; use App\Domain\Scraping\Domain\Contract\Repository\ChapterRepositoryInterface; use App\Domain\Scraping\Domain\Contract\Repository\MangaRepositoryInterface; use App\Domain\Scraping\Domain\Contract\Repository\SourceRepositoryInterface; -use App\Domain\Shared\Domain\Contract\ImageStorageInterface; use App\Domain\Scraping\Domain\Contract\Service\ImageDownloaderInterface; use App\Domain\Scraping\Domain\Contract\Service\ScraperFactoryInterface; -use App\Domain\Shared\Domain\Event\ChapterScraped; use App\Domain\Scraping\Domain\Event\ChapterScrapingFailed; use App\Domain\Scraping\Domain\Event\ChapterScrapingStarted; use App\Domain\Scraping\Domain\Model\Chapter; use App\Domain\Scraping\Domain\Model\Source; use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingRequest; use App\Domain\Scraping\Domain\Model\ValueObject\TempDirectory; +use App\Domain\Shared\Domain\Contract\ImageStorageInterface; use App\Domain\Shared\Domain\Contract\JobRepositoryInterface; +use App\Domain\Shared\Domain\Event\ChapterScraped; use Symfony\Component\Messenger\MessageBusInterface; readonly class ScrapeChapterHandler @@ -91,7 +91,6 @@ readonly class ScrapeChapterHandler $success = true; break; - } catch (\Exception $e) { $lastException = $e; } @@ -111,7 +110,6 @@ readonly class ScrapeChapterHandler } /** - * @param \App\Domain\Scraping\Domain\Model\Manga $manga * @return Source[] */ private function getSourcesToTry(\App\Domain\Scraping\Domain\Model\Manga $manga): array diff --git a/src/Domain/Scraping/Application/CommandHandler/SetMangaPreferredSourcesHandler.php b/src/Domain/Scraping/Application/CommandHandler/SetMangaPreferredSourcesHandler.php index bb40912..de239c3 100644 --- a/src/Domain/Scraping/Application/CommandHandler/SetMangaPreferredSourcesHandler.php +++ b/src/Domain/Scraping/Application/CommandHandler/SetMangaPreferredSourcesHandler.php @@ -10,7 +10,7 @@ readonly class SetMangaPreferredSourcesHandler { public function __construct( private MangaRepositoryInterface $mangaRepository, - private SourceRepositoryInterface $sourceRepository + private SourceRepositoryInterface $sourceRepository, ) { } @@ -25,6 +25,7 @@ readonly class SetMangaPreferredSourcesHandler // 2. Si pas de sources spécifiées, supprimer les sources préférées if (empty($command->sourceIds)) { $this->mangaRepository->updatePreferredSources($command->mangaId, []); + return; } diff --git a/src/Domain/Scraping/Application/CommandHandler/TestScraperConfigurationHandler.php b/src/Domain/Scraping/Application/CommandHandler/TestScraperConfigurationHandler.php index cda15b7..3de6699 100644 --- a/src/Domain/Scraping/Application/CommandHandler/TestScraperConfigurationHandler.php +++ b/src/Domain/Scraping/Application/CommandHandler/TestScraperConfigurationHandler.php @@ -4,14 +4,13 @@ namespace App\Domain\Scraping\Application\CommandHandler; use App\Domain\Scraping\Application\Command\TestScraperConfiguration; use App\Domain\Scraping\Application\Response\TestScraperConfigurationResponse; -use App\Domain\Scraping\Domain\Contract\Service\ScraperInterface; use App\Domain\Scraping\Domain\Contract\Service\ScraperFactoryInterface; use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingRequest; readonly class TestScraperConfigurationHandler { public function __construct( - private ScraperFactoryInterface $scraperFactory + private ScraperFactoryInterface $scraperFactory, ) { } @@ -24,7 +23,7 @@ readonly class TestScraperConfigurationHandler 'chapterUrlFormat' => $command->chapterUrlFormat, 'scrapingType' => $command->scrapingType, 'chapterSelector' => $command->chapterSelector, - 'chapterNumber' => $command->chapterNumber + 'chapterNumber' => $command->chapterNumber, ]; // Vérification que le scraper supporte le type de scraping if (!$this->scraperFactory->isSupported($command->scrapingType)) { @@ -42,13 +41,11 @@ readonly class TestScraperConfigurationHandler $scrapingResult = $scraper->scrape($scrapingRequest); - return TestScraperConfigurationResponse::success( $scrapingResult->getImageUrls(), $command->testUrl, $command->scrapingType ); - } catch (\Exception $e) { return TestScraperConfigurationResponse::failure( $command->testUrl, @@ -61,7 +58,7 @@ readonly class TestScraperConfigurationHandler private function tryWithFallbackScrapers( TestScraperConfiguration $command, array $scrapingParameters, - ?\Exception $originalException = null + ?\Exception $originalException = null, ): TestScraperConfigurationResponse { $errors = []; @@ -69,7 +66,7 @@ readonly class TestScraperConfigurationHandler $errors[] = [ 'type' => 'primary_scraper_failed', 'scraper' => $command->scrapingType, - 'message' => $originalException->getMessage() + 'message' => $originalException->getMessage(), ]; } @@ -99,13 +96,12 @@ readonly class TestScraperConfigurationHandler $scraperType, // Retourner le type de scraper qui a fonctionné "Scraper alternatif utilisé: {$scraperType} (au lieu de {$command->scrapingType})" ); - } catch (\Exception $e) { $triedScrapers[] = $scraperType; $errors[] = [ 'type' => 'fallback_scraper_failed', 'scraper' => $scraperType, - 'message' => $e->getMessage() + 'message' => $e->getMessage(), ]; } } @@ -114,7 +110,7 @@ readonly class TestScraperConfigurationHandler $errors[] = [ 'type' => 'all_scrapers_failed', 'message' => 'Aucun scraper disponible n\'a réussi à traiter cette URL', - 'tried_scrapers' => array_merge([$command->scrapingType], $triedScrapers) + 'tried_scrapers' => array_merge([$command->scrapingType], $triedScrapers), ]; return TestScraperConfigurationResponse::failure( @@ -135,7 +131,7 @@ readonly class TestScraperConfigurationHandler 'type' => 'selector_error', 'field' => 'imageSelector', 'message' => "Le sélecteur d'image '{$command->imageSelector}' ne trouve aucun élément sur la page", - 'suggestion' => "Vérifiez que le sélecteur CSS est correct et qu'il correspond aux éléments d'image sur la page" + 'suggestion' => "Vérifiez que le sélecteur CSS est correct et qu'il correspond aux éléments d'image sur la page", ]; } @@ -145,7 +141,7 @@ readonly class TestScraperConfigurationHandler 'type' => 'url_error', 'field' => 'testUrl', 'message' => "Impossible d'accéder à l'URL de test: {$command->testUrl}", - 'suggestion' => "Vérifiez que l'URL est correcte et accessible, et que le chapitre existe" + 'suggestion' => "Vérifiez que l'URL est correcte et accessible, et que le chapitre existe", ]; } @@ -155,7 +151,7 @@ readonly class TestScraperConfigurationHandler 'type' => 'selector_error', 'field' => 'nextPageSelector', 'message' => "Le sélecteur de page suivante '{$command->nextPageSelector}' pose problème", - 'suggestion' => "Vérifiez le sélecteur CSS pour la navigation entre les pages" + 'suggestion' => 'Vérifiez le sélecteur CSS pour la navigation entre les pages', ]; } @@ -165,7 +161,7 @@ readonly class TestScraperConfigurationHandler 'type' => 'general_error', 'field' => 'configuration', 'message' => $message, - 'suggestion' => "Vérifiez l'ensemble de la configuration et l'accessibilité du site web" + 'suggestion' => "Vérifiez l'ensemble de la configuration et l'accessibilité du site web", ]; } diff --git a/src/Domain/Scraping/Application/Query/GetMangaPreferredSources.php b/src/Domain/Scraping/Application/Query/GetMangaPreferredSources.php index b84f488..3200c1f 100644 --- a/src/Domain/Scraping/Application/Query/GetMangaPreferredSources.php +++ b/src/Domain/Scraping/Application/Query/GetMangaPreferredSources.php @@ -5,7 +5,7 @@ namespace App\Domain\Scraping\Application\Query; readonly class GetMangaPreferredSources { public function __construct( - public string $mangaId + public string $mangaId, ) { if (empty($mangaId)) { throw new \InvalidArgumentException('Manga ID cannot be empty'); diff --git a/src/Domain/Scraping/Application/QueryHandler/GetMangaPreferredSourcesHandler.php b/src/Domain/Scraping/Application/QueryHandler/GetMangaPreferredSourcesHandler.php index 7a50f0e..446d4c9 100644 --- a/src/Domain/Scraping/Application/QueryHandler/GetMangaPreferredSourcesHandler.php +++ b/src/Domain/Scraping/Application/QueryHandler/GetMangaPreferredSourcesHandler.php @@ -11,7 +11,7 @@ readonly class GetMangaPreferredSourcesHandler { public function __construct( private MangaRepositoryInterface $mangaRepository, - private SourceRepositoryInterface $sourceRepository + private SourceRepositoryInterface $sourceRepository, ) { } diff --git a/src/Domain/Scraping/Application/Response/GetMangaPreferredSourcesResponse.php b/src/Domain/Scraping/Application/Response/GetMangaPreferredSourcesResponse.php index 8c153aa..04a95a4 100644 --- a/src/Domain/Scraping/Application/Response/GetMangaPreferredSourcesResponse.php +++ b/src/Domain/Scraping/Application/Response/GetMangaPreferredSourcesResponse.php @@ -5,14 +5,13 @@ namespace App\Domain\Scraping\Application\Response; readonly class GetMangaPreferredSourcesResponse { /** - * @param string $mangaId - * @param array $sources Array of Source objects - * @param bool $hasPreferredSources Whether sources are preferred or all available sources + * @param array $sources Array of Source objects + * @param bool $hasPreferredSources Whether sources are preferred or all available sources */ public function __construct( public string $mangaId, public array $sources, - public bool $hasPreferredSources + public bool $hasPreferredSources, ) { } } diff --git a/src/Domain/Scraping/Domain/Contract/Repository/ChapterRepositoryInterface.php b/src/Domain/Scraping/Domain/Contract/Repository/ChapterRepositoryInterface.php index 6a75e92..380d0e7 100644 --- a/src/Domain/Scraping/Domain/Contract/Repository/ChapterRepositoryInterface.php +++ b/src/Domain/Scraping/Domain/Contract/Repository/ChapterRepositoryInterface.php @@ -7,6 +7,7 @@ use App\Domain\Scraping\Domain\Model\Chapter; interface ChapterRepositoryInterface { public function getById(string $id): ?Chapter; + /** * @throws ChapterNotFoundException */ diff --git a/src/Domain/Scraping/Domain/Contract/Repository/MangaRepositoryInterface.php b/src/Domain/Scraping/Domain/Contract/Repository/MangaRepositoryInterface.php index 63baf70..dd09796 100644 --- a/src/Domain/Scraping/Domain/Contract/Repository/MangaRepositoryInterface.php +++ b/src/Domain/Scraping/Domain/Contract/Repository/MangaRepositoryInterface.php @@ -9,11 +9,9 @@ interface MangaRepositoryInterface public function getById(string $id): ?Manga; /** - * Update the preferred sources for a manga in the specified order + * Update the preferred sources for a manga in the specified order. * - * @param string $mangaId * @param array $sourceIds Array of source IDs in preferred order - * @return void */ public function updatePreferredSources(string $mangaId, array $sourceIds): void; } diff --git a/src/Domain/Scraping/Domain/Contract/Repository/SourceRepositoryInterface.php b/src/Domain/Scraping/Domain/Contract/Repository/SourceRepositoryInterface.php index e300a52..a8b2e01 100644 --- a/src/Domain/Scraping/Domain/Contract/Repository/SourceRepositoryInterface.php +++ b/src/Domain/Scraping/Domain/Contract/Repository/SourceRepositoryInterface.php @@ -14,23 +14,19 @@ interface SourceRepositoryInterface public function getAll(): array; /** - * Validate that all source IDs exist and are active - * - * @param array $sourceIds - * @return bool + * Validate that all source IDs exist and are active. */ public function validateSourcesExist(array $sourceIds): bool; /** - * Get sources by their IDs in the same order as provided + * Get sources by their IDs in the same order as provided. * - * @param array $sourceIds * @return Source[] */ public function getByIds(array $sourceIds): array; /** - * Get all active sources + * Get all active sources. * * @return Source[] */ diff --git a/src/Domain/Scraping/Domain/Contract/Service/ImageDownloaderInterface.php b/src/Domain/Scraping/Domain/Contract/Service/ImageDownloaderInterface.php index 98fa8ae..baf8eb7 100644 --- a/src/Domain/Scraping/Domain/Contract/Service/ImageDownloaderInterface.php +++ b/src/Domain/Scraping/Domain/Contract/Service/ImageDownloaderInterface.php @@ -11,6 +11,7 @@ interface ImageDownloaderInterface /** * @param array $urls + * * @return array */ public function downloadBatch(array $urls, TempDirectory $tempDir, string $jobId): array; diff --git a/src/Domain/Scraping/Domain/Contract/Service/ScraperFactoryInterface.php b/src/Domain/Scraping/Domain/Contract/Service/ScraperFactoryInterface.php index 69ff6b2..1350d08 100644 --- a/src/Domain/Scraping/Domain/Contract/Service/ScraperFactoryInterface.php +++ b/src/Domain/Scraping/Domain/Contract/Service/ScraperFactoryInterface.php @@ -5,32 +5,32 @@ namespace App\Domain\Scraping\Domain\Contract\Service; interface ScraperFactoryInterface { /** - * Créer un scraper pour un type spécifique + * Créer un scraper pour un type spécifique. */ public function createScraper(string $type): ScraperInterface; /** - * Obtenir le scraper le plus approprié selon la priorité + * Obtenir le scraper le plus approprié selon la priorité. */ public function getBestScraper(): ScraperInterface; /** - * Obtenir le scraper de fallback (le plus simple) + * Obtenir le scraper de fallback (le plus simple). */ public function getFallbackScraper(): ScraperInterface; /** - * Essayer plusieurs scrapers en cascade jusqu'à ce qu'un fonctionne + * Essayer plusieurs scrapers en cascade jusqu'à ce qu'un fonctionne. */ public function getScraperWithFallback(string $preferredType): ScraperInterface; /** - * Obtenir les types de scrapers supportés + * Obtenir les types de scrapers supportés. */ public function getSupportedTypes(): array; /** - * Vérifier si un type de scraper est supporté + * Vérifier si un type de scraper est supporté. */ public function isSupported(string $type): bool; } diff --git a/src/Domain/Scraping/Domain/Contract/Service/ScraperInterface.php b/src/Domain/Scraping/Domain/Contract/Service/ScraperInterface.php index 7f58522..2a7c6d1 100644 --- a/src/Domain/Scraping/Domain/Contract/Service/ScraperInterface.php +++ b/src/Domain/Scraping/Domain/Contract/Service/ScraperInterface.php @@ -8,5 +8,6 @@ use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingResult; interface ScraperInterface { public function scrape(ScrapingRequest $request): ScrapingResult; + public function supports(string $sourceType): bool; } diff --git a/src/Domain/Scraping/Domain/Event/ChapterScrapingCompleted.php b/src/Domain/Scraping/Domain/Event/ChapterScrapingCompleted.php index c8dd183..09f6513 100644 --- a/src/Domain/Scraping/Domain/Event/ChapterScrapingCompleted.php +++ b/src/Domain/Scraping/Domain/Event/ChapterScrapingCompleted.php @@ -6,7 +6,7 @@ class ChapterScrapingCompleted { public function __construct( private readonly string $jobId, - private readonly array $scrapedPages + private readonly array $scrapedPages, ) { } diff --git a/src/Domain/Scraping/Domain/Event/ChapterScrapingFailed.php b/src/Domain/Scraping/Domain/Event/ChapterScrapingFailed.php index 95abcee..b28f6cd 100644 --- a/src/Domain/Scraping/Domain/Event/ChapterScrapingFailed.php +++ b/src/Domain/Scraping/Domain/Event/ChapterScrapingFailed.php @@ -8,7 +8,7 @@ readonly class ChapterScrapingFailed private string $jobId, private string $mangaId, private string $chapterNumber, - private string $reason + private string $reason, ) { } diff --git a/src/Domain/Scraping/Domain/Event/PageScrapingProgressed.php b/src/Domain/Scraping/Domain/Event/PageScrapingProgressed.php index 3fe846d..05ba62e 100644 --- a/src/Domain/Scraping/Domain/Event/PageScrapingProgressed.php +++ b/src/Domain/Scraping/Domain/Event/PageScrapingProgressed.php @@ -7,8 +7,8 @@ use App\Domain\Scraping\Domain\Model\ScrapingProgress; readonly class PageScrapingProgressed { public function __construct( - private string $jobId, - private ScrapingProgress $progress + private string $jobId, + private ScrapingProgress $progress, ) { } diff --git a/src/Domain/Scraping/Domain/Exception/SourceNotFoundException.php b/src/Domain/Scraping/Domain/Exception/SourceNotFoundException.php index 56aa369..c886bb1 100644 --- a/src/Domain/Scraping/Domain/Exception/SourceNotFoundException.php +++ b/src/Domain/Scraping/Domain/Exception/SourceNotFoundException.php @@ -2,8 +2,6 @@ namespace App\Domain\Scraping\Domain\Exception; -use Exception; - -class SourceNotFoundException extends Exception +class SourceNotFoundException extends \Exception { } diff --git a/src/Domain/Scraping/Domain/Model/Manga.php b/src/Domain/Scraping/Domain/Model/Manga.php index 466ec41..215f71e 100644 --- a/src/Domain/Scraping/Domain/Model/Manga.php +++ b/src/Domain/Scraping/Domain/Model/Manga.php @@ -59,9 +59,6 @@ class Manga return $this->preferredSources; } - /** - * @return bool - */ public function hasPreferredSources(): bool { return !empty($this->preferredSources); diff --git a/src/Domain/Scraping/Domain/Model/ScrapingJob.php b/src/Domain/Scraping/Domain/Model/ScrapingJob.php index 6732d3b..1f333c8 100644 --- a/src/Domain/Scraping/Domain/Model/ScrapingJob.php +++ b/src/Domain/Scraping/Domain/Model/ScrapingJob.php @@ -10,14 +10,14 @@ class ScrapingJob extends Job string $id, ?string $mangaId = null, ?float $chapterNumber = null, - ?string $sourceId = null + ?string $sourceId = null, ) { parent::__construct($id, 'scraping_job'); $this->maxAttempts = 1; $this->context = [ 'mangaId' => $mangaId, 'chapterNumber' => $chapterNumber, - 'sourceId' => $sourceId + 'sourceId' => $sourceId, ]; } } diff --git a/src/Domain/Scraping/Domain/Model/ScrapingProgress.php b/src/Domain/Scraping/Domain/Model/ScrapingProgress.php index 768468c..1a45d1b 100644 --- a/src/Domain/Scraping/Domain/Model/ScrapingProgress.php +++ b/src/Domain/Scraping/Domain/Model/ScrapingProgress.php @@ -6,15 +6,16 @@ readonly class ScrapingProgress { public function __construct( private int $pagesScraped, - private int $totalPages + private int $totalPages, ) { } public function getPercentage(): float { - if ($this->totalPages === 0) { + if (0 === $this->totalPages) { return 0; } + return ($this->pagesScraped / $this->totalPages) * 100; } } diff --git a/src/Domain/Scraping/Domain/Model/Source.php b/src/Domain/Scraping/Domain/Model/Source.php index 1bddef4..3509695 100644 --- a/src/Domain/Scraping/Domain/Model/Source.php +++ b/src/Domain/Scraping/Domain/Model/Source.php @@ -4,19 +4,18 @@ namespace App\Domain\Scraping\Domain\Model; use App\Domain\Scraping\Domain\Model\ValueObject\ChapterUrl; use App\Domain\Scraping\Domain\Model\ValueObject\SourceId; -use DateTimeImmutable; readonly class Source { public function __construct( - private SourceId $id, - private string $name, - private string $description, - private string $baseUrl, - private array $scrappingParameters, - private bool $isActive, - private DateTimeImmutable $createdAt, - private DateTimeImmutable $updatedAt + private SourceId $id, + private string $name, + private string $description, + private string $baseUrl, + private array $scrappingParameters, + private bool $isActive, + private \DateTimeImmutable $createdAt, + private \DateTimeImmutable $updatedAt, ) { } @@ -50,12 +49,12 @@ readonly class Source return $this->isActive; } - public function getCreatedAt(): DateTimeImmutable + public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; } - public function getUpdatedAt(): DateTimeImmutable + public function getUpdatedAt(): \DateTimeImmutable { return $this->updatedAt; } @@ -63,6 +62,7 @@ readonly class Source public function buildChapterUrl(string $mangaSlug, float $chapterNumber): string { $chapterUrl = new ChapterUrl($this->scrappingParameters['chapterUrlFormat'], $mangaSlug, $chapterNumber); + return $chapterUrl->getUrl(); } } diff --git a/src/Domain/Scraping/Domain/Model/ValueObject/ChapterUrl.php b/src/Domain/Scraping/Domain/Model/ValueObject/ChapterUrl.php index 6d50e98..ea8bd69 100644 --- a/src/Domain/Scraping/Domain/Model/ValueObject/ChapterUrl.php +++ b/src/Domain/Scraping/Domain/Model/ValueObject/ChapterUrl.php @@ -2,8 +2,6 @@ namespace App\Domain\Scraping\Domain\Model\ValueObject; -use InvalidArgumentException; - class ChapterUrl { private string $chapterUrlFormat; @@ -13,7 +11,7 @@ class ChapterUrl public function __construct( string $chapterUrlFormat, string $mangaSlug, - float $chapterNumber + float $chapterNumber, ) { $this->chapterUrlFormat = $this->validateUrlFormat($chapterUrlFormat); $this->mangaSlug = $mangaSlug; @@ -33,10 +31,9 @@ class ChapterUrl private function validateUrlFormat(string $format): string { if (!str_contains($format, '{slug}')) { - throw new InvalidArgumentException("The URL format must contain {slug} placeholder."); + throw new \InvalidArgumentException('The URL format must contain {slug} placeholder.'); } return $format; } - } diff --git a/src/Domain/Scraping/Domain/Model/ValueObject/DownloadResult.php b/src/Domain/Scraping/Domain/Model/ValueObject/DownloadResult.php index 8d71235..bbba7e1 100644 --- a/src/Domain/Scraping/Domain/Model/ValueObject/DownloadResult.php +++ b/src/Domain/Scraping/Domain/Model/ValueObject/DownloadResult.php @@ -6,7 +6,7 @@ readonly class DownloadResult { public function __construct( private string $localPath, - private string $originalUrl + private string $originalUrl, ) { if (empty($localPath)) { throw new \InvalidArgumentException('Local path cannot be empty'); diff --git a/src/Domain/Scraping/Domain/Model/ValueObject/ImageUrl.php b/src/Domain/Scraping/Domain/Model/ValueObject/ImageUrl.php index 95df920..826843b 100644 --- a/src/Domain/Scraping/Domain/Model/ValueObject/ImageUrl.php +++ b/src/Domain/Scraping/Domain/Model/ValueObject/ImageUrl.php @@ -5,7 +5,7 @@ namespace App\Domain\Scraping\Domain\Model\ValueObject; readonly class ImageUrl { public function __construct( - private string $url + private string $url, ) { if (!filter_var($url, FILTER_VALIDATE_URL)) { throw new \InvalidArgumentException('Invalid image URL provided'); diff --git a/src/Domain/Scraping/Domain/Model/ValueObject/PageNumber.php b/src/Domain/Scraping/Domain/Model/ValueObject/PageNumber.php index 4aa1564..52239ea 100644 --- a/src/Domain/Scraping/Domain/Model/ValueObject/PageNumber.php +++ b/src/Domain/Scraping/Domain/Model/ValueObject/PageNumber.php @@ -5,7 +5,7 @@ namespace App\Domain\Scraping\Domain\Model\ValueObject; class PageNumber { public function __construct( - private readonly int $number + private readonly int $number, ) { if ($number < 1) { throw new \InvalidArgumentException('Page number must be greater than 0'); diff --git a/src/Domain/Scraping/Domain/Model/ValueObject/ScrapingResult.php b/src/Domain/Scraping/Domain/Model/ValueObject/ScrapingResult.php index 71654e6..b2423c1 100644 --- a/src/Domain/Scraping/Domain/Model/ValueObject/ScrapingResult.php +++ b/src/Domain/Scraping/Domain/Model/ValueObject/ScrapingResult.php @@ -6,11 +6,11 @@ readonly class ScrapingResult { public function __construct( private array $imageUrls, - private int $totalPages + private int $totalPages, ) { foreach ($imageUrls as $url) { if (!filter_var($url, FILTER_VALIDATE_URL)) { - throw new \InvalidArgumentException('Invalid image URL provided: ' . $url); + throw new \InvalidArgumentException('Invalid image URL provided: '.$url); } } diff --git a/src/Domain/Scraping/Domain/Model/ValueObject/TempDirectory.php b/src/Domain/Scraping/Domain/Model/ValueObject/TempDirectory.php index bafccbe..ae7b5e5 100644 --- a/src/Domain/Scraping/Domain/Model/ValueObject/TempDirectory.php +++ b/src/Domain/Scraping/Domain/Model/ValueObject/TempDirectory.php @@ -8,7 +8,7 @@ class TempDirectory public function __construct() { - $this->path = sys_get_temp_dir() . '/' . uniqid('manga_scraper_'); + $this->path = sys_get_temp_dir().'/'.uniqid('manga_scraper_'); if (!mkdir($this->path, 0755, true)) { throw new \RuntimeException('Failed to create temporary directory'); } diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto/MangaPreferredSourcesDetail.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto/MangaPreferredSourcesDetail.php index 11f37bd..224ec7d 100644 --- a/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto/MangaPreferredSourcesDetail.php +++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/Dto/MangaPreferredSourcesDetail.php @@ -7,7 +7,7 @@ readonly class MangaPreferredSourcesDetail public function __construct( public string $mangaId, public array $sources, - public bool $hasPreferredSources + public bool $hasPreferredSources, ) { } } diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/GetMangaPreferredSourcesResource.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/GetMangaPreferredSourcesResource.php index bddceaf..5429c9b 100644 --- a/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/GetMangaPreferredSourcesResource.php +++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/GetMangaPreferredSourcesResource.php @@ -39,11 +39,11 @@ use App\Domain\Scraping\Infrastructure\ApiPlatform\State\Provider\GetMangaPrefer 'name' => ['type' => 'string'], 'baseUrl' => ['type' => 'string'], 'description' => ['type' => 'string'], - 'isActive' => ['type' => 'boolean'] - ] - ] - ] - ] + 'isActive' => ['type' => 'boolean'], + ], + ], + ], + ], ], 'example' => [ 'mangaId' => '1', @@ -54,29 +54,29 @@ use App\Domain\Scraping\Infrastructure\ApiPlatform\State\Provider\GetMangaPrefer 'name' => 'MangaDex', 'baseUrl' => 'https://mangadex.org', 'description' => 'Source principale', - 'isActive' => true + 'isActive' => true, ], [ 'id' => '2', 'name' => 'MangaKakalot', 'baseUrl' => 'https://mangakakalot.com', 'description' => 'Source secondaire', - 'isActive' => true - ] - ] - ] - ] + 'isActive' => true, + ], + ], + ], + ], ]) - ) + ), ] ) - ) + ), ] )] class GetMangaPreferredSourcesResource { public function __construct( - public string $id + public string $id, ) { } } diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/SetMangaPreferredSourcesResource.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/SetMangaPreferredSourcesResource.php index 0dc50ed..d882fb7 100644 --- a/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/SetMangaPreferredSourcesResource.php +++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/SetMangaPreferredSourcesResource.php @@ -28,18 +28,18 @@ use Symfony\Component\Validator\Constraints as Assert; 'properties' => [ 'sourceIds' => [ 'type' => 'array', - 'items' => ['type' => 'string'] - ] - ] + 'items' => ['type' => 'string'], + ], + ], ], 'example' => [ - 'sourceIds' => ['1', '2'] - ] - ] + 'sourceIds' => ['1', '2'], + ], + ], ]) ) ) - ) + ), ] )] class SetMangaPreferredSourcesResource @@ -47,9 +47,9 @@ class SetMangaPreferredSourcesResource public function __construct( #[Assert\NotNull] #[Assert\All([ - new Assert\Type('string') + new Assert\Type('string'), ])] - public array $sourceIds = [] + public array $sourceIds = [], ) { } } diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/TestScraperConfigurationRequest.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/TestScraperConfigurationRequest.php index 13e3e02..0885249 100644 --- a/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/TestScraperConfigurationRequest.php +++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/TestScraperConfigurationRequest.php @@ -36,56 +36,56 @@ use Symfony\Component\Validator\Constraints as Assert; 'type' => 'string', 'format' => 'uri', 'description' => 'URL de base du site web source', - 'example' => 'https://mangasite.example.com' + 'example' => 'https://mangasite.example.com', ], 'chapterUrlFormat' => [ 'type' => 'string', 'description' => 'Format d\'URL pour accéder aux chapitres avec placeholders {slug} et {chapter}', - 'example' => 'https://mangasite.example.com/manga/{slug}/chapter/{chapter}' + 'example' => 'https://mangasite.example.com/manga/{slug}/chapter/{chapter}', ], 'scrapingType' => [ 'type' => 'string', 'enum' => ['html', 'javascript'], 'description' => 'Type de scraping à utiliser (html pour les sites statiques, javascript pour les sites dynamiques)', - 'example' => 'html' + 'example' => 'html', ], 'testUrl' => [ 'type' => 'string', 'format' => 'uri', 'description' => 'URL complète d\'un chapitre existant à utiliser pour le test', - 'example' => 'https://mangasite.example.com/manga/one-piece/chapter/1' + 'example' => 'https://mangasite.example.com/manga/one-piece/chapter/1', ], 'mangaSlug' => [ 'type' => 'string', 'description' => 'Slug du manga utilisé dans les URLs (sera utilisé pour construire les URLs futures)', - 'example' => 'one-piece' + 'example' => 'one-piece', ], 'chapterNumber' => [ 'type' => 'number', 'minimum' => 0, 'description' => 'Numéro du chapitre à tester', - 'example' => 1.0 + 'example' => 1.0, ], 'imageSelector' => [ 'type' => 'string', 'nullable' => true, 'description' => 'Sélecteur CSS pour identifier les images dans la page', - 'example' => 'img.manga-page, .chapter-image img' + 'example' => 'img.manga-page, .chapter-image img', ], 'nextPageSelector' => [ 'type' => 'string', 'nullable' => true, 'description' => 'Sélecteur CSS pour le lien vers la page suivante (pour les lecteurs horizontaux)', - 'example' => 'a.next-page, .navigation .next' + 'example' => 'a.next-page, .navigation .next', ], 'chapterSelector' => [ 'type' => 'string', 'nullable' => true, 'description' => 'Sélecteur CSS pour identifier la zone contenant le chapitre', - 'example' => '.chapter-content, #manga-reader' - ] + 'example' => '.chapter-content, #manga-reader', + ], ], - 'required' => ['baseUrl', 'chapterUrlFormat', 'scrapingType', 'testUrl', 'mangaSlug', 'chapterNumber'] + 'required' => ['baseUrl', 'chapterUrlFormat', 'scrapingType', 'testUrl', 'mangaSlug', 'chapterNumber'], ], 'examples' => [ 'lecteur_vertical' => [ @@ -100,8 +100,8 @@ use Symfony\Component\Validator\Constraints as Assert; 'chapterNumber' => 1.0, 'imageSelector' => 'img.manga-page', 'nextPageSelector' => null, - 'chapterSelector' => '.chapter-content' - ] + 'chapterSelector' => '.chapter-content', + ], ], 'lecteur_horizontal' => [ 'summary' => 'Configuration pour un lecteur horizontal', @@ -115,11 +115,11 @@ use Symfony\Component\Validator\Constraints as Assert; 'chapterNumber' => 1.0, 'imageSelector' => '#manga-image', 'nextPageSelector' => 'a.next-page', - 'chapterSelector' => '.reader-container' - ] - ] - ] - ] + 'chapterSelector' => '.reader-container', + ], + ], + ], + ], ]) ), responses: [ @@ -132,28 +132,28 @@ use Symfony\Component\Validator\Constraints as Assert; 'properties' => [ 'success' => [ 'type' => 'boolean', - 'description' => 'Indique si le test a réussi' + 'description' => 'Indique si le test a réussi', ], 'imageUrls' => [ 'type' => 'array', 'items' => [ 'type' => 'string', - 'format' => 'uri' + 'format' => 'uri', ], - 'description' => 'Liste des URLs d\'images trouvées' + 'description' => 'Liste des URLs d\'images trouvées', ], 'totalImages' => [ 'type' => 'integer', - 'description' => 'Nombre total d\'images trouvées' + 'description' => 'Nombre total d\'images trouvées', ], 'testedUrl' => [ 'type' => 'string', 'format' => 'uri', - 'description' => 'URL qui a été testée' + 'description' => 'URL qui a été testée', ], 'scrapingType' => [ 'type' => 'string', - 'description' => 'Type de scraping utilisé' + 'description' => 'Type de scraping utilisé', ], 'errors' => [ 'type' => 'array', @@ -162,25 +162,25 @@ use Symfony\Component\Validator\Constraints as Assert; 'properties' => [ 'type' => [ 'type' => 'string', - 'description' => 'Type d\'erreur (selector_error, url_error, general_error)' + 'description' => 'Type d\'erreur (selector_error, url_error, general_error)', ], 'field' => [ 'type' => 'string', - 'description' => 'Champ concerné par l\'erreur' + 'description' => 'Champ concerné par l\'erreur', ], 'message' => [ 'type' => 'string', - 'description' => 'Message d\'erreur détaillé' + 'description' => 'Message d\'erreur détaillé', ], 'suggestion' => [ 'type' => 'string', - 'description' => 'Suggestion pour corriger l\'erreur' - ] - ] + 'description' => 'Suggestion pour corriger l\'erreur', + ], + ], ], - 'description' => 'Liste des erreurs détaillées (vide en cas de succès)' - ] - ] + 'description' => 'Liste des erreurs détaillées (vide en cas de succès)', + ], + ], ], 'examples' => [ 'succes' => [ @@ -190,13 +190,13 @@ use Symfony\Component\Validator\Constraints as Assert; 'imageUrls' => [ 'https://mangasite.example.com/images/chapter1/page1.jpg', 'https://mangasite.example.com/images/chapter1/page2.jpg', - 'https://mangasite.example.com/images/chapter1/page3.jpg' + 'https://mangasite.example.com/images/chapter1/page3.jpg', ], 'totalImages' => 3, 'testedUrl' => 'https://mangasite.example.com/manga/one-piece/chapter/1', 'scrapingType' => 'html', - 'errors' => [] - ] + 'errors' => [], + ], ], 'echec_selecteur' => [ 'summary' => 'Échec - Sélecteur invalide', @@ -211,10 +211,10 @@ use Symfony\Component\Validator\Constraints as Assert; 'type' => 'selector_error', 'field' => 'imageSelector', 'message' => 'Le sélecteur d\'image \'.invalid-selector\' ne trouve aucun élément sur la page', - 'suggestion' => 'Vérifiez que le sélecteur CSS est correct et qu\'il correspond aux éléments d\'image sur la page' - ] - ] - ] + 'suggestion' => 'Vérifiez que le sélecteur CSS est correct et qu\'il correspond aux éléments d\'image sur la page', + ], + ], + ], ], 'echec_url' => [ 'summary' => 'Échec - URL inaccessible', @@ -229,13 +229,13 @@ use Symfony\Component\Validator\Constraints as Assert; 'type' => 'url_error', 'field' => 'testUrl', 'message' => 'Impossible d\'accéder à l\'URL de test: https://invalid-site.example.com/chapter/1', - 'suggestion' => 'Vérifiez que l\'URL est correcte et accessible, et que le chapitre existe' - ] - ] - ] - ] - ] - ] + 'suggestion' => 'Vérifiez que l\'URL est correcte et accessible, et que le chapitre existe', + ], + ], + ], + ], + ], + ], ]) ), '422' => new OpenApiResponse( @@ -247,15 +247,15 @@ use Symfony\Component\Validator\Constraints as Assert; 'properties' => [ 'type' => [ 'type' => 'string', - 'example' => 'https://tools.ietf.org/html/rfc2616#section-10' + 'example' => 'https://tools.ietf.org/html/rfc2616#section-10', ], 'title' => [ 'type' => 'string', - 'example' => 'An error occurred' + 'example' => 'An error occurred', ], 'detail' => [ 'type' => 'string', - 'example' => 'baseUrl: L\'URL de base doit être une URL valide' + 'example' => 'baseUrl: L\'URL de base doit être une URL valide', ], 'violations' => [ 'type' => 'array', @@ -264,16 +264,16 @@ use Symfony\Component\Validator\Constraints as Assert; 'properties' => [ 'propertyPath' => [ 'type' => 'string', - 'description' => 'Champ contenant l\'erreur' + 'description' => 'Champ contenant l\'erreur', ], 'message' => [ 'type' => 'string', - 'description' => 'Message d\'erreur de validation' - ] - ] - ] - ] - ] + 'description' => 'Message d\'erreur de validation', + ], + ], + ], + ], + ], ], 'example' => [ 'type' => 'https://symfony.com/errors/validation', @@ -282,13 +282,13 @@ use Symfony\Component\Validator\Constraints as Assert; 'violations' => [ [ 'propertyPath' => 'baseUrl', - 'message' => 'L\'URL de base doit être une URL valide' - ] - ] - ] - ] + 'message' => 'L\'URL de base doit être une URL valide', + ], + ], + ], + ], ]) - ) + ), ] ) ), diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/TestScraperConfigurationResource.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/TestScraperConfigurationResource.php index 566e919..51eaa26 100644 --- a/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/TestScraperConfigurationResource.php +++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/Resource/TestScraperConfigurationResource.php @@ -17,14 +17,14 @@ readonly class TestScraperConfigurationResource description: 'Liste des URLs d\'images trouvées lors du scraping. Vide en cas d\'échec.', example: [ 'https://mangasite.example.com/images/chapter1/page1.jpg', - 'https://mangasite.example.com/images/chapter1/page2.jpg' + 'https://mangasite.example.com/images/chapter1/page2.jpg', ], schema: [ 'type' => 'array', 'items' => [ 'type' => 'string', - 'format' => 'uri' - ] + 'format' => 'uri', + ], ] )] public array $imageUrls, @@ -53,8 +53,8 @@ readonly class TestScraperConfigurationResource 'type' => 'selector_error', 'field' => 'imageSelector', 'message' => 'Le sélecteur d\'image ne trouve aucun élément', - 'suggestion' => 'Vérifiez le sélecteur CSS' - ] + 'suggestion' => 'Vérifiez le sélecteur CSS', + ], ], schema: [ 'type' => 'array', @@ -64,23 +64,23 @@ readonly class TestScraperConfigurationResource 'type' => [ 'type' => 'string', 'enum' => ['selector_error', 'url_error', 'general_error'], - 'description' => 'Type d\'erreur rencontrée' + 'description' => 'Type d\'erreur rencontrée', ], 'field' => [ 'type' => 'string', - 'description' => 'Champ de configuration concerné par l\'erreur' + 'description' => 'Champ de configuration concerné par l\'erreur', ], 'message' => [ 'type' => 'string', - 'description' => 'Message d\'erreur détaillé' + 'description' => 'Message d\'erreur détaillé', ], 'suggestion' => [ 'type' => 'string', - 'description' => 'Suggestion pour corriger l\'erreur' - ] + 'description' => 'Suggestion pour corriger l\'erreur', + ], ], - 'required' => ['type', 'field', 'message', 'suggestion'] - ] + 'required' => ['type', 'field', 'message', 'suggestion'], + ], ] )] public array $errors = [], diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/ScrapeChapterStateProcessor.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/ScrapeChapterStateProcessor.php index 71770de..490a69a 100644 --- a/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/ScrapeChapterStateProcessor.php +++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/ScrapeChapterStateProcessor.php @@ -35,13 +35,13 @@ final class ScrapeChapterStateProcessor implements ProcessorInterface $this->hub->publish(new Update( 'jobs/activity', json_encode([ - 'type' => 'job.created', - 'id' => $job->id, - 'type_job' => $job->type, - 'status' => $job->status->value, + 'type' => 'job.created', + 'id' => $job->id, + 'type_job' => $job->type, + 'status' => $job->status->value, 'createdAt' => $job->createdAt->format('c'), - 'context' => $job->context, - 'attempts' => $job->attempts, + 'context' => $job->context, + 'attempts' => $job->attempts, 'maxAttempts' => $job->maxAttempts, ]) )); diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/SetMangaPreferredSourcesStateProcessor.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/SetMangaPreferredSourcesStateProcessor.php index e584e3a..786f6c1 100644 --- a/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/SetMangaPreferredSourcesStateProcessor.php +++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/SetMangaPreferredSourcesStateProcessor.php @@ -11,7 +11,7 @@ use App\Domain\Scraping\Infrastructure\ApiPlatform\Resource\SetMangaPreferredSou final class SetMangaPreferredSourcesStateProcessor implements ProcessorInterface { public function __construct( - private readonly SetMangaPreferredSourcesHandler $handler + private readonly SetMangaPreferredSourcesHandler $handler, ) { } diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/TestScraperConfigurationStateProcessor.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/TestScraperConfigurationStateProcessor.php index 7149fce..188e2d8 100644 --- a/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/TestScraperConfigurationStateProcessor.php +++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Processor/TestScraperConfigurationStateProcessor.php @@ -12,7 +12,7 @@ use App\Domain\Scraping\Infrastructure\ApiPlatform\Resource\TestScraperConfigura readonly class TestScraperConfigurationStateProcessor implements ProcessorInterface { public function __construct( - private TestScraperConfigurationHandler $handler + private TestScraperConfigurationHandler $handler, ) { } diff --git a/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Provider/GetMangaPreferredSourcesStateProvider.php b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Provider/GetMangaPreferredSourcesStateProvider.php index ce5d464..966d88f 100644 --- a/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Provider/GetMangaPreferredSourcesStateProvider.php +++ b/src/Domain/Scraping/Infrastructure/ApiPlatform/State/Provider/GetMangaPreferredSourcesStateProvider.php @@ -11,7 +11,7 @@ use App\Domain\Scraping\Infrastructure\ApiPlatform\Dto\MangaPreferredSourcesDeta final class GetMangaPreferredSourcesStateProvider implements ProviderInterface { public function __construct( - private readonly GetMangaPreferredSourcesHandler $queryHandler + private readonly GetMangaPreferredSourcesHandler $queryHandler, ) { } @@ -33,7 +33,7 @@ final class GetMangaPreferredSourcesStateProvider implements ProviderInterface 'name' => $source->getName(), 'baseUrl' => $source->getBaseUrl(), 'description' => $source->getDescription(), - 'isActive' => $source->isActive() + 'isActive' => $source->isActive(), ]; }, $response->sources); diff --git a/src/Domain/Scraping/Infrastructure/CommandHandler/SymfonyScrapeChapterHandler.php b/src/Domain/Scraping/Infrastructure/CommandHandler/SymfonyScrapeChapterHandler.php index ef260e6..a8cfe40 100644 --- a/src/Domain/Scraping/Infrastructure/CommandHandler/SymfonyScrapeChapterHandler.php +++ b/src/Domain/Scraping/Infrastructure/CommandHandler/SymfonyScrapeChapterHandler.php @@ -10,7 +10,7 @@ use Symfony\Component\Messenger\Attribute\AsMessageHandler; class SymfonyScrapeChapterHandler { public function __construct( - private ScrapeChapterHandler $handler + private ScrapeChapterHandler $handler, ) { } diff --git a/src/Domain/Scraping/Infrastructure/EventSubscriber/ScrapingEventSubscriber.php b/src/Domain/Scraping/Infrastructure/EventSubscriber/ScrapingEventSubscriber.php index 8579743..7b149a0 100644 --- a/src/Domain/Scraping/Infrastructure/EventSubscriber/ScrapingEventSubscriber.php +++ b/src/Domain/Scraping/Infrastructure/EventSubscriber/ScrapingEventSubscriber.php @@ -2,13 +2,13 @@ namespace App\Domain\Scraping\Infrastructure\EventSubscriber; -use App\Domain\Shared\Domain\Event\ChapterScraped; +use App\Domain\Scraping\Domain\Contract\Repository\ChapterRepositoryInterface; use App\Domain\Scraping\Domain\Event\ChapterScrapingFailed; use App\Domain\Scraping\Domain\Event\ChapterScrapingStarted; use App\Domain\Scraping\Domain\Event\PageScrapingProgressed; -use App\Domain\Scraping\Domain\Contract\Repository\ChapterRepositoryInterface; use App\Domain\Shared\Domain\Contract\JobRepositoryInterface; use App\Domain\Shared\Domain\Contract\NotificationInterface; +use App\Domain\Shared\Domain\Event\ChapterScraped; use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Mercure\HubInterface; @@ -22,7 +22,7 @@ class ScrapingEventSubscriber implements EventSubscriberInterface private readonly ChapterRepositoryInterface $chapterRepository, private readonly JobRepositoryInterface $jobRepository, private readonly NotificationInterface $notification, - private readonly LoggerInterface $logger + private readonly LoggerInterface $logger, ) { } @@ -39,8 +39,8 @@ class ScrapingEventSubscriber implements EventSubscriberInterface $update = new Update( 'jobs/activity', json_encode([ - 'type' => 'job.progress_updated', - 'jobId' => $event->getJobId(), + 'type' => 'job.progress_updated', + 'jobId' => $event->getJobId(), 'progress' => $progress, ]) ); @@ -64,26 +64,28 @@ class ScrapingEventSubscriber implements EventSubscriberInterface public function onChapterScraped(ChapterScraped $event): void { $jobId = $event->getJobId(); - $this->logger->info('ChapterScraped reçu pour le job: ' . $jobId); + $this->logger->info('ChapterScraped reçu pour le job: '.$jobId); $job = $this->jobRepository->get($jobId); if (!$job) { - $this->logger->warning('Job non trouvé pour l\'ID: ' . $jobId); + $this->logger->warning('Job non trouvé pour l\'ID: '.$jobId); + return; } $chapterId = $job->context['chapterId'] ?? null; - $this->logger->info('ChapterId extrait du job: ' . $chapterId); + $this->logger->info('ChapterId extrait du job: '.$chapterId); $chapter = $this->chapterRepository->getById($chapterId); if (!$chapter) { - $this->logger->warning('Chapitre non trouvé pour l\'ID: ' . $chapterId); + $this->logger->warning('Chapitre non trouvé pour l\'ID: '.$chapterId); + return; } - $this->logger->info('Chapitre trouvé - ID: ' . $chapter->id . ', MangaId: ' . $chapter->mangaId . ', Number: ' . $chapter->chapterNumber); + $this->logger->info('Chapitre trouvé - ID: '.$chapter->id.', MangaId: '.$chapter->mangaId.', Number: '.$chapter->chapterNumber); $data = [ 'type' => 'chapter.scraped', @@ -91,13 +93,13 @@ class ScrapingEventSubscriber implements EventSubscriberInterface 'mangaId' => $chapter->mangaId, 'chapterNumber' => $chapter->chapterNumber, 'isAvailable' => true, - 'timestamp' => (new \DateTimeImmutable())->format('c') + 'timestamp' => (new \DateTimeImmutable())->format('c'), ]; $topics = [ - 'manga/chapter/' . $chapter->id, - 'manga/' . $chapter->mangaId . '/chapters', - 'scraping/status' + 'manga/chapter/'.$chapter->id, + 'manga/'.$chapter->mangaId.'/chapters', + 'scraping/status', ]; $update = new Update($topics, json_encode($data)); @@ -122,19 +124,19 @@ class ScrapingEventSubscriber implements EventSubscriberInterface json_encode(['type' => 'job.status_changed', 'jobId' => $event->getJobId(), 'status' => 'failed']) )); - $this->logger->info('ChapterScrapingFailed reçu pour mangaId: ' . $event->getMangaId() . ', chapter: ' . $event->getChapterNumber()); + $this->logger->info('ChapterScrapingFailed reçu pour mangaId: '.$event->getMangaId().', chapter: '.$event->getChapterNumber()); $data = [ 'type' => 'chapter.scraping.failed', 'mangaId' => $event->getMangaId(), 'chapterNumber' => $event->getChapterNumber(), 'reason' => $event->getReason(), - 'timestamp' => (new \DateTimeImmutable())->format('c') + 'timestamp' => (new \DateTimeImmutable())->format('c'), ]; $topics = [ - 'manga/' . $event->getMangaId() . '/chapters', - 'scraping/status' + 'manga/'.$event->getMangaId().'/chapters', + 'scraping/status', ]; $update = new Update($topics, json_encode($data)); diff --git a/src/Domain/Scraping/Infrastructure/Persistence/LegacyChapterRepository.php b/src/Domain/Scraping/Infrastructure/Persistence/LegacyChapterRepository.php index bbee98a..7b72e7b 100644 --- a/src/Domain/Scraping/Infrastructure/Persistence/LegacyChapterRepository.php +++ b/src/Domain/Scraping/Infrastructure/Persistence/LegacyChapterRepository.php @@ -38,10 +38,10 @@ readonly class LegacyChapterRepository implements ChapterRepositoryInterface { $entity = $this->entityManager->getRepository(EntityChapter::class)->findOneBy([ 'manga' => $mangaId, - 'number' => $chapterNumber + 'number' => $chapterNumber, ]); - if ($entity === null) { + if (null === $entity) { throw new ChapterNotFoundException(); } diff --git a/src/Domain/Scraping/Infrastructure/Persistence/LegacyMangaRepository.php b/src/Domain/Scraping/Infrastructure/Persistence/LegacyMangaRepository.php index 923a09d..f84cf0e 100644 --- a/src/Domain/Scraping/Infrastructure/Persistence/LegacyMangaRepository.php +++ b/src/Domain/Scraping/Infrastructure/Persistence/LegacyMangaRepository.php @@ -5,14 +5,14 @@ namespace App\Domain\Scraping\Infrastructure\Persistence; use App\Domain\Scraping\Domain\Contract\Repository\MangaRepositoryInterface; use App\Domain\Scraping\Domain\Model\Manga; use App\Domain\Scraping\Infrastructure\Persistence\Entity\MangaPreferredSourceEntity; -use App\Entity\Manga as EntityManga; use App\Entity\ContentSource; +use App\Entity\Manga as EntityManga; use Doctrine\ORM\EntityManagerInterface; readonly class LegacyMangaRepository implements MangaRepositoryInterface { public function __construct( - private EntityManagerInterface $entityManager + private EntityManagerInterface $entityManager, ) { } diff --git a/src/Domain/Scraping/Infrastructure/Persistence/LegacySourceRepository.php b/src/Domain/Scraping/Infrastructure/Persistence/LegacySourceRepository.php index 7ed1249..89c2023 100644 --- a/src/Domain/Scraping/Infrastructure/Persistence/LegacySourceRepository.php +++ b/src/Domain/Scraping/Infrastructure/Persistence/LegacySourceRepository.php @@ -3,17 +3,15 @@ namespace App\Domain\Scraping\Infrastructure\Persistence; use App\Domain\Scraping\Domain\Contract\Repository\SourceRepositoryInterface; -use App\Domain\Scraping\Domain\Exception\SourceNotFoundException; use App\Domain\Scraping\Domain\Model\Source; use App\Domain\Scraping\Domain\Model\ValueObject\SourceId; use App\Entity\ContentSource; -use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; readonly class LegacySourceRepository implements SourceRepositoryInterface { public function __construct( - private EntityManagerInterface $entityManager + private EntityManagerInterface $entityManager, ) { } @@ -103,25 +101,25 @@ readonly class LegacySourceRepository implements SourceRepositoryInterface } /** - * Convertit une entité ContentSource en modèle Source + * Convertit une entité ContentSource en modèle Source. */ private function convertEntityToModel(ContentSource $source): Source { return new Source( id: new SourceId((string) $source->getId()), name: $source->getCleanBaseUrl(), - description: 'Legacy Source: ' . $source->getBaseUrl(), + description: 'Legacy Source: '.$source->getBaseUrl(), baseUrl: $source->getBaseUrl(), scrappingParameters: [ 'imageSelector' => $source->getImageSelector(), 'nextPageSelector' => $source->getNextPageSelector(), 'chapterUrlFormat' => $source->getChapterUrlFormat(), 'scrapingType' => $source->getScrapingType(), - 'chapterSelector' => $source->getChapterSelector() + 'chapterSelector' => $source->getChapterSelector(), ], isActive: true, - createdAt: new DateTimeImmutable(), - updatedAt: new DateTimeImmutable() + createdAt: new \DateTimeImmutable(), + updatedAt: new \DateTimeImmutable() ); } } diff --git a/src/Domain/Scraping/Infrastructure/Service/ImageDownloader.php b/src/Domain/Scraping/Infrastructure/Service/ImageDownloader.php index 69ffdb6..381dbdf 100644 --- a/src/Domain/Scraping/Infrastructure/Service/ImageDownloader.php +++ b/src/Domain/Scraping/Infrastructure/Service/ImageDownloader.php @@ -14,14 +14,14 @@ readonly class ImageDownloader implements ImageDownloaderInterface { public function __construct( private HttpClientInterface $httpClient, - private MessageBusInterface $eventBus + private MessageBusInterface $eventBus, ) { } public function download(string $url, string $destination): void { $urlParts = parse_url($url); - $referer = ($urlParts['scheme'] ?? 'https') . '://' . ($urlParts['host'] ?? ''); + $referer = ($urlParts['scheme'] ?? 'https').'://'.($urlParts['host'] ?? ''); $response = $this->httpClient->request('GET', $url, [ 'headers' => [ @@ -31,7 +31,7 @@ readonly class ImageDownloader implements ImageDownloaderInterface $contentType = $response->getHeaders()['content-type'][0] ?? ''; if (!str_starts_with($contentType, 'image/')) { - throw new \RuntimeException('Invalid content type: ' . $contentType); + throw new \RuntimeException('Invalid content type: '.$contentType); } $imageData = $response->getContent(); @@ -77,7 +77,7 @@ readonly class ImageDownloader implements ImageDownloaderInterface $this->dispatchProgressEvent($jobId, $index + 1, $totalUrls); } catch (\Exception $e) { // Log l'erreur mais continue avec les autres images - error_log("Failed to download image {$url}: " . $e->getMessage()); + error_log("Failed to download image {$url}: ".$e->getMessage()); } } @@ -117,7 +117,7 @@ readonly class ImageDownloader implements ImageDownloaderInterface 'jpeg' => imagecreatefromjpeg($filePath), 'gif' => imagecreatefromgif($filePath), 'bmp' => imagecreatefromwbmp($filePath), - default => throw new \RuntimeException('Unsupported image format: ' . $realFormat . ' (content-type: ' . $contentType . ')'), + default => throw new \RuntimeException('Unsupported image format: '.$realFormat.' (content-type: '.$contentType.')'), }; } @@ -142,7 +142,7 @@ readonly class ImageDownloader implements ImageDownloaderInterface } // WebP: starts with RIFF....WEBP - if (str_starts_with($header, 'RIFF') && strpos($header, 'WEBP', 8) === 8) { + if (str_starts_with($header, 'RIFF') && 8 === strpos($header, 'WEBP', 8)) { return 'webp'; } @@ -156,13 +156,13 @@ readonly class ImageDownloader implements ImageDownloaderInterface return 'bmp'; } - throw new \RuntimeException('Unknown image format. Header: ' . bin2hex(substr($header, 0, 8))); + throw new \RuntimeException('Unknown image format. Header: '.bin2hex(substr($header, 0, 8))); } private function ensureJpgExtension(string $path): string { $info = pathinfo($path); - return $info['dirname'] . '/' . $info['filename'] . '.jpg'; + return $info['dirname'].'/'.$info['filename'].'.jpg'; } } diff --git a/src/Domain/Scraping/Infrastructure/Service/Scraper/AdvancedHtmlScraper.php b/src/Domain/Scraping/Infrastructure/Service/Scraper/AdvancedHtmlScraper.php index 663be88..4eadaf6 100644 --- a/src/Domain/Scraping/Infrastructure/Service/Scraper/AdvancedHtmlScraper.php +++ b/src/Domain/Scraping/Infrastructure/Service/Scraper/AdvancedHtmlScraper.php @@ -6,8 +6,8 @@ use App\Domain\Scraping\Domain\Contract\Service\ScraperInterface; use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingRequest; use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingResult; use Symfony\Component\DomCrawler\Crawler; -use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Component\HttpClient\HttpClient; +use Symfony\Contracts\HttpClient\HttpClientInterface; class AdvancedHtmlScraper implements ScraperInterface { @@ -17,20 +17,20 @@ class AdvancedHtmlScraper implements ScraperInterface 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0', - 'Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0' + 'Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0', ]; private const ACCEPT_HEADERS = [ 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' + 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', ]; private const ACCEPT_LANGUAGE_HEADERS = [ 'en-US,en;q=0.9', 'en-US,en;q=0.8', 'en-GB,en;q=0.9', - 'fr-FR,fr;q=0.9,en;q=0.8' + 'fr-FR,fr;q=0.9,en;q=0.8', ]; private const RETRY_ATTEMPTS = 3; @@ -38,14 +38,14 @@ class AdvancedHtmlScraper implements ScraperInterface private const REQUEST_TIMEOUT = 30; public function __construct( - private readonly HttpClientInterface $httpClient + private readonly HttpClientInterface $httpClient, ) { // Utiliser un client HTTP personnalisé si non fourni if (!$this->httpClient) { $this->httpClient = HttpClient::create([ 'timeout' => self::REQUEST_TIMEOUT, 'verify_peer' => false, - 'verify_host' => false + 'verify_host' => false, ]); } } @@ -61,7 +61,7 @@ class AdvancedHtmlScraper implements ScraperInterface return new ScrapingResult($pages, count($pages)); } catch (\Exception $e) { - throw new \RuntimeException('Advanced HTML scraping failed: ' . $e->getMessage(), 0, $e); + throw new \RuntimeException('Advanced HTML scraping failed: '.$e->getMessage(), 0, $e); } } @@ -127,7 +127,7 @@ class AdvancedHtmlScraper implements ScraperInterface // Chercher le lien suivant $nextLink = $crawler->filter($params['nextPageSelector'])->first(); - if ($nextLink->count() === 0) { + if (0 === $nextLink->count()) { break; } @@ -137,7 +137,7 @@ class AdvancedHtmlScraper implements ScraperInterface } $currentUrl = $this->resolveRelativeUrl($nextUrl, $currentUrl); - $pageCount++; + ++$pageCount; // Pause entre les requêtes pour éviter la détection sleep(1); @@ -150,7 +150,7 @@ class AdvancedHtmlScraper implements ScraperInterface { $lastException = null; - for ($attempt = 1; $attempt <= self::RETRY_ATTEMPTS; $attempt++) { + for ($attempt = 1; $attempt <= self::RETRY_ATTEMPTS; ++$attempt) { try { return $this->fetchHtml($url); } catch (\Exception $e) { @@ -173,7 +173,7 @@ class AdvancedHtmlScraper implements ScraperInterface try { $response = $this->httpClient->request('GET', $url, [ 'headers' => $headers, - 'timeout' => self::REQUEST_TIMEOUT + 'timeout' => self::REQUEST_TIMEOUT, ]); $statusCode = $response->getStatusCode(); @@ -185,14 +185,14 @@ class AdvancedHtmlScraper implements ScraperInterface $content = $response->getContent(); // Vérifier si on a été bloqué par Cloudflare - if (strpos($content, 'cf-browser-verification') !== false || - strpos($content, 'Checking your browser') !== false) { + if (false !== strpos($content, 'cf-browser-verification') + || false !== strpos($content, 'Checking your browser')) { throw new \RuntimeException('Blocked by Cloudflare protection'); } return $content; } catch (\Exception $e) { - throw new \RuntimeException('Failed to fetch HTML: ' . $e->getMessage(), 0, $e); + throw new \RuntimeException('Failed to fetch HTML: '.$e->getMessage(), 0, $e); } } @@ -210,7 +210,7 @@ class AdvancedHtmlScraper implements ScraperInterface 'Sec-Fetch-Mode' => 'navigate', 'Sec-Fetch-Site' => 'none', 'Sec-Fetch-User' => '?1', - 'Cache-Control' => 'max-age=0' + 'Cache-Control' => 'max-age=0', ]; } @@ -223,16 +223,16 @@ class AdvancedHtmlScraper implements ScraperInterface $parsedBase = parse_url($baseUrl); $scheme = $parsedBase['scheme']; $host = $parsedBase['host']; - $port = isset($parsedBase['port']) ? ':' . $parsedBase['port'] : ''; + $port = isset($parsedBase['port']) ? ':'.$parsedBase['port'] : ''; - if (strpos($url, '/') === 0) { + if (0 === strpos($url, '/')) { // URL absolue relative à la racine - return $scheme . '://' . $host . $port . $url; - } else { - // URL relative au chemin actuel - $path = isset($parsedBase['path']) ? dirname($parsedBase['path']) : ''; - return $scheme . '://' . $host . $port . $path . '/' . $url; + return $scheme.'://'.$host.$port.$url; } + // URL relative au chemin actuel + $path = isset($parsedBase['path']) ? dirname($parsedBase['path']) : ''; + + return $scheme.'://'.$host.$port.$path.'/'.$url; } private function cleanImageUrl(string $url): string diff --git a/src/Domain/Scraping/Infrastructure/Service/Scraper/HtmlScraper.php b/src/Domain/Scraping/Infrastructure/Service/Scraper/HtmlScraper.php index dc890cd..95c08b9 100644 --- a/src/Domain/Scraping/Infrastructure/Service/Scraper/HtmlScraper.php +++ b/src/Domain/Scraping/Infrastructure/Service/Scraper/HtmlScraper.php @@ -3,21 +3,19 @@ namespace App\Domain\Scraping\Infrastructure\Service\Scraper; use App\Domain\Scraping\Domain\Contract\Service\ImageDownloaderInterface; -use Symfony\Component\DomCrawler\Crawler; -use Symfony\Contracts\HttpClient\HttpClientInterface; -use Symfony\Component\Messenger\MessageBusInterface; use App\Domain\Scraping\Domain\Contract\Service\ScraperInterface; -use App\Domain\Scraping\Domain\Event\PageScrapingProgressed; use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingRequest; use App\Domain\Scraping\Domain\Model\ValueObject\ScrapingResult; -use App\Domain\Scraping\Domain\Model\ScrapingProgress; +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; class HtmlScraper implements ScraperInterface { public function __construct( private readonly ImageDownloaderInterface $imageDownloader, private readonly MessageBusInterface $eventBus, - private readonly HttpClientInterface $httpClient + private readonly HttpClientInterface $httpClient, ) { } @@ -32,7 +30,7 @@ class HtmlScraper implements ScraperInterface return new ScrapingResult($pages, count($pages)); } catch (\Exception $e) { - throw new \RuntimeException('Scraping failed: ' . $e->getMessage(), 0, $e); + throw new \RuntimeException('Scraping failed: '.$e->getMessage(), 0, $e); } } @@ -91,13 +89,13 @@ class HtmlScraper implements ScraperInterface $response = $this->httpClient->request('GET', $url); $statusCode = $response->getStatusCode(); - if ($statusCode >= 300 && $statusCode < 400 || $statusCode === 404) { - throw new \RuntimeException('Chapter Not Found at ' . $url); + if ($statusCode >= 300 && $statusCode < 400 || 404 === $statusCode) { + throw new \RuntimeException('Chapter Not Found at '.$url); } return $response->getContent(); } catch (\Exception $e) { - throw new \RuntimeException('Failed to fetch HTML: ' . $e->getMessage(), 0, $e); + throw new \RuntimeException('Failed to fetch HTML: '.$e->getMessage(), 0, $e); } } diff --git a/src/Domain/Scraping/Infrastructure/Service/Scraper/JavaScriptScraper.php b/src/Domain/Scraping/Infrastructure/Service/Scraper/JavaScriptScraper.php index dee9398..e7fafa9 100644 --- a/src/Domain/Scraping/Infrastructure/Service/Scraper/JavaScriptScraper.php +++ b/src/Domain/Scraping/Infrastructure/Service/Scraper/JavaScriptScraper.php @@ -15,7 +15,7 @@ class JavaScriptScraper implements ScraperInterface private const NODE_EXECUTABLE = 'node'; public function __construct( - private readonly string $projectDir + private readonly string $projectDir, ) { } @@ -24,10 +24,10 @@ class JavaScriptScraper implements ScraperInterface $scrappingParameters = $request->getScrapingParameters(); try { - $scriptPath = $this->projectDir . self::PUPPETEER_SCRIPT_PATH; + $scriptPath = $this->projectDir.self::PUPPETEER_SCRIPT_PATH; if (!file_exists($scriptPath)) { - throw new \RuntimeException('Puppeteer script not found at: ' . $scriptPath); + throw new \RuntimeException('Puppeteer script not found at: '.$scriptPath); } $imageUrls = !empty($scrappingParameters['nextPageSelector']) @@ -36,7 +36,7 @@ class JavaScriptScraper implements ScraperInterface return new ScrapingResult($imageUrls, count($imageUrls)); } catch (\Exception $e) { - throw new \RuntimeException('JavaScript scraping failed: ' . $e->getMessage(), 0, $e); + throw new \RuntimeException('JavaScript scraping failed: '.$e->getMessage(), 0, $e); } } @@ -52,22 +52,23 @@ class JavaScriptScraper implements ScraperInterface self::NODE_EXECUTABLE, $scriptPath, '--mode=vertical', - '--url=' . $request->getChapterUrl(), - '--image-selector=' . $params['imageSelector'], + '--url='.$request->getChapterUrl(), + '--image-selector='.$params['imageSelector'], '--wait-for-images=true', - '--scroll=true' + '--scroll=true', ]; // Ajouter les paramètres de chapitre si disponibles if (!empty($params['chapterSelector'])) { - $processArgs[] = '--chapter-selector=' . $params['chapterSelector']; + $processArgs[] = '--chapter-selector='.$params['chapterSelector']; } if (isset($params['chapterNumber'])) { - $processArgs[] = '--chapter-number=' . $params['chapterNumber']; + $processArgs[] = '--chapter-number='.$params['chapterNumber']; } $process = new Process($processArgs); + return $this->executeProcess($process); } @@ -79,22 +80,23 @@ class JavaScriptScraper implements ScraperInterface self::NODE_EXECUTABLE, $scriptPath, '--mode=horizontal', - '--url=' . $request->getChapterUrl(), - '--image-selector=' . $params['imageSelector'], - '--next-selector=' . $params['nextPageSelector'], - '--wait-for-images=true' + '--url='.$request->getChapterUrl(), + '--image-selector='.$params['imageSelector'], + '--next-selector='.$params['nextPageSelector'], + '--wait-for-images=true', ]; // Ajouter les paramètres de chapitre si disponibles if (!empty($params['chapterSelector'])) { - $processArgs[] = '--chapter-selector=' . $params['chapterSelector']; + $processArgs[] = '--chapter-selector='.$params['chapterSelector']; } if (isset($params['chapterNumber'])) { - $processArgs[] = '--chapter-number=' . $params['chapterNumber']; + $processArgs[] = '--chapter-number='.$params['chapterNumber']; } $process = new Process($processArgs); + return $this->executeProcess($process); } @@ -105,7 +107,7 @@ class JavaScriptScraper implements ScraperInterface if (!$process->isSuccessful()) { $error = $process->getErrorOutput() ?: $process->getOutput(); - throw new \RuntimeException('Puppeteer process failed: ' . $error); + throw new \RuntimeException('Puppeteer process failed: '.$error); } $output = $process->getOutput(); @@ -113,7 +115,7 @@ class JavaScriptScraper implements ScraperInterface $resultLine = end($lines); // Gérer le cas où le chapitre n'est pas trouvé - if (strpos($resultLine, 'CHAPTER_NOT_FOUND:') === 0) { + if (0 === strpos($resultLine, 'CHAPTER_NOT_FOUND:')) { $jsonData = substr($resultLine, 18); // Remove 'CHAPTER_NOT_FOUND:' prefix $errorData = json_decode($jsonData, true); @@ -125,7 +127,7 @@ class JavaScriptScraper implements ScraperInterface } // Gérer le cas normal avec des images - if (strpos($resultLine, 'RESULT:') === 0) { + if (0 === strpos($resultLine, 'RESULT:')) { $jsonData = substr($resultLine, 7); // Remove 'RESULT:' prefix $imageUrls = json_decode($jsonData, true); @@ -137,7 +139,7 @@ class JavaScriptScraper implements ScraperInterface } // Format de sortie non reconnu - throw new \RuntimeException('Invalid Puppeteer output format: ' . $resultLine); + throw new \RuntimeException('Invalid Puppeteer output format: '.$resultLine); } private function cleanImageUrls(array $urls): array diff --git a/src/Domain/Scraping/Infrastructure/Service/ScraperFactory.php b/src/Domain/Scraping/Infrastructure/Service/ScraperFactory.php index f653098..3216bd9 100644 --- a/src/Domain/Scraping/Infrastructure/Service/ScraperFactory.php +++ b/src/Domain/Scraping/Infrastructure/Service/ScraperFactory.php @@ -2,12 +2,12 @@ namespace App\Domain\Scraping\Infrastructure\Service; -use App\Domain\Scraping\Domain\Contract\Service\ScraperInterface; -use App\Domain\Scraping\Domain\Contract\Service\ScraperFactoryInterface; -use App\Domain\Scraping\Infrastructure\Service\Scraper\HtmlScraper; -use App\Domain\Scraping\Infrastructure\Service\Scraper\AdvancedHtmlScraper; -use App\Domain\Scraping\Infrastructure\Service\Scraper\JavaScriptScraper; use App\Domain\Scraping\Domain\Contract\Service\ImageDownloaderInterface; +use App\Domain\Scraping\Domain\Contract\Service\ScraperFactoryInterface; +use App\Domain\Scraping\Domain\Contract\Service\ScraperInterface; +use App\Domain\Scraping\Infrastructure\Service\Scraper\AdvancedHtmlScraper; +use App\Domain\Scraping\Infrastructure\Service\Scraper\HtmlScraper; +use App\Domain\Scraping\Infrastructure\Service\Scraper\JavaScriptScraper; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -31,13 +31,13 @@ class ScraperFactory implements ScraperFactoryInterface private readonly ImageDownloaderInterface $imageDownloader, private readonly MessageBusInterface $eventBus, private readonly HttpClientInterface $httpClient, - private readonly string $projectDir + private readonly string $projectDir, ) { $this->initializeScrapers(); } /** - * Créer un scraper pour un type spécifique + * Créer un scraper pour un type spécifique. */ public function createScraper(string $type): ScraperInterface { @@ -49,7 +49,7 @@ class ScraperFactory implements ScraperFactoryInterface } /** - * Obtenir le scraper le plus approprié selon la priorité + * Obtenir le scraper le plus approprié selon la priorité. */ public function getBestScraper(): ScraperInterface { @@ -60,7 +60,7 @@ class ScraperFactory implements ScraperFactoryInterface } /** - * Obtenir tous les scrapers disponibles + * Obtenir tous les scrapers disponibles. */ public function getAvailableScrapers(): array { @@ -68,7 +68,7 @@ class ScraperFactory implements ScraperFactoryInterface } /** - * Obtenir les types de scrapers supportés + * Obtenir les types de scrapers supportés. */ public function getSupportedTypes(): array { @@ -76,7 +76,7 @@ class ScraperFactory implements ScraperFactoryInterface } /** - * Vérifier si un type de scraper est supporté + * Vérifier si un type de scraper est supporté. */ public function isSupported(string $type): bool { @@ -84,7 +84,7 @@ class ScraperFactory implements ScraperFactoryInterface } /** - * Obtenir le scraper de fallback (le plus simple) + * Obtenir le scraper de fallback (le plus simple). */ public function getFallbackScraper(): ScraperInterface { @@ -92,7 +92,7 @@ class ScraperFactory implements ScraperFactoryInterface } /** - * Essayer plusieurs scrapers en cascade jusqu'à ce qu'un fonctionne + * Essayer plusieurs scrapers en cascade jusqu'à ce qu'un fonctionne. */ public function getScraperWithFallback(string $preferredType): ScraperInterface { @@ -106,7 +106,7 @@ class ScraperFactory implements ScraperFactoryInterface } /** - * Obtenir des statistiques sur les scrapers + * Obtenir des statistiques sur les scrapers. */ public function getScraperStats(): array { @@ -115,7 +115,7 @@ class ScraperFactory implements ScraperFactoryInterface 'supported_types' => $this->getSupportedTypes(), 'priorities' => self::SCRAPER_PRIORITIES, 'best_scraper' => $this->getBestScraper()::class, - 'fallback_scraper' => $this->getFallbackScraper()::class + 'fallback_scraper' => $this->getFallbackScraper()::class, ]; } @@ -140,7 +140,7 @@ class ScraperFactory implements ScraperFactoryInterface JavaScriptScraper::class => new JavaScriptScraper( $this->projectDir ), - default => throw new \InvalidArgumentException("Unknown scraper class: {$class}") + default => throw new \InvalidArgumentException("Unknown scraper class: {$class}"), }; } } diff --git a/src/Domain/Setting/Application/Command/DeleteContentSourceCommand.php b/src/Domain/Setting/Application/Command/DeleteContentSourceCommand.php index 5483f60..f841f20 100644 --- a/src/Domain/Setting/Application/Command/DeleteContentSourceCommand.php +++ b/src/Domain/Setting/Application/Command/DeleteContentSourceCommand.php @@ -5,7 +5,7 @@ namespace App\Domain\Setting\Application\Command; readonly class DeleteContentSourceCommand { public function __construct( - public int $id + public int $id, ) { } } diff --git a/src/Domain/Setting/Application/Command/ImportContentSourceCommand.php b/src/Domain/Setting/Application/Command/ImportContentSourceCommand.php index 184360e..9142bd8 100644 --- a/src/Domain/Setting/Application/Command/ImportContentSourceCommand.php +++ b/src/Domain/Setting/Application/Command/ImportContentSourceCommand.php @@ -5,7 +5,7 @@ namespace App\Domain\Setting\Application\Command; readonly class ImportContentSourceCommand { public function __construct( - public array $contentSources + public array $contentSources, ) { } } diff --git a/src/Domain/Setting/Application/CommandHandler/DeleteContentSourceCommandHandler.php b/src/Domain/Setting/Application/CommandHandler/DeleteContentSourceCommandHandler.php index bbf645c..4c49c3c 100644 --- a/src/Domain/Setting/Application/CommandHandler/DeleteContentSourceCommandHandler.php +++ b/src/Domain/Setting/Application/CommandHandler/DeleteContentSourceCommandHandler.php @@ -9,7 +9,7 @@ use App\Domain\Setting\Domain\Exception\ContentSourceNotFoundException; readonly class DeleteContentSourceCommandHandler { public function __construct( - private ContentSourceRepositoryInterface $contentSourceRepository + private ContentSourceRepositoryInterface $contentSourceRepository, ) { } diff --git a/src/Domain/Setting/Application/CommandHandler/ImportContentSourceCommandHandler.php b/src/Domain/Setting/Application/CommandHandler/ImportContentSourceCommandHandler.php index 3ab0442..9d8997a 100644 --- a/src/Domain/Setting/Application/CommandHandler/ImportContentSourceCommandHandler.php +++ b/src/Domain/Setting/Application/CommandHandler/ImportContentSourceCommandHandler.php @@ -5,12 +5,11 @@ namespace App\Domain\Setting\Application\CommandHandler; use App\Domain\Setting\Application\Command\ImportContentSourceCommand; use App\Domain\Setting\Domain\Contract\Repository\ContentSourceRepositoryInterface; use App\Domain\Setting\Domain\Model\ContentSource; -use InvalidArgumentException; readonly class ImportContentSourceCommandHandler { public function __construct( - private ContentSourceRepositoryInterface $contentSourceRepository + private ContentSourceRepositoryInterface $contentSourceRepository, ) { } @@ -18,12 +17,12 @@ readonly class ImportContentSourceCommandHandler { // Validation des données d'import if (!is_array($command->contentSources)) { - throw new InvalidArgumentException('Content sources must be an array'); + throw new \InvalidArgumentException('Content sources must be an array'); } foreach ($command->contentSources as $data) { if (!$this->isValidContentSourceData($data)) { - throw new InvalidArgumentException('Invalid content source data provided'); + throw new \InvalidArgumentException('Invalid content source data provided'); } // Recherche d'une source existante avec la même baseUrl diff --git a/src/Domain/Setting/Application/CommandHandler/UpsertContentSourceCommandHandler.php b/src/Domain/Setting/Application/CommandHandler/UpsertContentSourceCommandHandler.php index cb9cac5..f05a243 100644 --- a/src/Domain/Setting/Application/CommandHandler/UpsertContentSourceCommandHandler.php +++ b/src/Domain/Setting/Application/CommandHandler/UpsertContentSourceCommandHandler.php @@ -9,7 +9,7 @@ use App\Domain\Setting\Domain\Model\ContentSource; readonly class UpsertContentSourceCommandHandler { public function __construct( - private ContentSourceRepositoryInterface $contentSourceRepository + private ContentSourceRepositoryInterface $contentSourceRepository, ) { } diff --git a/src/Domain/Setting/Application/Query/GetContentSourceQuery.php b/src/Domain/Setting/Application/Query/GetContentSourceQuery.php index 993cf25..1d1b01d 100644 --- a/src/Domain/Setting/Application/Query/GetContentSourceQuery.php +++ b/src/Domain/Setting/Application/Query/GetContentSourceQuery.php @@ -5,7 +5,7 @@ namespace App\Domain\Setting\Application\Query; readonly class GetContentSourceQuery { public function __construct( - public int $id + public int $id, ) { } } diff --git a/src/Domain/Setting/Application/QueryHandler/ExportContentSourceQueryHandler.php b/src/Domain/Setting/Application/QueryHandler/ExportContentSourceQueryHandler.php index abfd10d..c22c898 100644 --- a/src/Domain/Setting/Application/QueryHandler/ExportContentSourceQueryHandler.php +++ b/src/Domain/Setting/Application/QueryHandler/ExportContentSourceQueryHandler.php @@ -5,12 +5,11 @@ namespace App\Domain\Setting\Application\QueryHandler; use App\Domain\Setting\Application\Query\ExportContentSourceQuery; use App\Domain\Setting\Application\Response\ExportContentSourceResponse; use App\Domain\Setting\Domain\Contract\Repository\ContentSourceRepositoryInterface; -use DateTimeImmutable; readonly class ExportContentSourceQueryHandler { public function __construct( - private ContentSourceRepositoryInterface $contentSourceRepository + private ContentSourceRepositoryInterface $contentSourceRepository, ) { } @@ -31,7 +30,7 @@ readonly class ExportContentSourceQueryHandler return new ExportContentSourceResponse( contentSources: $exportData, - exportDate: (new DateTimeImmutable())->format('Y-m-d H:i:s') + exportDate: (new \DateTimeImmutable())->format('Y-m-d H:i:s') ); } } diff --git a/src/Domain/Setting/Application/QueryHandler/GetContentSourceQueryHandler.php b/src/Domain/Setting/Application/QueryHandler/GetContentSourceQueryHandler.php index 447658f..8e70328 100644 --- a/src/Domain/Setting/Application/QueryHandler/GetContentSourceQueryHandler.php +++ b/src/Domain/Setting/Application/QueryHandler/GetContentSourceQueryHandler.php @@ -10,7 +10,7 @@ use App\Domain\Setting\Domain\Exception\ContentSourceNotFoundException; readonly class GetContentSourceQueryHandler { public function __construct( - private ContentSourceRepositoryInterface $contentSourceRepository + private ContentSourceRepositoryInterface $contentSourceRepository, ) { } diff --git a/src/Domain/Setting/Application/QueryHandler/ListContentSourceQueryHandler.php b/src/Domain/Setting/Application/QueryHandler/ListContentSourceQueryHandler.php index fb0ddee..e250676 100644 --- a/src/Domain/Setting/Application/QueryHandler/ListContentSourceQueryHandler.php +++ b/src/Domain/Setting/Application/QueryHandler/ListContentSourceQueryHandler.php @@ -10,7 +10,7 @@ use App\Domain\Setting\Domain\Contract\Repository\ContentSourceRepositoryInterfa readonly class ListContentSourceQueryHandler { public function __construct( - private ContentSourceRepositoryInterface $contentSourceRepository + private ContentSourceRepositoryInterface $contentSourceRepository, ) { } diff --git a/src/Domain/Setting/Domain/Contract/Repository/ContentSourceRepositoryInterface.php b/src/Domain/Setting/Domain/Contract/Repository/ContentSourceRepositoryInterface.php index f965965..c2db7a2 100644 --- a/src/Domain/Setting/Domain/Contract/Repository/ContentSourceRepositoryInterface.php +++ b/src/Domain/Setting/Domain/Contract/Repository/ContentSourceRepositoryInterface.php @@ -7,11 +7,17 @@ use App\Domain\Setting\Domain\Model\ContentSource; interface ContentSourceRepositoryInterface { public function findAll(): array; + public function findById(int $id): ?ContentSource; + public function findByBaseUrl(string $baseUrl): ?ContentSource; + public function save(ContentSource $contentSource): void; + public function delete(ContentSource $contentSource): void; + public function deleteAll(): void; + /** * @param ContentSource[] $contentSources */ diff --git a/src/Domain/Setting/Domain/Exception/ContentSourceNotFoundException.php b/src/Domain/Setting/Domain/Exception/ContentSourceNotFoundException.php index 8dd4884..00dcce2 100644 --- a/src/Domain/Setting/Domain/Exception/ContentSourceNotFoundException.php +++ b/src/Domain/Setting/Domain/Exception/ContentSourceNotFoundException.php @@ -2,9 +2,7 @@ namespace App\Domain\Setting\Domain\Exception; -use RuntimeException; - -class ContentSourceNotFoundException extends RuntimeException +class ContentSourceNotFoundException extends \RuntimeException { public function __construct(int $id) { diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/DeleteContentSourceResource.php b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/DeleteContentSourceResource.php index ac0094b..4a3daa4 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/DeleteContentSourceResource.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/DeleteContentSourceResource.php @@ -4,6 +4,8 @@ namespace App\Domain\Setting\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Setting\Infrastructure\ApiPlatform\State\Processor\DeleteContentSourceStateProcessor; use App\Domain\Setting\Infrastructure\ApiPlatform\State\Provider\DeleteContentSourceStateProvider; @@ -15,36 +17,34 @@ use App\Domain\Setting\Infrastructure\ApiPlatform\State\Provider\DeleteContentSo provider: DeleteContentSourceStateProvider::class, processor: DeleteContentSourceStateProcessor::class, name: 'delete_content_source', - openapiContext: [ - 'summary' => 'Delete a content source', - 'description' => 'Permanently deletes a content source', - 'parameters' => [ - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'schema' => [ - 'type' => 'integer' - ], - 'description' => 'The content source ID' - ] + openapi: new Operation( + summary: 'Delete a content source', + description: 'Permanently deletes a content source', + parameters: [ + new Parameter( + name: 'id', + in: 'path', + required: true, + schema: ['type' => 'integer'], + description: 'The content source ID' + ), ], - 'responses' => [ + responses: [ '204' => [ - 'description' => 'Content source successfully deleted' + 'description' => 'Content source successfully deleted', ], '404' => [ - 'description' => 'Content source not found' - ] + 'description' => 'Content source not found', + ], ] - ] - ) + ) + ), ] )] class DeleteContentSourceResource { public function __construct( - public int $id + public int $id, ) { } } diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ExportContentSourceResource.php b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ExportContentSourceResource.php index 745f952..93a040c 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ExportContentSourceResource.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ExportContentSourceResource.php @@ -13,7 +13,7 @@ use App\Domain\Setting\Infrastructure\ApiPlatform\State\Provider\ExportContentSo uriTemplate: '/content-sources/export', provider: ExportContentSourceStateProvider::class, description: 'Exporte toutes les sources de contenu au format JSON' - ) + ), ] )] class ExportContentSourceResource diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/GetContentSourceResource.php b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/GetContentSourceResource.php index 639a557..9c9ad4e 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/GetContentSourceResource.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/GetContentSourceResource.php @@ -14,7 +14,7 @@ use Symfony\Component\Validator\Constraints as Assert; uriTemplate: '/content-sources/{id}', provider: GetContentSourceStateProvider::class, description: 'Récupère une source de contenu par son identifiant' - ) + ), ] )] class GetContentSourceResource diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ImportContentSourceResource.php b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ImportContentSourceResource.php index 8c7a77f..f5fdbe1 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ImportContentSourceResource.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ImportContentSourceResource.php @@ -16,7 +16,7 @@ use Symfony\Component\Validator\Constraints as Assert; input: ImportContentSourceResource::class, status: 201, description: 'Importe des sources de contenu depuis un tableau JSON' - ) + ), ] )] class ImportContentSourceResource @@ -25,7 +25,7 @@ class ImportContentSourceResource #[Assert\NotBlank(message: 'Les sources de contenu sont obligatoires')] #[Assert\Type('array', message: 'Les sources de contenu doivent être un tableau')] #[Assert\Count(min: 1, minMessage: 'Au moins une source de contenu doit être fournie')] - public readonly array $contentSources = [] + public readonly array $contentSources = [], ) { } } diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ListContentSourceResource.php b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ListContentSourceResource.php index 57c8e85..03b3d31 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ListContentSourceResource.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/ListContentSourceResource.php @@ -13,7 +13,7 @@ use App\Domain\Setting\Infrastructure\ApiPlatform\State\Provider\ListContentSour uriTemplate: '/content-sources', provider: ListContentSourceStateProvider::class, description: 'Récupère la liste des sources de contenu' - ) + ), ] )] class ListContentSourceResource diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/UpsertContentSourceResource.php b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/UpsertContentSourceResource.php index a147f59..2de8dc9 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/UpsertContentSourceResource.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/Resource/UpsertContentSourceResource.php @@ -16,6 +16,7 @@ use Symfony\Component\Validator\Constraints as Assert; uriTemplate: '/content-sources', processor: UpsertContentSourceStateProcessor::class, input: UpsertContentSourceResource::class, + collectDenormalizationErrors: true, status: 201, description: 'Crée une nouvelle source de contenu' ), @@ -24,8 +25,9 @@ use Symfony\Component\Validator\Constraints as Assert; provider: GetContentSourceStateProvider::class, processor: UpsertContentSourceStateProcessor::class, input: UpsertContentSourceResource::class, + collectDenormalizationErrors: true, description: 'Met à jour une source de contenu existante' - ) + ), ] )] class UpsertContentSourceResource diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/DeleteContentSourceStateProcessor.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/DeleteContentSourceStateProcessor.php index a8acfca..e0fb481 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/DeleteContentSourceStateProcessor.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/DeleteContentSourceStateProcessor.php @@ -13,7 +13,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DeleteContentSourceStateProcessor implements ProcessorInterface { public function __construct( - private DeleteContentSourceCommandHandler $handler + private DeleteContentSourceCommandHandler $handler, ) { } diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/ImportContentSourceStateProcessor.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/ImportContentSourceStateProcessor.php index 2c6bf18..99f521c 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/ImportContentSourceStateProcessor.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/ImportContentSourceStateProcessor.php @@ -9,12 +9,11 @@ use App\Domain\Setting\Application\CommandHandler\ImportContentSourceCommandHand use App\Domain\Setting\Infrastructure\ApiPlatform\Resource\ImportContentSourceResource; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use InvalidArgumentException; readonly class ImportContentSourceStateProcessor implements ProcessorInterface { public function __construct( - private ImportContentSourceCommandHandler $handler + private ImportContentSourceCommandHandler $handler, ) { } @@ -30,7 +29,7 @@ readonly class ImportContentSourceStateProcessor implements ProcessorInterface $this->handler->handle($command); return Response::HTTP_CREATED; - } catch (InvalidArgumentException $e) { + } catch (\InvalidArgumentException $e) { throw new BadRequestHttpException($e->getMessage()); } } diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/UpsertContentSourceStateProcessor.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/UpsertContentSourceStateProcessor.php index 51b62d7..649a19d 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/UpsertContentSourceStateProcessor.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Processor/UpsertContentSourceStateProcessor.php @@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\Response; readonly class UpsertContentSourceStateProcessor implements ProcessorInterface { public function __construct( - private UpsertContentSourceCommandHandler $handler + private UpsertContentSourceCommandHandler $handler, ) { } diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/DeleteContentSourceStateProvider.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/DeleteContentSourceStateProvider.php index ed5cb70..af1be37 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/DeleteContentSourceStateProvider.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/DeleteContentSourceStateProvider.php @@ -12,7 +12,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DeleteContentSourceStateProvider implements ProviderInterface { public function __construct( - private ContentSourceRepositoryInterface $contentSourceRepository + private ContentSourceRepositoryInterface $contentSourceRepository, ) { } diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ExportContentSourceStateProvider.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ExportContentSourceStateProvider.php index 8b4b1e4..6bd9d24 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ExportContentSourceStateProvider.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ExportContentSourceStateProvider.php @@ -10,7 +10,7 @@ use App\Domain\Setting\Application\QueryHandler\ExportContentSourceQueryHandler; readonly class ExportContentSourceStateProvider implements ProviderInterface { public function __construct( - private ExportContentSourceQueryHandler $handler + private ExportContentSourceQueryHandler $handler, ) { } diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/GetContentSourceStateProvider.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/GetContentSourceStateProvider.php index 43d3503..4680296 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/GetContentSourceStateProvider.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/GetContentSourceStateProvider.php @@ -13,7 +13,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class GetContentSourceStateProvider implements ProviderInterface { public function __construct( - private GetContentSourceQueryHandler $handler + private GetContentSourceQueryHandler $handler, ) { } diff --git a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ListContentSourceStateProvider.php b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ListContentSourceStateProvider.php index a95253a..b0d9e4e 100644 --- a/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ListContentSourceStateProvider.php +++ b/src/Domain/Setting/Infrastructure/ApiPlatform/State/Provider/ListContentSourceStateProvider.php @@ -11,7 +11,7 @@ use App\Domain\Setting\Infrastructure\ApiPlatform\Resource\GetContentSourceResou readonly class ListContentSourceStateProvider implements ProviderInterface { public function __construct( - private ListContentSourceQueryHandler $handler + private ListContentSourceQueryHandler $handler, ) { } diff --git a/src/Domain/Setting/Infrastructure/Persistence/Repository/DoctrineContentSourceRepository.php b/src/Domain/Setting/Infrastructure/Persistence/Repository/DoctrineContentSourceRepository.php index 2eb3440..5b68db8 100644 --- a/src/Domain/Setting/Infrastructure/Persistence/Repository/DoctrineContentSourceRepository.php +++ b/src/Domain/Setting/Infrastructure/Persistence/Repository/DoctrineContentSourceRepository.php @@ -12,7 +12,7 @@ readonly class DoctrineContentSourceRepository implements ContentSourceRepositor { public function __construct( private EntityManagerInterface $entityManager, - private ContentSourceMapper $mapper + private ContentSourceMapper $mapper, ) { } @@ -77,7 +77,7 @@ readonly class DoctrineContentSourceRepository implements ContentSourceRepositor public function deleteAll(): void { - $this->entityManager->createQuery('DELETE FROM ' . ContentSourceEntity::class)->execute(); + $this->entityManager->createQuery('DELETE FROM '.ContentSourceEntity::class)->execute(); } public function saveMultiple(array $contentSources): void diff --git a/src/Domain/Shared/Application/Command/DeleteJob.php b/src/Domain/Shared/Application/Command/DeleteJob.php index cfe46d8..017e4e9 100644 --- a/src/Domain/Shared/Application/Command/DeleteJob.php +++ b/src/Domain/Shared/Application/Command/DeleteJob.php @@ -9,7 +9,7 @@ use App\Domain\Shared\Domain\Contract\CommandInterface; readonly class DeleteJob implements CommandInterface { public function __construct( - public string $id + public string $id, ) { } } diff --git a/src/Domain/Shared/Application/CommandHandler/DeleteJobHandler.php b/src/Domain/Shared/Application/CommandHandler/DeleteJobHandler.php index 5ff69a0..fd21c13 100644 --- a/src/Domain/Shared/Application/CommandHandler/DeleteJobHandler.php +++ b/src/Domain/Shared/Application/CommandHandler/DeleteJobHandler.php @@ -12,18 +12,14 @@ use App\Domain\Shared\Domain\Contract\JobRepositoryInterface; readonly class DeleteJobHandler implements CommandHandlerInterface { public function __construct( - private JobRepositoryInterface $jobRepository + private JobRepositoryInterface $jobRepository, ) { } public function handle(CommandInterface $command): void { if (!$command instanceof DeleteJob) { - throw new \InvalidArgumentException(sprintf( - 'Command must be instance of %s, %s given', - DeleteJob::class, - get_class($command) - )); + throw new \InvalidArgumentException(sprintf('Command must be instance of %s, %s given', DeleteJob::class, get_class($command))); } $this->jobRepository->get($command->id); diff --git a/src/Domain/Shared/Application/CommandHandler/DeleteJobsByCriteriaHandler.php b/src/Domain/Shared/Application/CommandHandler/DeleteJobsByCriteriaHandler.php index 1162547..673a311 100644 --- a/src/Domain/Shared/Application/CommandHandler/DeleteJobsByCriteriaHandler.php +++ b/src/Domain/Shared/Application/CommandHandler/DeleteJobsByCriteriaHandler.php @@ -12,18 +12,14 @@ use App\Domain\Shared\Domain\Contract\JobRepositoryInterface; readonly class DeleteJobsByCriteriaHandler implements CommandHandlerInterface { public function __construct( - private JobRepositoryInterface $jobRepository + private JobRepositoryInterface $jobRepository, ) { } public function handle(CommandInterface $command): void { if (!$command instanceof DeleteJobsByCriteria) { - throw new \InvalidArgumentException(sprintf( - 'Command must be instance of %s, %s given', - DeleteJobsByCriteria::class, - get_class($command) - )); + throw new \InvalidArgumentException(sprintf('Command must be instance of %s, %s given', DeleteJobsByCriteria::class, get_class($command))); } $criteria = []; diff --git a/src/Domain/Shared/Application/Query/GetJobById.php b/src/Domain/Shared/Application/Query/GetJobById.php index 19fa20f..d814cb6 100644 --- a/src/Domain/Shared/Application/Query/GetJobById.php +++ b/src/Domain/Shared/Application/Query/GetJobById.php @@ -9,7 +9,7 @@ use App\Domain\Shared\Domain\Contract\QueryInterface; readonly class GetJobById implements QueryInterface { public function __construct( - public string $id + public string $id, ) { } } diff --git a/src/Domain/Shared/Application/Query/ListJobsQuery.php b/src/Domain/Shared/Application/Query/ListJobsQuery.php index 80fda89..fbf89c3 100644 --- a/src/Domain/Shared/Application/Query/ListJobsQuery.php +++ b/src/Domain/Shared/Application/Query/ListJobsQuery.php @@ -20,7 +20,7 @@ readonly class ListJobsQuery implements QueryInterface public ?int $page = 1, public ?int $limit = 20, public ?string $sortBy = 'createdAt', - public ?string $sortOrder = 'DESC' + public ?string $sortOrder = 'DESC', ) { if ($this->page < 1) { throw new \InvalidArgumentException('Page must be greater than 0'); diff --git a/src/Domain/Shared/Application/QueryHandler/GetJobByIdHandler.php b/src/Domain/Shared/Application/QueryHandler/GetJobByIdHandler.php index 73c2c0f..5e76c89 100644 --- a/src/Domain/Shared/Application/QueryHandler/GetJobByIdHandler.php +++ b/src/Domain/Shared/Application/QueryHandler/GetJobByIdHandler.php @@ -6,26 +6,22 @@ namespace App\Domain\Shared\Application\QueryHandler; use App\Domain\Shared\Application\Query\GetJobById; use App\Domain\Shared\Application\Response\JobResponse; +use App\Domain\Shared\Domain\Contract\JobRepositoryInterface; use App\Domain\Shared\Domain\Contract\QueryHandlerInterface; use App\Domain\Shared\Domain\Contract\QueryInterface; use App\Domain\Shared\Domain\Contract\ResponseInterface; -use App\Domain\Shared\Domain\Contract\JobRepositoryInterface; readonly class GetJobByIdHandler implements QueryHandlerInterface { public function __construct( - private JobRepositoryInterface $jobRepository + private JobRepositoryInterface $jobRepository, ) { } public function handle(QueryInterface $query): ResponseInterface { if (!$query instanceof GetJobById) { - throw new \InvalidArgumentException(sprintf( - 'Query must be instance of %s, %s given', - GetJobById::class, - get_class($query) - )); + throw new \InvalidArgumentException(sprintf('Query must be instance of %s, %s given', GetJobById::class, get_class($query))); } $job = $this->jobRepository->get($query->id); diff --git a/src/Domain/Shared/Application/QueryHandler/ListJobsQueryHandler.php b/src/Domain/Shared/Application/QueryHandler/ListJobsQueryHandler.php index 29eaadf..63d7349 100644 --- a/src/Domain/Shared/Application/QueryHandler/ListJobsQueryHandler.php +++ b/src/Domain/Shared/Application/QueryHandler/ListJobsQueryHandler.php @@ -6,26 +6,22 @@ namespace App\Domain\Shared\Application\QueryHandler; use App\Domain\Shared\Application\Query\ListJobsQuery; use App\Domain\Shared\Application\Response\JobListResponse; +use App\Domain\Shared\Domain\Contract\JobRepositoryInterface; use App\Domain\Shared\Domain\Contract\QueryHandlerInterface; use App\Domain\Shared\Domain\Contract\QueryInterface; use App\Domain\Shared\Domain\Contract\ResponseInterface; -use App\Domain\Shared\Domain\Contract\JobRepositoryInterface; readonly class ListJobsQueryHandler implements QueryHandlerInterface { public function __construct( - private JobRepositoryInterface $jobRepository + private JobRepositoryInterface $jobRepository, ) { } public function handle(QueryInterface $query): ResponseInterface { if (!$query instanceof ListJobsQuery) { - throw new \InvalidArgumentException(sprintf( - 'Query must be instance of %s, %s given', - ListJobsQuery::class, - get_class($query) - )); + throw new \InvalidArgumentException(sprintf('Query must be instance of %s, %s given', ListJobsQuery::class, get_class($query))); } $criteria = [ @@ -36,7 +32,7 @@ readonly class ListJobsQueryHandler implements QueryHandlerInterface 'sortBy' => $query->sortBy, 'sortOrder' => $query->sortOrder, 'offset' => $query->getOffset(), - 'limit' => $query->limit + 'limit' => $query->limit, ]; $jobs = $this->jobRepository->findByCriteria($criteria); diff --git a/src/Domain/Shared/Application/Response/JobListResponse.php b/src/Domain/Shared/Application/Response/JobListResponse.php index a18ed84..4f79a0f 100644 --- a/src/Domain/Shared/Application/Response/JobListResponse.php +++ b/src/Domain/Shared/Application/Response/JobListResponse.php @@ -17,7 +17,7 @@ readonly class JobListResponse implements ResponseInterface public int $total, public int $page, public int $limit, - public int $pages + public int $pages, ) { } diff --git a/src/Domain/Shared/Application/Response/JobResponse.php b/src/Domain/Shared/Application/Response/JobResponse.php index 4d51006..51c7476 100644 --- a/src/Domain/Shared/Application/Response/JobResponse.php +++ b/src/Domain/Shared/Application/Response/JobResponse.php @@ -19,7 +19,7 @@ readonly class JobResponse implements ResponseInterface public ?string $failureReason, public int $attempts, public int $maxAttempts, - public array $context + public array $context, ) { } diff --git a/src/Domain/Shared/Domain/Contract/CommandHandlerInterface.php b/src/Domain/Shared/Domain/Contract/CommandHandlerInterface.php index fb0d0c8..411fa1b 100644 --- a/src/Domain/Shared/Domain/Contract/CommandHandlerInterface.php +++ b/src/Domain/Shared/Domain/Contract/CommandHandlerInterface.php @@ -8,8 +8,8 @@ interface CommandHandlerInterface { /** * @template T of CommandInterface + * * @param T $command - * @return void */ public function handle(CommandInterface $command): void; } diff --git a/src/Domain/Shared/Domain/Contract/FailedJobRepositoryInterface.php b/src/Domain/Shared/Domain/Contract/FailedJobRepositoryInterface.php index 4789f43..1cf9850 100644 --- a/src/Domain/Shared/Domain/Contract/FailedJobRepositoryInterface.php +++ b/src/Domain/Shared/Domain/Contract/FailedJobRepositoryInterface.php @@ -7,9 +7,14 @@ use App\Domain\Shared\Domain\Model\FailedJob; interface FailedJobRepositoryInterface { public function save(FailedJob $failedJob): void; + public function get(string $id): ?FailedJob; + public function delete(string $id): void; + public function findAll(): array; + public function findByJobType(string $jobType): array; + public function findRetryableJobs(): array; } diff --git a/src/Domain/Shared/Domain/Contract/FileUploadInterface.php b/src/Domain/Shared/Domain/Contract/FileUploadInterface.php index 917d077..4f34ebb 100644 --- a/src/Domain/Shared/Domain/Contract/FileUploadInterface.php +++ b/src/Domain/Shared/Domain/Contract/FileUploadInterface.php @@ -4,37 +4,35 @@ declare(strict_types=1); namespace App\Domain\Shared\Domain\Contract; -use App\Domain\Shared\Domain\Model\FileUpload; - interface FileUploadInterface { /** - * Déplace un fichier uploadé vers un répertoire temporaire + * Déplace un fichier uploadé vers un répertoire temporaire. */ public function moveUploadedFile(string $sourcePath, string $targetDirectory, string $originalName): string; /** - * Vérifie si un fichier existe + * Vérifie si un fichier existe. */ public function fileExists(string $filePath): bool; /** - * Supprime un fichier + * Supprime un fichier. */ public function deleteFile(string $filePath): void; /** - * Déplace un fichier d'un emplacement à un autre + * Déplace un fichier d'un emplacement à un autre. */ public function moveFile(string $sourcePath, string $targetPath): void; /** - * Crée un répertoire s'il n'existe pas + * Crée un répertoire s'il n'existe pas. */ public function createDirectory(string $path): void; /** - * Valide le format d'un fichier + * Valide le format d'un fichier. */ public function validateFileFormat(string $filePath, array $allowedExtensions): bool; } diff --git a/src/Domain/Shared/Domain/Contract/ImageStorageInterface.php b/src/Domain/Shared/Domain/Contract/ImageStorageInterface.php index 715294a..fb29483 100644 --- a/src/Domain/Shared/Domain/Contract/ImageStorageInterface.php +++ b/src/Domain/Shared/Domain/Contract/ImageStorageInterface.php @@ -8,7 +8,8 @@ interface ImageStorageInterface * Store images from local file paths into the individual images storage. * Used by the scraping flow. * - * @param string[] $localImagePaths + * @param string[] $localImagePaths + * * @return string The directory path where images are stored (pagesDirectory) */ public function storeChapterImages(string $targetId, array $localImagePaths): string; diff --git a/src/Domain/Shared/Domain/Contract/JobRepositoryInterface.php b/src/Domain/Shared/Domain/Contract/JobRepositoryInterface.php index dcff019..e43f380 100644 --- a/src/Domain/Shared/Domain/Contract/JobRepositoryInterface.php +++ b/src/Domain/Shared/Domain/Contract/JobRepositoryInterface.php @@ -8,11 +8,17 @@ use App\Domain\Shared\Domain\Model\JobStatus; interface JobRepositoryInterface { public function save(Job $job): void; + public function get(string $id): ?Job; + public function findByStatus(JobStatus $status): array; + public function findByType(string $type): array; + public function findPendingJobs(): array; + public function findInProgressJobs(): array; + public function findFailedJobs(): array; /** @@ -27,6 +33,7 @@ interface JobRepositoryInterface * offset?: int, * limit?: int * } $criteria + * * @return Job[] */ public function findByCriteria(array $criteria): array; @@ -51,6 +58,7 @@ interface JobRepositoryInterface * createdAfter?: ?\DateTimeImmutable, * createdBefore?: ?\DateTimeImmutable * } $criteria + * * @return int Nombre de jobs supprimés */ public function deleteByCriteria(array $criteria): int; diff --git a/src/Domain/Shared/Domain/Contract/MetadataExtractorInterface.php b/src/Domain/Shared/Domain/Contract/MetadataExtractorInterface.php index fc2ef43..6d553d0 100644 --- a/src/Domain/Shared/Domain/Contract/MetadataExtractorInterface.php +++ b/src/Domain/Shared/Domain/Contract/MetadataExtractorInterface.php @@ -9,12 +9,12 @@ use App\Domain\Shared\Domain\Model\FileMetadata; interface MetadataExtractorInterface { /** - * Extrait les métadonnées d'un fichier + * Extrait les métadonnées d'un fichier. */ public function extractMetadata(string $filePath, string $originalFileName): FileMetadata; /** - * Vérifie si le fichier peut être traité par cet extracteur + * Vérifie si le fichier peut être traité par cet extracteur. */ public function canHandle(string $filePath): bool; } diff --git a/src/Domain/Shared/Domain/Contract/QueryHandlerInterface.php b/src/Domain/Shared/Domain/Contract/QueryHandlerInterface.php index 1b2909e..ee941de 100644 --- a/src/Domain/Shared/Domain/Contract/QueryHandlerInterface.php +++ b/src/Domain/Shared/Domain/Contract/QueryHandlerInterface.php @@ -9,7 +9,9 @@ interface QueryHandlerInterface /** * @template T of QueryInterface * @template R of ResponseInterface + * * @param T $query + * * @return R */ public function handle(QueryInterface $query): ResponseInterface; diff --git a/src/Domain/Shared/Domain/Model/FailedJob.php b/src/Domain/Shared/Domain/Model/FailedJob.php index e5377cc..6eda5b7 100644 --- a/src/Domain/Shared/Domain/Model/FailedJob.php +++ b/src/Domain/Shared/Domain/Model/FailedJob.php @@ -11,7 +11,7 @@ class FailedJob public readonly string $failureReason, public readonly array $context, public readonly \DateTimeImmutable $failedAt, - public readonly int $attempt + public readonly int $attempt, ) { } diff --git a/src/Domain/Shared/Domain/Model/FileMetadata.php b/src/Domain/Shared/Domain/Model/FileMetadata.php index 465fd4e..4ceb0b4 100644 --- a/src/Domain/Shared/Domain/Model/FileMetadata.php +++ b/src/Domain/Shared/Domain/Model/FileMetadata.php @@ -12,7 +12,7 @@ readonly class FileMetadata public ?int $chapter = null, public ?string $author = null, public ?string $description = null, - public array $additionalData = [] + public array $additionalData = [], ) { } diff --git a/src/Domain/Shared/Domain/Model/FileUpload.php b/src/Domain/Shared/Domain/Model/FileUpload.php index f15325b..44233f6 100644 --- a/src/Domain/Shared/Domain/Model/FileUpload.php +++ b/src/Domain/Shared/Domain/Model/FileUpload.php @@ -12,14 +12,14 @@ readonly class FileUpload public string $path, public string $extension, public int $size, - public \DateTimeImmutable $uploadedAt + public \DateTimeImmutable $uploadedAt, ) { } public static function create( string $originalName, string $path, - int $size + int $size, ): self { return new self( id: uniqid('file_', true), @@ -44,6 +44,6 @@ readonly class FileUpload $pow = min($pow, count($units) - 1); $bytes /= (1 << (10 * $pow)); - return round($bytes, $precision) . ' ' . $units[$pow]; + return round($bytes, $precision).' '.$units[$pow]; } } diff --git a/src/Domain/Shared/Domain/Model/Job.php b/src/Domain/Shared/Domain/Model/Job.php index 8d5a377..bcac608 100644 --- a/src/Domain/Shared/Domain/Model/Job.php +++ b/src/Domain/Shared/Domain/Model/Job.php @@ -15,7 +15,7 @@ abstract class Job public function __construct( public readonly string $id, - public readonly string $type + public readonly string $type, ) { $this->status = JobStatus::PENDING; $this->createdAt = new \DateTimeImmutable(); @@ -25,7 +25,7 @@ abstract class Job { $this->status = JobStatus::IN_PROGRESS; $this->startedAt = new \DateTimeImmutable(); - $this->attempts++; + ++$this->attempts; } public function complete(): void @@ -51,6 +51,6 @@ abstract class Job public function canBeRetried(): bool { - return $this->status === JobStatus::FAILED && $this->attempts < $this->maxAttempts; + return JobStatus::FAILED === $this->status && $this->attempts < $this->maxAttempts; } } diff --git a/src/Domain/Shared/Infrastructure/ApiPlatform/Resource/GetJobListResource.php b/src/Domain/Shared/Infrastructure/ApiPlatform/Resource/GetJobListResource.php index e24ea57..1f33288 100644 --- a/src/Domain/Shared/Infrastructure/ApiPlatform/Resource/GetJobListResource.php +++ b/src/Domain/Shared/Infrastructure/ApiPlatform/Resource/GetJobListResource.php @@ -4,14 +4,14 @@ declare(strict_types=1); namespace App\Domain\Shared\Infrastructure\ApiPlatform\Resource; -use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Parameter; use App\Domain\Shared\Application\Response\JobResponse; -use App\Domain\Shared\Domain\Model\JobStatus; use App\Domain\Shared\Infrastructure\ApiPlatform\State\Processor\DeleteJobProcessor; use App\Domain\Shared\Infrastructure\ApiPlatform\State\Processor\DeleteJobsByCriteriaProcessor; use App\Domain\Shared\Infrastructure\ApiPlatform\State\Provider\DeleteJobProvider; @@ -27,91 +27,83 @@ use Symfony\Component\Validator\Constraints as Assert; provider: GetJobListStateProvider::class, output: GetJobListResource::class, description: 'Liste des jobs', - openapiContext: [ - 'parameters' => [ - [ - 'name' => 'status', - 'in' => 'query', - 'description' => 'Filtrer par status (séparés par des virgules pour plusieurs statuts, ex: "pending,in_progress" ou sous forme de tableau avec "status[]=")', - 'required' => false, - 'schema' => [ + openapi: new Operation( + parameters: [ + new Parameter( + name: 'status', + in: 'query', + description: 'Filtrer par status (séparés par des virgules pour plusieurs statuts, ex: "pending,in_progress" ou sous forme de tableau avec "status[]=")', + required: false, + schema: [ 'oneOf' => [ [ 'type' => 'string', - 'example' => 'pending,in_progress' + 'example' => 'pending,in_progress', ], [ 'type' => 'array', 'items' => [ 'type' => 'string', - 'enum' => ['pending', 'in_progress', 'completed', 'failed', 'cancelled'] + 'enum' => ['pending', 'in_progress', 'completed', 'failed', 'cancelled'], ], - 'example' => ['pending', 'in_progress'] - ] - ] + 'example' => ['pending', 'in_progress'], + ], + ], ], - 'style' => 'form', - 'explode' => false - ], - [ - 'name' => 'type', - 'in' => 'query', - 'description' => 'Filtrer par type de job (ex: scraping_job)', - 'required' => false, - 'schema' => ['type' => 'string'] - ], - [ - 'name' => 'createdAfter', - 'in' => 'query', - 'description' => 'Date de création minimum (format ISO8601)', - 'required' => false, - 'schema' => ['type' => 'string', 'format' => 'date-time'] - ], - [ - 'name' => 'createdBefore', - 'in' => 'query', - 'description' => 'Date de création maximum (format ISO8601)', - 'required' => false, - 'schema' => ['type' => 'string', 'format' => 'date-time'] - ], - [ - 'name' => 'page', - 'in' => 'query', - 'description' => 'Numéro de la page', - 'required' => false, - 'schema' => ['type' => 'integer', 'default' => 1, 'minimum' => 1] - ], - [ - 'name' => 'limit', - 'in' => 'query', - 'description' => 'Nombre d\'éléments par page', - 'required' => false, - 'schema' => ['type' => 'integer', 'default' => 20, 'minimum' => 1] - ], - [ - 'name' => 'sortBy', - 'in' => 'query', - 'description' => 'Champ de tri', - 'required' => false, - 'schema' => [ - 'type' => 'string', - 'enum' => ['createdAt', 'type', 'status'], - 'default' => 'createdAt' - ] - ], - [ - 'name' => 'sortOrder', - 'in' => 'query', - 'description' => 'Ordre de tri', - 'required' => false, - 'schema' => [ - 'type' => 'string', - 'enum' => ['ASC', 'DESC'], - 'default' => 'DESC' - ] - ] + style: 'form', + explode: false + ), + new Parameter( + name: 'type', + in: 'query', + description: 'Filtrer par type de job (ex: scraping_job)', + required: false, + schema: ['type' => 'string'] + ), + new Parameter( + name: 'createdAfter', + in: 'query', + description: 'Date de création minimum (format ISO8601)', + required: false, + schema: ['type' => 'string', 'format' => 'date-time'] + ), + new Parameter( + name: 'createdBefore', + in: 'query', + description: 'Date de création maximum (format ISO8601)', + required: false, + schema: ['type' => 'string', 'format' => 'date-time'] + ), + new Parameter( + name: 'page', + in: 'query', + description: 'Numéro de la page', + required: false, + schema: ['type' => 'integer', 'default' => 1, 'minimum' => 1] + ), + new Parameter( + name: 'limit', + in: 'query', + description: 'Nombre d\'éléments par page', + required: false, + schema: ['type' => 'integer', 'default' => 20, 'minimum' => 1] + ), + new Parameter( + name: 'sortBy', + in: 'query', + description: 'Champ de tri', + required: false, + schema: ['type' => 'string', 'enum' => ['createdAt', 'type', 'status'], 'default' => 'createdAt'] + ), + new Parameter( + name: 'sortOrder', + in: 'query', + description: 'Ordre de tri', + required: false, + schema: ['type' => 'string', 'enum' => ['ASC', 'DESC'], 'default' => 'DESC'] + ), ] - ] + ) ), new Get( uriTemplate: '/jobs/{id}', @@ -129,38 +121,38 @@ use Symfony\Component\Validator\Constraints as Assert; uriTemplate: '/jobs', processor: DeleteJobsByCriteriaProcessor::class, description: 'Supprime les jobs correspondant aux critères', - openapiContext: [ - 'parameters' => [ - [ - 'name' => 'status', - 'in' => 'query', - 'description' => 'Filtrer par statut(s) (virgule-séparés ou tableau)', - 'required' => false, - 'schema' => ['type' => 'string'], - ], - [ - 'name' => 'type', - 'in' => 'query', - 'description' => 'Filtrer par type de job', - 'required' => false, - 'schema' => ['type' => 'string'], - ], - [ - 'name' => 'createdAfter', - 'in' => 'query', - 'description' => 'Date de création minimum (ISO8601)', - 'required' => false, - 'schema' => ['type' => 'string', 'format' => 'date-time'], - ], - [ - 'name' => 'createdBefore', - 'in' => 'query', - 'description' => 'Date de création maximum (ISO8601)', - 'required' => false, - 'schema' => ['type' => 'string', 'format' => 'date-time'], - ], + openapi: new Operation( + parameters: [ + new Parameter( + name: 'status', + in: 'query', + description: 'Filtrer par statut(s) (virgule-séparés ou tableau)', + required: false, + schema: ['type' => 'string'] + ), + new Parameter( + name: 'type', + in: 'query', + description: 'Filtrer par type de job', + required: false, + schema: ['type' => 'string'] + ), + new Parameter( + name: 'createdAfter', + in: 'query', + description: 'Date de création minimum (ISO8601)', + required: false, + schema: ['type' => 'string', 'format' => 'date-time'] + ), + new Parameter( + name: 'createdBefore', + in: 'query', + description: 'Date de création maximum (ISO8601)', + required: false, + schema: ['type' => 'string', 'format' => 'date-time'] + ), ] - ] + ) ), ] )] @@ -194,7 +186,7 @@ class GetJobListResource #[Assert\GreaterThan(0)] public readonly int $maxAttempts = 3, #[ApiProperty(description: 'Données contextuelles du job')] - public readonly array $context = [] + public readonly array $context = [], ) { } diff --git a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Processor/DeleteJobProcessor.php b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Processor/DeleteJobProcessor.php index 0a56fc9..892ac1a 100644 --- a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Processor/DeleteJobProcessor.php +++ b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Processor/DeleteJobProcessor.php @@ -14,7 +14,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DeleteJobProcessor implements ProcessorInterface { public function __construct( - private DeleteJobHandler $handler + private DeleteJobHandler $handler, ) { } diff --git a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Processor/DeleteJobsByCriteriaProcessor.php b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Processor/DeleteJobsByCriteriaProcessor.php index f8e348d..85c8f39 100644 --- a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Processor/DeleteJobsByCriteriaProcessor.php +++ b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Processor/DeleteJobsByCriteriaProcessor.php @@ -15,7 +15,7 @@ readonly class DeleteJobsByCriteriaProcessor implements ProcessorInterface { public function __construct( private DeleteJobsByCriteriaHandler $handler, - private RequestStack $requestStack + private RequestStack $requestStack, ) { } diff --git a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/DeleteJobProvider.php b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/DeleteJobProvider.php index 048c330..0948d87 100644 --- a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/DeleteJobProvider.php +++ b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/DeleteJobProvider.php @@ -15,7 +15,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class DeleteJobProvider implements ProviderInterface { public function __construct( - private GetJobByIdHandler $handler + private GetJobByIdHandler $handler, ) { } diff --git a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobListStateProvider.php b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobListStateProvider.php index 403818c..d55ff07 100644 --- a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobListStateProvider.php +++ b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobListStateProvider.php @@ -5,17 +5,17 @@ declare(strict_types=1); namespace App\Domain\Shared\Infrastructure\ApiPlatform\State\Provider; use ApiPlatform\Metadata\Operation; +use ApiPlatform\State\Pagination\ArrayPaginator; use ApiPlatform\State\ProviderInterface; use App\Domain\Shared\Application\Query\ListJobsQuery; use App\Domain\Shared\Application\QueryHandler\ListJobsQueryHandler; use App\Domain\Shared\Domain\Model\JobStatus; use App\Domain\Shared\Infrastructure\ApiPlatform\Resource\GetJobListResource; -use ApiPlatform\State\Pagination\ArrayPaginator; readonly class GetJobListStateProvider implements ProviderInterface { public function __construct( - private ListJobsQueryHandler $handler + private ListJobsQueryHandler $handler, ) { } diff --git a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobStateProvider.php b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobStateProvider.php index c5e3ad9..e2a50a9 100644 --- a/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobStateProvider.php +++ b/src/Domain/Shared/Infrastructure/ApiPlatform/State/Provider/GetJobStateProvider.php @@ -15,7 +15,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; readonly class GetJobStateProvider implements ProviderInterface { public function __construct( - private GetJobByIdHandler $handler + private GetJobByIdHandler $handler, ) { } diff --git a/src/Domain/Shared/Infrastructure/Persistence/Entity/FailedJobEntity.php b/src/Domain/Shared/Infrastructure/Persistence/Entity/FailedJobEntity.php index 6d00bbc..7a1fbd5 100644 --- a/src/Domain/Shared/Infrastructure/Persistence/Entity/FailedJobEntity.php +++ b/src/Domain/Shared/Infrastructure/Persistence/Entity/FailedJobEntity.php @@ -32,6 +32,7 @@ class FailedJobEntity public function setId(string $id): self { $this->id = $id; + return $this; } @@ -43,6 +44,7 @@ class FailedJobEntity public function setType(string $type): self { $this->type = $type; + return $this; } @@ -54,6 +56,7 @@ class FailedJobEntity public function setFailureReason(string $failureReason): self { $this->failureReason = $failureReason; + return $this; } @@ -65,6 +68,7 @@ class FailedJobEntity public function setFailedAt(\DateTimeImmutable $failedAt): self { $this->failedAt = $failedAt; + return $this; } @@ -76,6 +80,7 @@ class FailedJobEntity public function setContext(array $context): self { $this->context = $context; + return $this; } } diff --git a/src/Domain/Shared/Infrastructure/Persistence/Entity/JobEntity.php b/src/Domain/Shared/Infrastructure/Persistence/Entity/JobEntity.php index e2b8682..f9107bb 100644 --- a/src/Domain/Shared/Infrastructure/Persistence/Entity/JobEntity.php +++ b/src/Domain/Shared/Infrastructure/Persistence/Entity/JobEntity.php @@ -47,6 +47,7 @@ class JobEntity public function setId(string $id): self { $this->id = $id; + return $this; } @@ -58,6 +59,7 @@ class JobEntity public function setType(string $type): self { $this->type = $type; + return $this; } @@ -69,6 +71,7 @@ class JobEntity public function setStatus(string $status): self { $this->status = $status; + return $this; } @@ -80,6 +83,7 @@ class JobEntity public function setCreatedAt(\DateTimeImmutable $createdAt): self { $this->createdAt = $createdAt; + return $this; } @@ -91,6 +95,7 @@ class JobEntity public function setStartedAt(?\DateTimeImmutable $startedAt): self { $this->startedAt = $startedAt; + return $this; } @@ -102,6 +107,7 @@ class JobEntity public function setCompletedAt(?\DateTimeImmutable $completedAt): self { $this->completedAt = $completedAt; + return $this; } @@ -113,6 +119,7 @@ class JobEntity public function setFailureReason(?string $failureReason): self { $this->failureReason = $failureReason; + return $this; } @@ -124,6 +131,7 @@ class JobEntity public function setAttempts(int $attempts): self { $this->attempts = $attempts; + return $this; } @@ -135,6 +143,7 @@ class JobEntity public function setMaxAttempts(int $maxAttempts): self { $this->maxAttempts = $maxAttempts; + return $this; } @@ -146,6 +155,7 @@ class JobEntity public function setContext(array $context): self { $this->context = $context; + return $this; } } diff --git a/src/Domain/Shared/Infrastructure/Persistence/Mapper/JobMapper.php b/src/Domain/Shared/Infrastructure/Persistence/Mapper/JobMapper.php index 1babb10..fe76e71 100644 --- a/src/Domain/Shared/Infrastructure/Persistence/Mapper/JobMapper.php +++ b/src/Domain/Shared/Infrastructure/Persistence/Mapper/JobMapper.php @@ -28,14 +28,14 @@ readonly class JobMapper public function toDomain(JobEntity $entity): Job { - $job = match($entity->getType()) { + $job = match ($entity->getType()) { 'scraping_job' => new ScrapingJob( $entity->getId(), $entity->getContext()['mangaId'], $entity->getContext()['chapterNumber'], $entity->getContext()['sourceId'] ), - default => throw new \RuntimeException(sprintf('Unknown job type: %s', $entity->getType())) + default => throw new \RuntimeException(sprintf('Unknown job type: %s', $entity->getType())), }; $job->status = JobStatus::from($entity->getStatus()); diff --git a/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineFailedJobRepository.php b/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineFailedJobRepository.php index 7caf1c7..49261aa 100644 --- a/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineFailedJobRepository.php +++ b/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineFailedJobRepository.php @@ -4,7 +4,6 @@ namespace App\Domain\Shared\Infrastructure\Persistence\Repository; use App\Domain\Shared\Domain\Contract\FailedJobRepositoryInterface; use App\Domain\Shared\Domain\Model\FailedJob; -use App\Domain\Shared\Domain\Model\Job; use App\Domain\Shared\Infrastructure\Persistence\Entity\FailedJobEntity; use App\Domain\Shared\Infrastructure\Persistence\Mapper\FailedJobMapper; use Doctrine\ORM\EntityManagerInterface; @@ -13,7 +12,7 @@ readonly class DoctrineFailedJobRepository implements FailedJobRepositoryInterfa { public function __construct( private EntityManagerInterface $entityManager, - private FailedJobMapper $mapper + private FailedJobMapper $mapper, ) { } diff --git a/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineJobRepository.php b/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineJobRepository.php index f875340..e67e405 100644 --- a/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineJobRepository.php +++ b/src/Domain/Shared/Infrastructure/Persistence/Repository/DoctrineJobRepository.php @@ -14,7 +14,7 @@ readonly class DoctrineJobRepository implements JobRepositoryInterface { public function __construct( private EntityManagerInterface $entityManager, - private JobMapper $mapper + private JobMapper $mapper, ) { } @@ -110,8 +110,8 @@ readonly class DoctrineJobRepository implements JobRepositoryInterface if (isset($criteria['statuses']) && is_array($criteria['statuses']) && !empty($criteria['statuses'])) { $expr = $qb->expr()->orX(); foreach ($criteria['statuses'] as $key => $status) { - $paramName = 'status' . $key; - $expr->add($qb->expr()->eq('j.status', ':' . $paramName)); + $paramName = 'status'.$key; + $expr->add($qb->expr()->eq('j.status', ':'.$paramName)); $qb->setParameter($paramName, $status->value); } $qb->andWhere($expr); @@ -136,7 +136,7 @@ readonly class DoctrineJobRepository implements JobRepositoryInterface } if (isset($criteria['sortBy'])) { - $qb->orderBy('j.' . $criteria['sortBy'], $criteria['sortOrder'] ?? 'ASC'); + $qb->orderBy('j.'.$criteria['sortBy'], $criteria['sortOrder'] ?? 'ASC'); } if (isset($criteria['offset'])) { @@ -161,8 +161,8 @@ readonly class DoctrineJobRepository implements JobRepositoryInterface if (isset($criteria['statuses']) && is_array($criteria['statuses']) && !empty($criteria['statuses'])) { $expr = $qb->expr()->orX(); foreach ($criteria['statuses'] as $key => $status) { - $paramName = 'status' . $key; - $expr->add($qb->expr()->eq('j.status', ':' . $paramName)); + $paramName = 'status'.$key; + $expr->add($qb->expr()->eq('j.status', ':'.$paramName)); $qb->setParameter($paramName, $status->value); } $qb->andWhere($expr); @@ -209,8 +209,8 @@ readonly class DoctrineJobRepository implements JobRepositoryInterface if (isset($criteria['statuses']) && is_array($criteria['statuses']) && !empty($criteria['statuses'])) { $expr = $qb->expr()->orX(); foreach ($criteria['statuses'] as $key => $status) { - $paramName = 'status' . $key; - $expr->add($qb->expr()->eq('j.status', ':' . $paramName)); + $paramName = 'status'.$key; + $expr->add($qb->expr()->eq('j.status', ':'.$paramName)); $qb->setParameter($paramName, $status->value); } $qb->andWhere($expr); diff --git a/src/Domain/Shared/Infrastructure/Service/ImageStorageManager.php b/src/Domain/Shared/Infrastructure/Service/ImageStorageManager.php index b84860a..408aa05 100644 --- a/src/Domain/Shared/Infrastructure/Service/ImageStorageManager.php +++ b/src/Domain/Shared/Infrastructure/Service/ImageStorageManager.php @@ -3,7 +3,6 @@ namespace App\Domain\Shared\Infrastructure\Service; use App\Domain\Shared\Domain\Contract\ImageStorageInterface; -use ZipArchive; class ImageStorageManager implements ImageStorageInterface { @@ -13,7 +12,7 @@ class ImageStorageManager implements ImageStorageInterface public function storeChapterImages(string $targetId, array $localImagePaths): string { - $targetDir = $this->storagePath . '/pages/' . $targetId; + $targetDir = $this->storagePath.'/pages/'.$targetId; if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); @@ -32,7 +31,7 @@ class ImageStorageManager implements ImageStorageInterface public function extractFromCbz(string $targetId, string $cbzBinary): string { - $targetDir = $this->storagePath . '/pages/' . $targetId; + $targetDir = $this->storagePath.'/pages/'.$targetId; if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); @@ -41,14 +40,14 @@ class ImageStorageManager implements ImageStorageInterface $tmpFile = tempnam(sys_get_temp_dir(), 'cbz_'); file_put_contents($tmpFile, $cbzBinary); - $zip = new ZipArchive(); - if ($zip->open($tmpFile) !== true) { + $zip = new \ZipArchive(); + if (true !== $zip->open($tmpFile)) { unlink($tmpFile); throw new \RuntimeException('Failed to open CBZ file as ZIP archive'); } $imageEntries = []; - for ($i = 0; $i < $zip->numFiles; $i++) { + for ($i = 0; $i < $zip->numFiles; ++$i) { $name = $zip->getNameIndex($i); if (preg_match('/\.(jpg|jpeg|png|webp|gif)$/i', $name)) { $imageEntries[] = ['index' => $i, 'name' => $name]; @@ -75,17 +74,17 @@ class ImageStorageManager implements ImageStorageInterface $tmpFile = tempnam(sys_get_temp_dir(), 'cbz_'); file_put_contents($tmpFile, $cbzBinary); - $zip = new ZipArchive(); - if ($zip->open($tmpFile) !== true) { + $zip = new \ZipArchive(); + if (true !== $zip->open($tmpFile)) { unlink($tmpFile); throw new \RuntimeException('Failed to open CBZ file as ZIP archive'); } $count = 0; - for ($i = 0; $i < $zip->numFiles; $i++) { + for ($i = 0; $i < $zip->numFiles; ++$i) { $name = $zip->getNameIndex($i); if (preg_match('/\.(jpg|jpeg|png|webp|gif)$/i', $name)) { - $count++; + ++$count; } } diff --git a/src/Domain/Shared/Infrastructure/Service/MangaFileManager.php b/src/Domain/Shared/Infrastructure/Service/MangaFileManager.php index f107cd2..771ba24 100644 --- a/src/Domain/Shared/Infrastructure/Service/MangaFileManager.php +++ b/src/Domain/Shared/Infrastructure/Service/MangaFileManager.php @@ -20,9 +20,10 @@ readonly class MangaFileManager implements MangaPathManagerInterface public function getMangaDirectory(string $mangaTitle, string $publicationYear): string { - $mangaDirName = ucfirst($this->slugify($mangaTitle)) . ' (' . $publicationYear . ')'; + $mangaDirName = ucfirst($this->slugify($mangaTitle)).' ('.$publicationYear.')'; $dir = sprintf('%s/public/cbz/%s', $this->projectDir, $mangaDirName); $this->ensureDirectory($dir); + return $dir; } @@ -31,12 +32,14 @@ readonly class MangaFileManager implements MangaPathManagerInterface $mangaDir = $this->getMangaDirectory($mangaTitle, $publicationYear); $dir = sprintf('%s/volume_%02d', $mangaDir, $volumeNumber); $this->ensureDirectory($dir); + return $dir; } public function buildChapterCbzPath(string $mangaTitle, string $publicationYear, int $volumeNumber, string $chapterNumber): string { $volumeDir = $this->getVolumeDirectory($mangaTitle, $publicationYear, $volumeNumber); + return sprintf( '%s/%s_vol%d_ch%s.cbz', $volumeDir, @@ -49,6 +52,7 @@ readonly class MangaFileManager implements MangaPathManagerInterface public function buildVolumeCbzPath(string $mangaTitle, string $publicationYear, int $volumeNumber): string { $volumeDir = $this->getVolumeDirectory($mangaTitle, $publicationYear, $volumeNumber); + return sprintf( '%s/%s_vol%d.cbz', $volumeDir, @@ -61,7 +65,7 @@ readonly class MangaFileManager implements MangaPathManagerInterface public function createCbzArchive(array $files, string $cbzPath): void { $zip = new \ZipArchive(); - if ($zip->open($cbzPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { + if (true !== $zip->open($cbzPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) { throw new \RuntimeException('Failed to create CBZ archive'); } @@ -104,6 +108,7 @@ readonly class MangaFileManager implements MangaPathManagerInterface $text = trim($text, '-'); $text = preg_replace('~-+~', '-', $text); $text = strtolower($text); + return $text ?: 'n-a'; } } diff --git a/src/Domain/Shared/Infrastructure/Service/SymfonyFileUpload.php b/src/Domain/Shared/Infrastructure/Service/SymfonyFileUpload.php index f37ae80..43e3f2d 100644 --- a/src/Domain/Shared/Infrastructure/Service/SymfonyFileUpload.php +++ b/src/Domain/Shared/Infrastructure/Service/SymfonyFileUpload.php @@ -13,14 +13,14 @@ readonly class SymfonyFileUpload implements FileUploadInterface { public function __construct( private Filesystem $filesystem, - private string $uploadsDirectory + private string $uploadsDirectory, ) { } public function moveUploadedFile(string $sourcePath, string $targetDirectory, string $originalName): string { try { - $targetPath = $targetDirectory . '/' . uniqid() . '_' . $originalName; + $targetPath = $targetDirectory.'/'.uniqid().'_'.$originalName; $this->filesystem->copy($sourcePath, $targetPath); return $targetPath; @@ -46,10 +46,7 @@ readonly class SymfonyFileUpload implements FileUploadInterface try { $this->filesystem->rename($sourcePath, $targetPath, true); } catch (FileException $e) { - throw FileProcessingException::uploadFailed( - basename($sourcePath), - $e->getMessage() - ); + throw FileProcessingException::uploadFailed(basename($sourcePath), $e->getMessage()); } } @@ -63,6 +60,7 @@ readonly class SymfonyFileUpload implements FileUploadInterface public function validateFileFormat(string $filePath, array $allowedExtensions): bool { $extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); + return in_array($extension, $allowedExtensions, true); } } diff --git a/src/Domain/Shared/Infrastructure/Service/SymfonyNotification.php b/src/Domain/Shared/Infrastructure/Service/SymfonyNotification.php index 4393d71..c2a8061 100644 --- a/src/Domain/Shared/Infrastructure/Service/SymfonyNotification.php +++ b/src/Domain/Shared/Infrastructure/Service/SymfonyNotification.php @@ -11,7 +11,7 @@ use Symfony\Component\Mercure\Update; readonly class SymfonyNotification implements NotificationInterface { public function __construct( - private HubInterface $hub + private HubInterface $hub, ) { } diff --git a/src/Domain/System/Infrastructure/ApiPlatform/Resource/GetSystemStatusResource.php b/src/Domain/System/Infrastructure/ApiPlatform/Resource/GetSystemStatusResource.php index b814004..a01796a 100644 --- a/src/Domain/System/Infrastructure/ApiPlatform/Resource/GetSystemStatusResource.php +++ b/src/Domain/System/Infrastructure/ApiPlatform/Resource/GetSystemStatusResource.php @@ -13,7 +13,7 @@ use App\Domain\System\Infrastructure\ApiPlatform\State\Provider\GetSystemStatusS new Get( uriTemplate: '/system/status', provider: GetSystemStatusStateProvider::class, - ) + ), ] )] class GetSystemStatusResource diff --git a/src/Domain/System/Infrastructure/ApiPlatform/State/Provider/GetSystemStatusStateProvider.php b/src/Domain/System/Infrastructure/ApiPlatform/State/Provider/GetSystemStatusStateProvider.php index 122dd90..65dddad 100644 --- a/src/Domain/System/Infrastructure/ApiPlatform/State/Provider/GetSystemStatusStateProvider.php +++ b/src/Domain/System/Infrastructure/ApiPlatform/State/Provider/GetSystemStatusStateProvider.php @@ -83,6 +83,6 @@ final class GetSystemStatusStateProvider implements ProviderInterface $exp = (int) floor(log($bytes, 1024)); $exp = min($exp, count($units) - 1); - return round($bytes / (1024 ** $exp), 2) . ' ' . $units[$exp]; + return round($bytes / (1024 ** $exp), 2).' '.$units[$exp]; } } diff --git a/src/Domain/System/Infrastructure/Persistence/Repository/DoctrineSystemStatusRepository.php b/src/Domain/System/Infrastructure/Persistence/Repository/DoctrineSystemStatusRepository.php index 0ccc5f2..cc2beb3 100644 --- a/src/Domain/System/Infrastructure/Persistence/Repository/DoctrineSystemStatusRepository.php +++ b/src/Domain/System/Infrastructure/Persistence/Repository/DoctrineSystemStatusRepository.php @@ -18,21 +18,21 @@ class DoctrineSystemStatusRepository implements SystemStatusRepositoryInterface public function countMangas(): int { return (int) $this->entityManager - ->createQuery('SELECT COUNT(m) FROM ' . Manga::class . ' m') + ->createQuery('SELECT COUNT(m) FROM '.Manga::class.' m') ->getSingleScalarResult(); } public function countMonitoredMangas(): int { return (int) $this->entityManager - ->createQuery('SELECT COUNT(m) FROM ' . Manga::class . ' m WHERE m.monitored = true') + ->createQuery('SELECT COUNT(m) FROM '.Manga::class.' m WHERE m.monitored = true') ->getSingleScalarResult(); } public function countMangasByStatus(): array { $results = $this->entityManager - ->createQuery('SELECT m.status, COUNT(m) as cnt FROM ' . Manga::class . ' m GROUP BY m.status') + ->createQuery('SELECT m.status, COUNT(m) as cnt FROM '.Manga::class.' m GROUP BY m.status') ->getResult(); $counts = []; @@ -47,28 +47,28 @@ class DoctrineSystemStatusRepository implements SystemStatusRepositoryInterface public function countChapters(): int { return (int) $this->entityManager - ->createQuery('SELECT COUNT(c) FROM ' . Chapter::class . ' c') + ->createQuery('SELECT COUNT(c) FROM '.Chapter::class.' c') ->getSingleScalarResult(); } public function countDownloadedChapters(): int { return (int) $this->entityManager - ->createQuery('SELECT COUNT(c) FROM ' . Chapter::class . ' c WHERE c.cbzPath IS NOT NULL') + ->createQuery('SELECT COUNT(c) FROM '.Chapter::class.' c WHERE c.cbzPath IS NOT NULL') ->getSingleScalarResult(); } public function countContentSources(): int { return (int) $this->entityManager - ->createQuery('SELECT COUNT(s) FROM ' . ContentSource::class . ' s') + ->createQuery('SELECT COUNT(s) FROM '.ContentSource::class.' s') ->getSingleScalarResult(); } public function countContentSourcesByHealth(): array { $results = $this->entityManager - ->createQuery('SELECT s.healthStatus, COUNT(s) as cnt FROM ' . ContentSource::class . ' s GROUP BY s.healthStatus') + ->createQuery('SELECT s.healthStatus, COUNT(s) as cnt FROM '.ContentSource::class.' s GROUP BY s.healthStatus') ->getResult(); $counts = []; diff --git a/src/Entity/ApiToken.php b/src/Entity/ApiToken.php index e132977..75b5aca 100644 --- a/src/Entity/ApiToken.php +++ b/src/Entity/ApiToken.php @@ -43,7 +43,7 @@ class ApiToken */ public function __construct(string $tokenType = self::PERSONAL_ACCESS_TOKEN_PREFIX) { - $this->token = $tokenType . bin2hex(random_bytes(32)); + $this->token = $tokenType.bin2hex(random_bytes(32)); } public function getId(): ?int @@ -101,6 +101,6 @@ class ApiToken public function isValid(): bool { - return $this->expiresAt === null || $this->expiresAt > new \DateTimeImmutable(); + return null === $this->expiresAt || $this->expiresAt > new \DateTimeImmutable(); } } diff --git a/src/Entity/Chapter.php b/src/Entity/Chapter.php index cfb1552..7584ecd 100644 --- a/src/Entity/Chapter.php +++ b/src/Entity/Chapter.php @@ -3,8 +3,6 @@ namespace App\Entity; use App\Repository\ChapterRepository; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: ChapterRepository::class)] diff --git a/src/Entity/ContentSource.php b/src/Entity/ContentSource.php index 0f8ccaa..4747fe0 100644 --- a/src/Entity/ContentSource.php +++ b/src/Entity/ContentSource.php @@ -73,7 +73,7 @@ class ContentSource return $this->imageSelector; } - public function setImageSelector(string $imageSelector): static + public function setImageSelector(?string $imageSelector): static { $this->imageSelector = $imageSelector; @@ -107,6 +107,7 @@ class ContentSource public function getChapterUrl(string $mangaTitle, float $chapterNumber): string { $urlGenerator = new ChapterUrlGenerator($this->chapterUrlFormat); + return $urlGenerator->getChapterUrl($mangaTitle, $chapterNumber); } diff --git a/src/Entity/Manga.php b/src/Entity/Manga.php index 19d3768..646affa 100644 --- a/src/Entity/Manga.php +++ b/src/Entity/Manga.php @@ -7,7 +7,6 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Serializer\Annotation\Groups; #[ORM\Entity(repositoryClass: MangaRepository::class)] class Manga @@ -35,7 +34,7 @@ class Manga #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $description = null; - #[ORM\Column(type: Types::ARRAY, nullable: true)] + #[ORM\Column(type: Types::JSON, nullable: true)] private ?array $genres = null; #[ORM\Column] @@ -107,6 +106,7 @@ class Manga return $chapter; } } + return null; } diff --git a/src/Entity/User.php b/src/Entity/User.php index 6c33b9c..243772f 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -117,7 +117,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface */ public function getRoles(): array { - if ($this->accessTokenScopes !== null) { + if (null !== $this->accessTokenScopes) { $roles = $this->roles; $roles[] = 'ROLE_FRONT_USER'; } else { diff --git a/src/Kernel.php b/src/Kernel.php index fd10ba9..680d626 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -2,9 +2,8 @@ namespace App; -use Override; -use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\ORM\Mapping\ClassMetadata; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -14,10 +13,10 @@ class Kernel extends BaseKernel { use MicroKernelTrait; - #[Override] + #[\Override] protected function build(ContainerBuilder $container): void { - $container->addCompilerPass(new class () implements CompilerPassInterface { + $container->addCompilerPass(new class implements CompilerPassInterface { public function process(ContainerBuilder $container): void { $container->getDefinition('doctrine.orm.default_configuration') @@ -25,7 +24,7 @@ class Kernel extends BaseKernel 'setIdentityGenerationPreferences', [ [ - PostgreSQLPlatform::class => ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE, + PostgreSQLPlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE, ], ] ); diff --git a/src/Repository/MangaRepository.php b/src/Repository/MangaRepository.php index 6d75fe3..0e4578f 100644 --- a/src/Repository/MangaRepository.php +++ b/src/Repository/MangaRepository.php @@ -63,10 +63,9 @@ class MangaRepository extends ServiceEntityRepository LIMIT 10 '; - $stmt = $conn->prepare($sql); - $resultSet = $stmt->executeQuery([ + $resultSet = $conn->executeQuery($sql, [ 'slug' => $slug, - 'max_distance' => intval(ceil(strlen($slug) / 3)) + 'max_distance' => intval(ceil(strlen($slug) / 3)), ]); $results = $resultSet->fetchAllAssociative(); @@ -106,9 +105,9 @@ class MangaRepository extends ServiceEntityRepository public function findAllSortedAndFiltered($sort = 'title', $order = 'asc', $status = 'all') { $qb = $this->createQueryBuilder('m') - ->orderBy('m.' . $sort, $order); + ->orderBy('m.'.$sort, $order); - if ($status !== 'all') { + if ('all' !== $status) { $qb->andWhere('m.status = :status') ->setParameter('status', $status); } diff --git a/src/Security/ApiTokenHandler.php b/src/Security/ApiTokenHandler.php index 45777f3..ba4114f 100644 --- a/src/Security/ApiTokenHandler.php +++ b/src/Security/ApiTokenHandler.php @@ -12,7 +12,6 @@ readonly class ApiTokenHandler implements AccessTokenHandlerInterface { public function __construct(private ApiTokenRepository $apiTokenRepository) { - } public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge diff --git a/src/State/UserHashPasswordProcessor.php b/src/State/UserHashPasswordProcessor.php index e70a1be..ef0d52a 100644 --- a/src/State/UserHashPasswordProcessor.php +++ b/src/State/UserHashPasswordProcessor.php @@ -14,13 +14,15 @@ readonly class UserHashPasswordProcessor implements ProcessorInterface public function __construct(private ProcessorInterface $decoratedProcessor, private UserPasswordHasherInterface $passwordHasher) { } + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed { - if ($data instanceof User && $data->getPlainPassword() !== null) { + if ($data instanceof User && null !== $data->getPlainPassword()) { $data->setPassword($this->passwordHasher->hashPassword($data, $data->getPlainPassword())); } $this->decoratedProcessor->process($data, $operation, $uriVariables, $context); + return $data; } } diff --git a/symfony.lock b/symfony.lock index c8056ad..a89bd48 100644 --- a/symfony.lock +++ b/symfony.lock @@ -135,6 +135,18 @@ ".env" ] }, + "symfony/form": { + "version": "8.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.2", + "ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b" + }, + "files": [ + "config/packages/csrf.yaml" + ] + }, "symfony/framework-bundle": { "version": "7.0", "recipe": { @@ -223,6 +235,18 @@ "tests/bootstrap.php" ] }, + "symfony/property-info": { + "version": "8.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.3", + "ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7" + }, + "files": [ + "config/packages/property_info.yaml" + ] + }, "symfony/routing": { "version": "7.0", "recipe": { @@ -236,6 +260,18 @@ "config/routes.yaml" ] }, + "symfony/scheduler": { + "version": "8.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.2", + "ref": "caea3c928ee9e1b21288fd76aef36f16ea355515" + }, + "files": [ + "src/Schedule.php" + ] + }, "symfony/security-bundle": { "version": "7.0", "recipe": { diff --git a/tests/Domain/Manga/Adapter/InMemoryChapterSynchronizationService.php b/tests/Domain/Manga/Adapter/InMemoryChapterSynchronizationService.php index f22c5a5..5ce6b30 100644 --- a/tests/Domain/Manga/Adapter/InMemoryChapterSynchronizationService.php +++ b/tests/Domain/Manga/Adapter/InMemoryChapterSynchronizationService.php @@ -15,7 +15,7 @@ class InMemoryChapterSynchronizationService implements ChapterSynchronizationSer $this->synchronizedChapters[$manga->getId()->getValue()] = [ 'manga_id' => $manga->getId()->getValue(), 'external_id' => $manga->getExternalId()?->getValue(), - 'synchronized_at' => new \DateTimeImmutable() + 'synchronized_at' => new \DateTimeImmutable(), ]; // Retourne les IDs des chapitres synchronisés (simulation) diff --git a/tests/Domain/Manga/Adapter/InMemoryImageProcessor.php b/tests/Domain/Manga/Adapter/InMemoryImageProcessor.php index 5ee6b6c..b187f09 100644 --- a/tests/Domain/Manga/Adapter/InMemoryImageProcessor.php +++ b/tests/Domain/Manga/Adapter/InMemoryImageProcessor.php @@ -11,19 +11,21 @@ class InMemoryImageProcessor implements ImageProcessorInterface /** @var array */ private array $downloadedImages = []; - + /** @var array */ private array $thumbnails = []; public function downloadImage(string $imageUrl): string { $this->downloadedImages[$imageUrl] = self::FAKE_FULL_IMAGE_PATH; + return self::FAKE_FULL_IMAGE_PATH; } public function createThumbnail(string $originalImagePath): string { $this->thumbnails[$originalImagePath] = self::FAKE_THUMBNAIL_PATH; + return self::FAKE_THUMBNAIL_PATH; } @@ -36,4 +38,4 @@ class InMemoryImageProcessor implements ImageProcessorInterface { return $this->thumbnails; } -} \ No newline at end of file +} diff --git a/tests/Domain/Manga/Adapter/InMemoryMangaProvider.php b/tests/Domain/Manga/Adapter/InMemoryMangaProvider.php index 2c5dc73..fadbf7a 100644 --- a/tests/Domain/Manga/Adapter/InMemoryMangaProvider.php +++ b/tests/Domain/Manga/Adapter/InMemoryMangaProvider.php @@ -48,4 +48,4 @@ class InMemoryMangaProvider implements MangaProviderInterface { return new MangaCollection([]); } -} \ No newline at end of file +} diff --git a/tests/Domain/Manga/Adapter/InMemoryMangaRepository.php b/tests/Domain/Manga/Adapter/InMemoryMangaRepository.php index 05c56e3..a8c3190 100644 --- a/tests/Domain/Manga/Adapter/InMemoryMangaRepository.php +++ b/tests/Domain/Manga/Adapter/InMemoryMangaRepository.php @@ -30,7 +30,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface $valueA = $this->getPropertyValue($a, $sortBy); $valueB = $this->getPropertyValue($b, $sortBy); - return $sortOrder === 'asc' + return 'asc' === $sortOrder ? $valueA <=> $valueB : $valueB <=> $valueA; }); @@ -57,6 +57,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface return $manga; } } + return null; } @@ -105,10 +106,10 @@ class InMemoryMangaRepository implements MangaRepositoryInterface private function getPropertyValue(Manga $manga, string $property): mixed { - return match($property) { + return match ($property) { 'title' => $manga->getTitle()->getValue(), 'publicationYear' => $manga->getPublicationYear(), - default => throw new \InvalidArgumentException("Unknown sort property: $property") + default => throw new \InvalidArgumentException("Unknown sort property: $property"), }; } @@ -121,7 +122,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface $chapters = $this->chapters[$mangaId]; usort($chapters, function (Chapter $a, Chapter $b) use ($sortOrder) { - return $sortOrder === 'desc' + return 'desc' === $sortOrder ? $b->getNumber() <=> $a->getNumber() : $a->getNumber() <=> $b->getNumber(); }); @@ -138,12 +139,13 @@ class InMemoryMangaRepository implements MangaRepositoryInterface $chapters = $this->chapters[$mangaId]; usort($chapters, function (Chapter $a, Chapter $b) use ($sortOrder) { - return $sortOrder === 'desc' + return 'desc' === $sortOrder ? $b->getNumber() <=> $a->getNumber() : $a->getNumber() <=> $b->getNumber(); }); $offset = ($page - 1) * $limit; + return array_slice($chapters, $offset, $limit); } @@ -171,6 +173,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface if ($chapter && $chapter->isVisible()) { return $chapter; } + return null; } @@ -181,6 +184,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface return $chapter; } } + return null; } @@ -196,10 +200,9 @@ class InMemoryMangaRepository implements MangaRepositoryInterface { return array_values(array_filter( $this->chaptersById, - fn (Chapter $chapter) => - $chapter->getMangaId()->getValue() === $mangaId && - $chapter->getVolume() === $volume && - $chapter->isVisible() + fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId + && $chapter->getVolume() === $volume + && $chapter->isVisible() )); } @@ -207,11 +210,10 @@ class InMemoryMangaRepository implements MangaRepositoryInterface { return array_values(array_filter( $this->chaptersById, - fn (Chapter $chapter) => - $chapter->getMangaId()->getValue() === $mangaId && - $chapter->getVolume() === $volume && - $chapter->isVisible() && - $chapter->isAvailable() + fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId + && $chapter->getVolume() === $volume + && $chapter->isVisible() + && $chapter->isAvailable() )); } @@ -228,13 +230,13 @@ class InMemoryMangaRepository implements MangaRepositoryInterface { $this->chapters[$mangaId] = []; - for ($i = 1; $i <= $count; $i++) { + for ($i = 1; $i <= $count; ++$i) { $chapter = new Chapter( - id: new ChapterId((string)$i), + id: new ChapterId((string) $i), mangaId: new MangaId($mangaId), - number: (float)$i, + number: (float) $i, title: "Chapter $i", - volume: (int)ceil($i / 10), + volume: (int) ceil($i / 10), isVisible: true, createdAt: new \DateTimeImmutable() ); @@ -250,6 +252,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface return $manga; } } + return null; } @@ -267,7 +270,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface $manga->getTitle()->getValue(), $manga->getSlug()->getValue(), $manga->getAuthor(), - $manga->getDescription() + $manga->getDescription(), ]; foreach ($manga->getAlternativeSlugs() as $altSlug) { @@ -287,6 +290,7 @@ class InMemoryMangaRepository implements MangaRepositoryInterface usort($sortedMangas, fn (Manga $a, Manga $b) => $a->getTitle()->getValue() <=> $b->getTitle()->getValue()); $offset = ($page - 1) * $limit; + return array_slice($sortedMangas, $offset, $limit); } @@ -320,9 +324,9 @@ class InMemoryMangaRepository implements MangaRepositoryInterface return false; } - if ($criteria->lastCheckBefore !== null) { + if (null !== $criteria->lastCheckBefore) { $lastCheck = $manga->getLastMonitoringCheck(); - if ($lastCheck === null || $lastCheck >= $criteria->lastCheckBefore) { + if (null === $lastCheck || $lastCheck >= $criteria->lastCheckBefore) { return false; } } diff --git a/tests/Domain/Manga/Adapter/InMemoryMangadexClient.php b/tests/Domain/Manga/Adapter/InMemoryMangadexClient.php index 505ce9b..164d752 100644 --- a/tests/Domain/Manga/Adapter/InMemoryMangadexClient.php +++ b/tests/Domain/Manga/Adapter/InMemoryMangadexClient.php @@ -13,7 +13,7 @@ class InMemoryMangadexClient implements MangadexClientInterface public function __construct( array $mangas = [], array $feeds = [], - array $aggregates = [] + array $aggregates = [], ) { $this->mangas = $mangas; $this->feeds = $feeds; @@ -34,8 +34,8 @@ class InMemoryMangadexClient implements MangadexClientInterface { $results = []; foreach ($this->mangas as $id => $manga) { - if (isset($manga['attributes']['title']['en']) && - str_contains( + if (isset($manga['attributes']['title']['en']) + && str_contains( strtolower($manga['attributes']['title']['en']), strtolower($title) ) @@ -52,7 +52,7 @@ class InMemoryMangadexClient implements MangadexClientInterface $statistics = []; foreach ($mangaIds as $id) { $statistics[$id] = [ - 'rating' => ['average' => 4.5] // Default rating for tests + 'rating' => ['average' => 4.5], // Default rating for tests ]; } @@ -64,18 +64,18 @@ class InMemoryMangadexClient implements MangadexClientInterface if (!isset($this->feeds[$mangaId])) { return [ 'data' => [], - 'total' => 0 + 'total' => 0, ]; } $feed = $this->feeds[$mangaId]; - if ($order === 'desc') { + if ('desc' === $order) { $feed = array_reverse($feed); } return [ 'data' => array_slice($feed, $offset, $limit), - 'total' => count($feed) + 'total' => count($feed), ]; } @@ -84,13 +84,13 @@ class InMemoryMangadexClient implements MangadexClientInterface if (!isset($this->aggregates[$mangaId])) { return [ 'result' => 'ok', - 'volumes' => [] + 'volumes' => [], ]; } return [ 'result' => 'ok', - 'volumes' => $this->aggregates[$mangaId] + 'volumes' => $this->aggregates[$mangaId], ]; } @@ -102,7 +102,7 @@ class InMemoryMangadexClient implements MangadexClientInterface return [ 'result' => 'ok', - 'data' => array_merge(['id' => $mangaId], $this->mangas[$mangaId]) + 'data' => array_merge(['id' => $mangaId], $this->mangas[$mangaId]), ]; } @@ -125,4 +125,4 @@ class InMemoryMangadexClient implements MangadexClientInterface { $this->aggregates[$mangaId] = $aggregate; } -} \ No newline at end of file +} diff --git a/tests/Domain/Manga/Adapter/InMemoryPathManager.php b/tests/Domain/Manga/Adapter/InMemoryPathManager.php index 1605292..7ef649a 100644 --- a/tests/Domain/Manga/Adapter/InMemoryPathManager.php +++ b/tests/Domain/Manga/Adapter/InMemoryPathManager.php @@ -11,28 +11,31 @@ class InMemoryPathManager implements MangaPathManagerInterface public function getMangaDirectory(string $mangaTitle, string $publicationYear): string { - $dir = '/tmp/manga/' . $this->slugify($mangaTitle) . '_' . $publicationYear; + $dir = '/tmp/manga/'.$this->slugify($mangaTitle).'_'.$publicationYear; $this->ensureDirectory($dir); + return $dir; } public function getVolumeDirectory(string $mangaTitle, string $publicationYear, int $volumeNumber): string { - $dir = $this->getMangaDirectory($mangaTitle, $publicationYear) . '/volume_' . $volumeNumber; + $dir = $this->getMangaDirectory($mangaTitle, $publicationYear).'/volume_'.$volumeNumber; $this->ensureDirectory($dir); + return $dir; } public function buildChapterCbzPath(string $mangaTitle, string $publicationYear, int $volumeNumber, string $chapterNumber): string { $dir = $this->getVolumeDirectory($mangaTitle, $publicationYear, $volumeNumber); - return $dir . '/' . $this->slugify($mangaTitle) . '_vol' . $volumeNumber . '_ch' . $chapterNumber . '.cbz'; + + return $dir.'/'.$this->slugify($mangaTitle).'_vol'.$volumeNumber.'_ch'.$chapterNumber.'.cbz'; } public function buildVolumeCbzPath(string $mangaTitle, string $publicationYear, int $volumeNumber): string { return $this->getVolumeDirectory($mangaTitle, $publicationYear, $volumeNumber) - . '/' . $this->slugify($mangaTitle) . '_vol' . $volumeNumber . '.cbz'; + .'/'.$this->slugify($mangaTitle).'_vol'.$volumeNumber.'.cbz'; } public function createCbzArchive(array $files, string $cbzPath): void @@ -56,7 +59,7 @@ class InMemoryPathManager implements MangaPathManagerInterface } /** - * Get all stored files + * Get all stored files. */ public function getFiles(): array { @@ -64,7 +67,7 @@ class InMemoryPathManager implements MangaPathManagerInterface } /** - * Clear all stored files + * Clear all stored files. */ public function clear(): void { @@ -79,6 +82,7 @@ class InMemoryPathManager implements MangaPathManagerInterface $text = trim($text, '-'); $text = preg_replace('~-+~', '-', $text); $text = strtolower($text); + return $text ?: 'n-a'; } diff --git a/tests/Domain/Manga/Application/Command/ToggleMangaMonitoringTest.php b/tests/Domain/Manga/Application/Command/ToggleMangaMonitoringTest.php index d404217..8ea8bba 100644 --- a/tests/Domain/Manga/Application/Command/ToggleMangaMonitoringTest.php +++ b/tests/Domain/Manga/Application/Command/ToggleMangaMonitoringTest.php @@ -8,7 +8,7 @@ use PHPUnit\Framework\TestCase; class ToggleMangaMonitoringTest extends TestCase { - public function testCreateCommandWithValidData(): void + public function testCreateCommandWithValidData(): void { // Arrange & Act $mangaId = new MangaId('manga-123'); diff --git a/tests/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandlerTest.php b/tests/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandlerTest.php index 70b9015..d17aace 100644 --- a/tests/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandlerTest.php +++ b/tests/Domain/Manga/Application/CommandHandler/CreateMangaFromMangadexHandlerTest.php @@ -10,9 +10,9 @@ use App\Domain\Manga\Domain\Model\ValueObject\ExternalId; use App\Domain\Manga\Domain\Model\ValueObject\MangaId; use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug; use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle; +use App\Tests\Domain\Manga\Adapter\InMemoryImageProcessor; use App\Tests\Domain\Manga\Adapter\InMemoryMangaProvider; use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository; -use App\Tests\Domain\Manga\Adapter\InMemoryImageProcessor; use App\Tests\Shared\Adapter\InMemoryEventDispatcher; use PHPUnit\Framework\TestCase; @@ -23,6 +23,7 @@ class CreateMangaFromMangadexHandlerTest extends TestCase private InMemoryImageProcessor $imageProcessor; private CreateMangaFromMangadexHandler $handler; private InMemoryEventDispatcher $eventDispatcher; + protected function setUp(): void { $manga = new Manga( diff --git a/tests/Domain/Manga/Application/CommandHandler/CreateMangaHandlerTest.php b/tests/Domain/Manga/Application/CommandHandler/CreateMangaHandlerTest.php index b263f22..41c1ade 100644 --- a/tests/Domain/Manga/Application/CommandHandler/CreateMangaHandlerTest.php +++ b/tests/Domain/Manga/Application/CommandHandler/CreateMangaHandlerTest.php @@ -4,8 +4,8 @@ namespace App\Tests\Domain\Manga\Application\CommandHandler; use App\Domain\Manga\Application\Command\CreateManga; use App\Domain\Manga\Application\CommandHandler\CreateMangaHandler; -use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository; use App\Tests\Domain\Manga\Adapter\InMemoryImageProcessor; +use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository; use App\Tests\Shared\Adapter\InMemoryMessageBus; use PHPUnit\Framework\TestCase; @@ -15,6 +15,7 @@ class CreateMangaHandlerTest extends TestCase private InMemoryImageProcessor $imageProcessor; private CreateMangaHandler $handler; private InMemoryMessageBus $messageBus; + protected function setUp(): void { $this->repository = new InMemoryMangaRepository(); @@ -80,4 +81,4 @@ class CreateMangaHandlerTest extends TestCase $this->assertEquals('One Piece', $savedManga->getTitle()->getValue()); $this->assertNull($savedManga->getImageUrls()); } -} \ No newline at end of file +} diff --git a/tests/Domain/Manga/Application/CommandHandler/ImportChapterHandlerTest.php b/tests/Domain/Manga/Application/CommandHandler/ImportChapterHandlerTest.php index d6f36cb..4221a88 100644 --- a/tests/Domain/Manga/Application/CommandHandler/ImportChapterHandlerTest.php +++ b/tests/Domain/Manga/Application/CommandHandler/ImportChapterHandlerTest.php @@ -4,8 +4,8 @@ namespace App\Tests\Domain\Manga\Application\CommandHandler; use App\Domain\Manga\Application\Command\ImportChapter; use App\Domain\Manga\Application\CommandHandler\ImportChapterHandler; -use App\Domain\Manga\Domain\Exception\MangaNotFoundException; use App\Domain\Manga\Domain\Exception\ChapterNotFoundException; +use App\Domain\Manga\Domain\Exception\MangaNotFoundException; use App\Domain\Manga\Domain\Model\Chapter; use App\Domain\Manga\Domain\Model\Manga; use App\Domain\Manga\Domain\Model\ValueObject\ChapterId; @@ -32,7 +32,7 @@ class ImportChapterHandlerTest extends TestCase ); } - public function test_it_throws_exception_when_chapter_not_found(): void + public function testItThrowsExceptionWhenChapterNotFound(): void { // Arrange $mangaId = 'manga-123'; @@ -62,7 +62,7 @@ class ImportChapterHandlerTest extends TestCase $this->handler->handle($command); } - public function test_it_updates_existing_chapter_with_new_path(): void + public function testItUpdatesExistingChapterWithNewPath(): void { // Arrange $mangaId = 'manga-123'; @@ -113,7 +113,7 @@ class ImportChapterHandlerTest extends TestCase $this->assertNotNull($updatedChapter->getPagesDirectory()); } - public function test_it_throws_exception_when_manga_not_found(): void + public function testItThrowsExceptionWhenMangaNotFound(): void { // Arrange $cbzBinary = $this->createValidCbzBinary(); @@ -130,7 +130,7 @@ class ImportChapterHandlerTest extends TestCase $this->handler->handle($command); } - public function test_it_throws_exception_when_file_is_not_valid_cbz(): void + public function testItThrowsExceptionWhenFileIsNotValidCbz(): void { // Arrange $mangaId = 'manga-123'; @@ -167,7 +167,7 @@ class ImportChapterHandlerTest extends TestCase unlink($tmpFile); $zip = new \ZipArchive(); - if ($zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { + if (true !== $zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) { throw new \RuntimeException('Cannot create test CBZ file'); } diff --git a/tests/Domain/Manga/Application/CommandHandler/ImportVolumeHandlerTest.php b/tests/Domain/Manga/Application/CommandHandler/ImportVolumeHandlerTest.php index 8ded66c..5bf7b5b 100644 --- a/tests/Domain/Manga/Application/CommandHandler/ImportVolumeHandlerTest.php +++ b/tests/Domain/Manga/Application/CommandHandler/ImportVolumeHandlerTest.php @@ -31,7 +31,7 @@ class ImportVolumeHandlerTest extends TestCase ); } - public function test_it_updates_all_chapters_in_volume(): void + public function testItUpdatesAllChaptersInVolume(): void { // Arrange $mangaId = 'manga-123'; @@ -47,11 +47,11 @@ class ImportVolumeHandlerTest extends TestCase 'ongoing' ); // Create chapters in volume 1 and add through the aggregate - for ($i = 1; $i <= 3; $i++) { + for ($i = 1; $i <= 3; ++$i) { $chapter = new Chapter( new ChapterId("chapter-$i"), new MangaId($mangaId), - (float)$i, + (float) $i, "Chapter $i", $volumeNumber, true, @@ -81,7 +81,7 @@ class ImportVolumeHandlerTest extends TestCase } } - public function test_it_throws_exception_when_manga_not_found(): void + public function testItThrowsExceptionWhenMangaNotFound(): void { // Arrange $cbzBinary = $this->createValidCbzBinary(); @@ -98,7 +98,7 @@ class ImportVolumeHandlerTest extends TestCase $this->handler->handle($command); } - public function test_it_throws_exception_when_file_is_not_valid_cbz(): void + public function testItThrowsExceptionWhenFileIsNotValidCbz(): void { // Arrange $mangaId = 'manga-123'; @@ -129,7 +129,7 @@ class ImportVolumeHandlerTest extends TestCase $this->handler->handle($command); } - public function test_it_throws_exception_when_no_chapters_in_volume(): void + public function testItThrowsExceptionWhenNoChaptersInVolume(): void { // Arrange $mangaId = 'manga-123'; @@ -166,7 +166,7 @@ class ImportVolumeHandlerTest extends TestCase unlink($tmpFile); $zip = new \ZipArchive(); - if ($zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { + if (true !== $zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) { throw new \RuntimeException('Cannot create test CBZ file'); } diff --git a/tests/Domain/Manga/Application/CommandHandler/ToggleMangaMonitoringHandlerTest.php b/tests/Domain/Manga/Application/CommandHandler/ToggleMangaMonitoringHandlerTest.php index c5b6901..8edb976 100644 --- a/tests/Domain/Manga/Application/CommandHandler/ToggleMangaMonitoringHandlerTest.php +++ b/tests/Domain/Manga/Application/CommandHandler/ToggleMangaMonitoringHandlerTest.php @@ -9,7 +9,6 @@ use App\Domain\Manga\Domain\Model\ValueObject\ExternalId; use App\Domain\Manga\Domain\Model\ValueObject\MangaId; use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug; use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle; -use App\Domain\Manga\Domain\Model\ValueObject\MonitoringStatus; use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository; use PHPUnit\Framework\TestCase; diff --git a/tests/Domain/Manga/Application/EventListener/ChapterScrapedListenerTest.php b/tests/Domain/Manga/Application/EventListener/ChapterScrapedListenerTest.php index bfe0721..000b4f3 100644 --- a/tests/Domain/Manga/Application/EventListener/ChapterScrapedListenerTest.php +++ b/tests/Domain/Manga/Application/EventListener/ChapterScrapedListenerTest.php @@ -59,7 +59,7 @@ class ChapterScrapedListenerTest extends TestCase $event = new ChapterScraped( jobId: 'job-abc', chapterId: $chapterId, - pagesDirectory: '/data/pages/' . $chapterId, + pagesDirectory: '/data/pages/'.$chapterId, pageCount: 25, ); @@ -67,7 +67,7 @@ class ChapterScrapedListenerTest extends TestCase $updatedChapter = $this->mangaRepository->findChapterById($chapterId); $this->assertNotNull($updatedChapter); - $this->assertEquals('/data/pages/' . $chapterId, $updatedChapter->getPagesDirectory()); + $this->assertEquals('/data/pages/'.$chapterId, $updatedChapter->getPagesDirectory()); $this->assertEquals(25, $updatedChapter->getPageCount()); } diff --git a/tests/Domain/Manga/Application/QueryHandler/FindMangaMatchByFilenameHandlerTest.php b/tests/Domain/Manga/Application/QueryHandler/FindMangaMatchByFilenameHandlerTest.php index 873f9b2..f6f0ae0 100644 --- a/tests/Domain/Manga/Application/QueryHandler/FindMangaMatchByFilenameHandlerTest.php +++ b/tests/Domain/Manga/Application/QueryHandler/FindMangaMatchByFilenameHandlerTest.php @@ -31,7 +31,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase ); } - public function test_it_finds_exact_match_by_title(): void + public function testItFindsExactMatchByTitle(): void { // Given $manga = $this->createManga( @@ -62,7 +62,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase $this->assertNotEmpty($response->possibleTitles); } - public function test_it_returns_empty_matches_when_no_manga_found(): void + public function testItReturnsEmptyMatchesWhenNoMangaFound(): void { // Given - no manga in repository @@ -76,7 +76,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase $this->assertNull($response->getBestMatch()); } - public function test_it_finds_multiple_matches_and_sorts_by_score(): void + public function testItFindsMultipleMatchesAndSortsByScore(): void { // Given $manga1 = $this->createManga( @@ -113,13 +113,13 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase $this->assertEquals('One Piece', $bestMatch->title); // Les scores doivent être triés par ordre décroissant - $scores = array_map(fn($match) => $match->matchScore, $response->matches); + $scores = array_map(fn ($match) => $match->matchScore, $response->matches); $sortedScores = $scores; rsort($sortedScores); $this->assertEquals($sortedScores, $scores); } - public function test_it_extracts_chapter_and_volume_numbers(): void + public function testItExtractsChapterAndVolumeNumbers(): void { // Given $manga = $this->createManga( @@ -144,7 +144,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase } } - public function test_it_handles_decimal_chapter_numbers(): void + public function testItHandlesDecimalChapterNumbers(): void { // Given $manga = $this->createManga( @@ -169,7 +169,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase } } - public function test_it_finds_matches_with_alternative_slugs(): void + public function testItFindsMatchesWithAlternativeSlugs(): void { // Given $manga = $this->createManga( @@ -192,7 +192,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase $this->assertEquals('Shingeki no Kyojin', $bestMatch->title); } - public function test_it_handles_filename_without_chapter_or_volume(): void + public function testItHandlesFilenameWithoutChapterOrVolume(): void { // Given $manga = $this->createManga( @@ -217,7 +217,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase $this->assertNull($bestMatch->volumeNumber); } - public function test_it_finds_single_match_with_unique_id(): void + public function testItFindsSingleMatchWithUniqueId(): void { // Given $manga = $this->createManga( @@ -236,7 +236,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase $this->assertEquals('123', $response->matches[0]->id); } - public function test_it_provides_possible_titles_in_response(): void + public function testItProvidesPossibleTitlesInResponse(): void { // Given $manga = $this->createManga( @@ -255,7 +255,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase $this->assertEquals(['dragon-ball'], $response->possibleTitles); } - public function test_it_handles_filename_with_only_volume(): void + public function testItHandlesFilenameWithOnlyVolume(): void { // Given $manga = $this->createManga( @@ -281,7 +281,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase $this->assertNull($bestMatch->chapterNumber); } - public function test_it_handles_filename_with_only_chapter(): void + public function testItHandlesFilenameWithOnlyChapter(): void { // Given $manga = $this->createManga( @@ -307,7 +307,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase $this->assertNull($bestMatch->volumeNumber); } - public function test_it_handles_various_volume_only_formats(): void + public function testItHandlesVariousVolumeOnlyFormats(): void { // Given $manga = $this->createManga( @@ -344,7 +344,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase } } - public function test_it_handles_various_chapter_only_formats(): void + public function testItHandlesVariousChapterOnlyFormats(): void { // Given $manga = $this->createManga( @@ -387,7 +387,7 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase string $title, string $slug, array $alternativeSlugs = [], - ?string $thumbnailUrl = null + ?string $thumbnailUrl = null, ): Manga { return new Manga( id: new MangaId($id), @@ -411,4 +411,3 @@ class FindMangaMatchByFilenameHandlerTest extends TestCase $this->repository->clear(); } } - diff --git a/tests/Domain/Manga/Application/QueryHandler/GetMangaBySlugHandlerTest.php b/tests/Domain/Manga/Application/QueryHandler/GetMangaBySlugHandlerTest.php index c7f93ee..eb56920 100644 --- a/tests/Domain/Manga/Application/QueryHandler/GetMangaBySlugHandlerTest.php +++ b/tests/Domain/Manga/Application/QueryHandler/GetMangaBySlugHandlerTest.php @@ -39,7 +39,7 @@ class GetMangaBySlugHandlerTest extends TestCase externalId: null, imageUrl: 'https://example.com/image.jpg', imageUrls: new ImageUrls('https://example.com/image.jpg', 'https://example.com/thumbnail.jpg'), - + rating: 4.5 ); $this->repository->save($manga); diff --git a/tests/Domain/Manga/Application/QueryHandler/GetMangaChaptersHandlerTest.php b/tests/Domain/Manga/Application/QueryHandler/GetMangaChaptersHandlerTest.php index 2f7b3da..ceed7ac 100644 --- a/tests/Domain/Manga/Application/QueryHandler/GetMangaChaptersHandlerTest.php +++ b/tests/Domain/Manga/Application/QueryHandler/GetMangaChaptersHandlerTest.php @@ -28,7 +28,7 @@ class GetMangaChaptersHandlerTest extends TestCase public function testHandleThrowsExceptionWhenMangaNotFound(): void { $this->expectException(MangaNotFoundException::class); - + $query = new GetMangaChapters('non-existent-id'); $this->handler->handle($query); } @@ -37,11 +37,11 @@ class GetMangaChaptersHandlerTest extends TestCase { // Arrange $this->givenMangaExists('123'); - + // Act $query = new GetMangaChapters('123'); $response = $this->handler->handle($query); - + // Assert $this->assertEmpty($response->chapters); $this->assertEquals(0, $response->total); @@ -55,11 +55,11 @@ class GetMangaChaptersHandlerTest extends TestCase { // Arrange $this->givenMangaExistsWithChapters('123', 25); - + // Act $query = new GetMangaChapters('123', page: 2, limit: 10); $response = $this->handler->handle($query); - + // Assert $this->assertCount(10, $response->chapters); $this->assertEquals(25, $response->total); @@ -137,7 +137,7 @@ class GetMangaChaptersHandlerTest extends TestCase title: null, volume: null, isVisible: true, - pagesDirectory: '/manga/ch' . $num . '/', + pagesDirectory: '/manga/ch'.$num.'/', )); } @@ -166,7 +166,7 @@ class GetMangaChaptersHandlerTest extends TestCase title: null, volume: null, isVisible: true, - pagesDirectory: '/manga/ch' . $num . '/', + pagesDirectory: '/manga/ch'.$num.'/', )); } @@ -236,4 +236,4 @@ class GetMangaChaptersHandlerTest extends TestCase rating: null ); } -} \ No newline at end of file +} diff --git a/tests/Domain/Manga/Application/QueryHandler/GetMangaListHandlerTest.php b/tests/Domain/Manga/Application/QueryHandler/GetMangaListHandlerTest.php index 9d81d78..b3eafe7 100644 --- a/tests/Domain/Manga/Application/QueryHandler/GetMangaListHandlerTest.php +++ b/tests/Domain/Manga/Application/QueryHandler/GetMangaListHandlerTest.php @@ -6,8 +6,8 @@ use App\Domain\Manga\Application\Query\GetMangaList; use App\Domain\Manga\Application\QueryHandler\GetMangaListHandler; use App\Domain\Manga\Domain\Model\Manga; use App\Domain\Manga\Domain\Model\ValueObject\MangaId; -use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle; use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug; +use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle; use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository; use PHPUnit\Framework\TestCase; @@ -25,9 +25,9 @@ class GetMangaListHandlerTest extends TestCase public function testHandleReturnsEmptyListWhenNoMangas(): void { $query = new GetMangaList(); - + $response = $this->handler->handle($query); - + $this->assertEmpty($response->mangas); $this->assertEquals(0, $response->total); $this->assertEquals(1, $response->page); @@ -40,11 +40,11 @@ class GetMangaListHandlerTest extends TestCase { // Arrange $this->givenMangasExist(25); - + // Act $query = new GetMangaList(page: 2, limit: 10); $response = $this->handler->handle($query); - + // Assert $this->assertCount(10, $response->mangas); $this->assertEquals(25, $response->total); @@ -74,9 +74,9 @@ class GetMangaListHandlerTest extends TestCase private function givenMangasExist(int $count): void { - for ($i = 1; $i <= $count; $i++) { + for ($i = 1; $i <= $count; ++$i) { $this->repository->save( - $this->createManga((string)$i, "Manga $i", 2020) + $this->createManga((string) $i, "Manga $i", 2020) ); } } @@ -99,4 +99,4 @@ class GetMangaListHandlerTest extends TestCase { $this->repository->clear(); } -} \ No newline at end of file +} diff --git a/tests/Domain/Manga/Application/QueryHandler/SearchMangaHandlerTest.php b/tests/Domain/Manga/Application/QueryHandler/SearchMangaHandlerTest.php index 9e710a8..4fe558c 100644 --- a/tests/Domain/Manga/Application/QueryHandler/SearchMangaHandlerTest.php +++ b/tests/Domain/Manga/Application/QueryHandler/SearchMangaHandlerTest.php @@ -5,7 +5,6 @@ namespace App\Tests\Domain\Manga\Application\QueryHandler; use App\Domain\Manga\Application\Query\SearchManga; use App\Domain\Manga\Application\QueryHandler\SearchMangaHandler; use App\Domain\Manga\Domain\Model\Manga; -use App\Domain\Manga\Domain\Model\MangaCollection; use App\Domain\Manga\Domain\Model\ValueObject\ExternalId; use App\Domain\Manga\Domain\Model\ValueObject\MangaId; use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug; @@ -57,4 +56,4 @@ class SearchMangaHandlerTest extends TestCase $this->assertEquals('One Piece', $response->items[0]->title); $this->assertEquals('one-piece', $response->items[0]->slug); } -} \ No newline at end of file +} diff --git a/tests/Domain/Manga/Infrastructure/Client/MangadexClientTest.php b/tests/Domain/Manga/Infrastructure/Client/MangadexClientTest.php index 47ed10b..3ebbf40 100644 --- a/tests/Domain/Manga/Infrastructure/Client/MangadexClientTest.php +++ b/tests/Domain/Manga/Infrastructure/Client/MangadexClientTest.php @@ -26,13 +26,14 @@ class MangadexClientTest extends TestCase ); } - private function mockAuthenticationResponse(): MockObject&ResponseInterface + private function mockAuthenticationResponse(): MockObject&ResponseInterface { $authResponse = $this->createMock(ResponseInterface::class); $authResponse->method('toArray')->willReturn([ 'access_token' => 'access_token', - 'refresh_token' => 'refresh_token' + 'refresh_token' => 'refresh_token', ]); + return $authResponse; } @@ -46,11 +47,11 @@ class MangadexClientTest extends TestCase 'POST', 'https://auth.mangadex.org/realms/mangadex/protocol/openid-connect/token', $this->callback(function ($options) { - return $options['body']['grant_type'] === 'password' - && $options['body']['username'] === 'username' - && $options['body']['password'] === 'password' - && $options['body']['client_id'] === 'client_id' - && $options['body']['client_secret'] === 'client_secret'; + return 'password' === $options['body']['grant_type'] + && 'username' === $options['body']['username'] + && 'password' === $options['body']['password'] + && 'client_id' === $options['body']['client_id'] + && 'client_secret' === $options['body']['client_secret']; }) ) ->willReturn($response); @@ -76,10 +77,10 @@ class MangadexClientTest extends TestCase [ 'id' => '123', 'attributes' => [ - 'title' => ['en' => 'Test Manga'] - ] - ] - ] + 'title' => ['en' => 'Test Manga'], + ], + ], + ], ]; $authResponse = $this->mockAuthenticationResponse(); @@ -93,6 +94,7 @@ class MangadexClientTest extends TestCase if (str_contains($url, 'auth.mangadex.org')) { return $authResponse; } + return $searchResponse; }); @@ -105,9 +107,9 @@ class MangadexClientTest extends TestCase $expectedResponse = [ 'statistics' => [ '123' => [ - 'rating' => ['average' => 4.5] - ] - ] + 'rating' => ['average' => 4.5], + ], + ], ]; $authResponse = $this->mockAuthenticationResponse(); @@ -121,10 +123,11 @@ class MangadexClientTest extends TestCase if (str_contains($url, 'auth.mangadex.org')) { return $authResponse; } + return $ratingsResponse; }); $result = $this->client->getMangaRatings(['123']); $this->assertEquals($expectedResponse, $result); } -} \ No newline at end of file +} diff --git a/tests/Domain/Manga/Infrastructure/Provider/MangadexProviderTest.php b/tests/Domain/Manga/Infrastructure/Provider/MangadexProviderTest.php index 7d5a1bc..0846f47 100644 --- a/tests/Domain/Manga/Infrastructure/Provider/MangadexProviderTest.php +++ b/tests/Domain/Manga/Infrastructure/Provider/MangadexProviderTest.php @@ -43,30 +43,30 @@ class MangadexProviderTest extends TestCase 'year' => 2020, 'status' => 'ongoing', 'tags' => [ - ['attributes' => ['name' => ['en' => 'Action']]] - ] + ['attributes' => ['name' => ['en' => 'Action']]], + ], ], 'relationships' => [ [ 'type' => 'author', - 'attributes' => ['name' => 'Test Author'] + 'attributes' => ['name' => 'Test Author'], ], [ 'type' => 'cover_art', - 'attributes' => ['fileName' => 'cover.jpg'] - ] - ] - ] - ] + 'attributes' => ['fileName' => 'cover.jpg'], + ], + ], + ], + ], ]); $this->client->method('getMangaRatings') ->willReturn([ 'statistics' => [ '123' => [ - 'rating' => ['average' => 4.5] - ] - ] + 'rating' => ['average' => 4.5], + ], + ], ]); $result = $this->provider->search('test'); @@ -74,7 +74,7 @@ class MangadexProviderTest extends TestCase $this->assertCount(1, $mangas); $manga = $mangas[0]; - + $this->assertEquals('Test Manga', $manga->getTitle()->getValue()); $this->assertEquals('test-manga', $manga->getSlug()->getValue()); $this->assertEquals('Test description', $manga->getDescription()); @@ -95,14 +95,14 @@ class MangadexProviderTest extends TestCase 'id' => '123', 'attributes' => [ // Missing required 'title' field - 'description' => ['en' => 'Test description'] + 'description' => ['en' => 'Test description'], ], - 'relationships' => [] - ] - ] + 'relationships' => [], + ], + ], ]); $result = $this->provider->search('test'); $this->assertCount(0, $result->getItems()); } -} \ No newline at end of file +} diff --git a/tests/Domain/Manga/Infrastructure/Service/FilenameAnalyzerTest.php b/tests/Domain/Manga/Infrastructure/Service/FilenameAnalyzerTest.php index 88defbb..d82b898 100644 --- a/tests/Domain/Manga/Infrastructure/Service/FilenameAnalyzerTest.php +++ b/tests/Domain/Manga/Infrastructure/Service/FilenameAnalyzerTest.php @@ -16,7 +16,7 @@ class FilenameAnalyzerTest extends TestCase $this->analyzer = new FilenameAnalyzer(); } - public function test_it_analyzes_one_piece_filename_correctly(): void + public function testItAnalyzesOnePieceFilenameCorrectly(): void { // Given $filename = 'one-piece_vol108_ch1094.cbz'; @@ -32,7 +32,7 @@ class FilenameAnalyzerTest extends TestCase $this->assertTrue($result->hasVolumeNumber()); } - public function test_it_handles_different_filename_formats(): void + public function testItHandlesDifferentFilenameFormats(): void { $testCases = [ // Format underscore @@ -77,7 +77,7 @@ class FilenameAnalyzerTest extends TestCase } } - public function test_it_extracts_and_cleans_title(): void + public function testItExtractsAndCleansTitle(): void { // Given $filename = 'one-piece_vol108_ch1094.cbz'; @@ -90,7 +90,7 @@ class FilenameAnalyzerTest extends TestCase $this->assertNotEmpty($result->getTitle()->getValue(), 'Title should not be empty'); } - public function test_it_handles_files_without_volume_or_chapter(): void + public function testItHandlesFilesWithoutVolumeOrChapter(): void { $testCases = [ [ @@ -114,7 +114,7 @@ class FilenameAnalyzerTest extends TestCase } } - public function test_it_handles_cbz_and_cbr_extensions(): void + public function testItHandlesCbzAndCbrExtensions(): void { // Given $testCases = [ @@ -133,7 +133,7 @@ class FilenameAnalyzerTest extends TestCase } } - public function test_it_cleans_common_patterns(): void + public function testItCleansCommonPatterns(): void { $testCases = [ [ @@ -160,7 +160,7 @@ class FilenameAnalyzerTest extends TestCase } } - public function test_it_handles_filename_with_only_volume(): void + public function testItHandlesFilenameWithOnlyVolume(): void { $testCases = [ [ @@ -197,7 +197,7 @@ class FilenameAnalyzerTest extends TestCase } } - public function test_it_handles_filename_with_only_chapter(): void + public function testItHandlesFilenameWithOnlyChapter(): void { $testCases = [ [ @@ -234,7 +234,7 @@ class FilenameAnalyzerTest extends TestCase } } - public function test_it_handles_full_dash_patterns(): void + public function testItHandlesFullDashPatterns(): void { $testCases = [ [ diff --git a/tests/Domain/Manga/Infrastructure/Service/MangadxChapterSynchronizationServiceTest.php b/tests/Domain/Manga/Infrastructure/Service/MangadxChapterSynchronizationServiceTest.php index 1523e93..93b9e12 100644 --- a/tests/Domain/Manga/Infrastructure/Service/MangadxChapterSynchronizationServiceTest.php +++ b/tests/Domain/Manga/Infrastructure/Service/MangadxChapterSynchronizationServiceTest.php @@ -57,7 +57,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase } /** - * Chapitres sans volume entre deux volumes différents → assignés au volume précédent + * Chapitres sans volume entre deux volumes différents → assignés au volume précédent. * * Ch1→Vol1, Ch2→null, Ch3→null, Ch4→Vol2 * Après sync : Ch2 et Ch3 doivent avoir Vol1 @@ -83,7 +83,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase } /** - * Chapitres en début de série sans volume → assignés au premier volume trouvé + * Chapitres en début de série sans volume → assignés au premier volume trouvé. * * Ch1→null, Ch2→null, Ch3→Vol1 * Après sync : Ch1 et Ch2 doivent avoir Vol1 @@ -107,7 +107,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase } /** - * Chapitres avec volumes explicites ne sont pas modifiés + * Chapitres avec volumes explicites ne sont pas modifiés. * * Ch1→Vol1, Ch2→Vol1, Ch3→Vol2 → inchangé */ @@ -130,7 +130,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase } /** - * La version française est prioritaire sur l'anglaise + * La version française est prioritaire sur l'anglaise. * * Même chapitre disponible EN (volume 1) et FR (volume 2) → FR gagne */ @@ -151,7 +151,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase } /** - * Seuls les nouveaux chapitres sont sauvegardés (pas les doublons) + * Seuls les nouveaux chapitres sont sauvegardés (pas les doublons). * * Ch1 déjà en DB + Ch2 nouveau → seul Ch2 est retourné */ @@ -188,6 +188,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase /** * @param Chapter[] $chapters + * * @return array */ private function indexedByNumber(array $chapters): array @@ -196,6 +197,7 @@ class MangadxChapterSynchronizationServiceTest extends TestCase foreach ($chapters as $chapter) { $result[$chapter->getNumber()] = $chapter; } + return $result; } } diff --git a/tests/Domain/Reader/Adapter/InMemoryChapterRepository.php b/tests/Domain/Reader/Adapter/InMemoryChapterRepository.php index 37ec69a..b263212 100644 --- a/tests/Domain/Reader/Adapter/InMemoryChapterRepository.php +++ b/tests/Domain/Reader/Adapter/InMemoryChapterRepository.php @@ -19,7 +19,7 @@ final class InMemoryChapterRepository implements ChapterRepositoryInterface { $this->chapters[$chapterId->getValue()] = [ 'pages' => $pages, - 'context' => $context + 'context' => $context, ]; } @@ -90,5 +90,4 @@ final class InMemoryChapterRepository implements ChapterRepositoryInterface return $nextChapter ? new ChapterId($nextChapter) : null; } - } diff --git a/tests/Domain/Scraping/Adapter/InMemoryChapterRepository.php b/tests/Domain/Scraping/Adapter/InMemoryChapterRepository.php index 0d91813..75cf8b7 100644 --- a/tests/Domain/Scraping/Adapter/InMemoryChapterRepository.php +++ b/tests/Domain/Scraping/Adapter/InMemoryChapterRepository.php @@ -3,8 +3,8 @@ namespace App\Tests\Domain\Scraping\Adapter; use App\Domain\Scraping\Domain\Contract\Repository\ChapterRepositoryInterface; -use App\Domain\Scraping\Domain\Model\Chapter; use App\Domain\Scraping\Domain\Exception\ChapterNotFoundException; +use App\Domain\Scraping\Domain\Model\Chapter; class InMemoryChapterRepository implements ChapterRepositoryInterface { @@ -31,8 +31,6 @@ class InMemoryChapterRepository implements ChapterRepositoryInterface $this->chapters[$chapter->id] = $chapter; } - - public function clear(): void { $this->chapters = []; diff --git a/tests/Domain/Scraping/Adapter/InMemoryImageStorage.php b/tests/Domain/Scraping/Adapter/InMemoryImageStorage.php index 0287476..3d60be0 100644 --- a/tests/Domain/Scraping/Adapter/InMemoryImageStorage.php +++ b/tests/Domain/Scraping/Adapter/InMemoryImageStorage.php @@ -11,7 +11,7 @@ class InMemoryImageStorage implements ImageStorageInterface public function storeChapterImages(string $targetId, array $localImagePaths): string { - $dir = '/fake/pages/' . $targetId; + $dir = '/fake/pages/'.$targetId; $this->stored[$targetId] = $dir; return $dir; @@ -19,7 +19,7 @@ class InMemoryImageStorage implements ImageStorageInterface public function extractFromCbz(string $targetId, string $cbzBinary): string { - $dir = '/fake/pages/' . $targetId; + $dir = '/fake/pages/'.$targetId; $this->stored[$targetId] = $dir; return $dir; diff --git a/tests/Domain/Scraping/Adapter/InMemorySourceRepository.php b/tests/Domain/Scraping/Adapter/InMemorySourceRepository.php index 503ac5f..e3d5387 100644 --- a/tests/Domain/Scraping/Adapter/InMemorySourceRepository.php +++ b/tests/Domain/Scraping/Adapter/InMemorySourceRepository.php @@ -5,7 +5,6 @@ namespace App\Tests\Domain\Scraping\Adapter; use App\Domain\Scraping\Domain\Contract\Repository\SourceRepositoryInterface; use App\Domain\Scraping\Domain\Model\Source; use App\Domain\Scraping\Domain\Model\ValueObject\SourceId; -use DateTimeImmutable; class InMemorySourceRepository implements SourceRepositoryInterface { @@ -24,11 +23,11 @@ class InMemorySourceRepository implements SourceRepositoryInterface 'nextPageSelector' => null, 'chapterUrlFormat' => 'https://example.com/manga/{slug}/chapter-{chapterNumber}', 'scrapingType' => 'html', - 'chapterSelector' => '.chapter-item' + 'chapterSelector' => '.chapter-item', ], true, - new DateTimeImmutable(), - new DateTimeImmutable() + new \DateTimeImmutable(), + new \DateTimeImmutable() ); } @@ -58,6 +57,7 @@ class InMemorySourceRepository implements SourceRepositoryInterface return false; } } + return true; } @@ -72,6 +72,7 @@ class InMemorySourceRepository implements SourceRepositoryInterface $sources[] = $this->sources[$sourceId]; } } + return $sources; } @@ -82,7 +83,7 @@ class InMemorySourceRepository implements SourceRepositoryInterface { return array_filter( array_values($this->sources), - fn(Source $source) => $source->isActive() + fn (Source $source) => $source->isActive() ); } diff --git a/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php b/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php index b2999c2..e963444 100644 --- a/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php +++ b/tests/Domain/Scraping/Application/CommandHandler/ScrapeChapterHandlerTest.php @@ -4,7 +4,6 @@ namespace App\Tests\Domain\Scraping\Application\CommandHandler; use App\Domain\Scraping\Application\Command\ScrapeChapter; use App\Domain\Scraping\Application\CommandHandler\ScrapeChapterHandler; -use App\Domain\Scraping\Domain\Event\ChapterScrapingFailed; use App\Domain\Scraping\Domain\Event\ChapterScrapingStarted; use App\Domain\Scraping\Domain\Model\Chapter; use App\Domain\Scraping\Domain\Model\ScrapingJob; diff --git a/tests/Domain/Shared/Adapter/InMemoryJobRepository.php b/tests/Domain/Shared/Adapter/InMemoryJobRepository.php index 118bf0b..e312042 100644 --- a/tests/Domain/Shared/Adapter/InMemoryJobRepository.php +++ b/tests/Domain/Shared/Adapter/InMemoryJobRepository.php @@ -24,12 +24,12 @@ class InMemoryJobRepository implements JobRepositoryInterface public function findByStatus(JobStatus $status): array { - return array_filter($this->jobs, fn(Job $job) => $job->status === $status); + return array_filter($this->jobs, fn (Job $job) => $job->status === $status); } public function findByType(string $type): array { - return array_filter($this->jobs, fn(Job $job) => $job->type === $type); + return array_filter($this->jobs, fn (Job $job) => $job->type === $type); } public function findPendingJobs(): array @@ -52,33 +52,34 @@ class InMemoryJobRepository implements JobRepositoryInterface $jobs = $this->jobs; if (isset($criteria['statuses']) && is_array($criteria['statuses']) && !empty($criteria['statuses'])) { - $jobs = array_filter($jobs, fn(Job $job) => in_array($job->status, $criteria['statuses'])); + $jobs = array_filter($jobs, fn (Job $job) => in_array($job->status, $criteria['statuses'])); } elseif (isset($criteria['status'])) { - $jobs = array_filter($jobs, fn(Job $job) => $job->status === $criteria['status']); + $jobs = array_filter($jobs, fn (Job $job) => $job->status === $criteria['status']); } if (isset($criteria['type'])) { - $jobs = array_filter($jobs, fn(Job $job) => $job->type === $criteria['type']); + $jobs = array_filter($jobs, fn (Job $job) => $job->type === $criteria['type']); } if (isset($criteria['createdAfter'])) { - $jobs = array_filter($jobs, fn(Job $job) => $job->createdAt >= $criteria['createdAfter']); + $jobs = array_filter($jobs, fn (Job $job) => $job->createdAt >= $criteria['createdAfter']); } if (isset($criteria['createdBefore'])) { - $jobs = array_filter($jobs, fn(Job $job) => $job->createdAt <= $criteria['createdBefore']); + $jobs = array_filter($jobs, fn (Job $job) => $job->createdAt <= $criteria['createdBefore']); } if (isset($criteria['sortBy'])) { - usort($jobs, function(Job $a, Job $b) use ($criteria) { + usort($jobs, function (Job $a, Job $b) use ($criteria) { $sortOrder = $criteria['sortOrder'] ?? 'ASC'; - $comparison = match($criteria['sortBy']) { + $comparison = match ($criteria['sortBy']) { 'createdAt' => $a->createdAt <=> $b->createdAt, 'startedAt' => ($a->startedAt ?? new \DateTimeImmutable()) <=> ($b->startedAt ?? new \DateTimeImmutable()), 'completedAt' => ($a->completedAt ?? new \DateTimeImmutable()) <=> ($b->completedAt ?? new \DateTimeImmutable()), - default => 0 + default => 0, }; - return $sortOrder === 'ASC' ? $comparison : -$comparison; + + return 'ASC' === $sortOrder ? $comparison : -$comparison; }); } diff --git a/tests/Domain/Shared/Application/CommandHandler/DeleteJobHandlerTest.php b/tests/Domain/Shared/Application/CommandHandler/DeleteJobHandlerTest.php index f7a10a2..7107270 100644 --- a/tests/Domain/Shared/Application/CommandHandler/DeleteJobHandlerTest.php +++ b/tests/Domain/Shared/Application/CommandHandler/DeleteJobHandlerTest.php @@ -22,7 +22,7 @@ class DeleteJobHandlerTest extends TestCase $this->handler = new DeleteJobHandler($this->repository); } - public function test_it_deletes_existing_job(): void + public function testItDeletesExistingJob(): void { $job = new ScrapingJob('job-1', 'manga-1', 1.0, 'source-1'); $this->repository->save($job); @@ -32,7 +32,7 @@ class DeleteJobHandlerTest extends TestCase $this->assertNull($this->repository->get('job-1')); } - public function test_it_throws_when_job_not_found(): void + public function testItThrowsWhenJobNotFound(): void { $this->expectException(JobNotFoundException::class); diff --git a/tests/Domain/Shared/Application/CommandHandler/DeleteJobsByCriteriaHandlerTest.php b/tests/Domain/Shared/Application/CommandHandler/DeleteJobsByCriteriaHandlerTest.php index bf68208..c4e6ed4 100644 --- a/tests/Domain/Shared/Application/CommandHandler/DeleteJobsByCriteriaHandlerTest.php +++ b/tests/Domain/Shared/Application/CommandHandler/DeleteJobsByCriteriaHandlerTest.php @@ -22,7 +22,7 @@ class DeleteJobsByCriteriaHandlerTest extends TestCase $this->handler = new DeleteJobsByCriteriaHandler($this->repository); } - public function test_it_deletes_jobs_by_status(): void + public function testItDeletesJobsByStatus(): void { $job1 = new ScrapingJob('job-1', 'manga-1', 1.0, 'source-1'); $job2 = new ScrapingJob('job-2', 'manga-1', 2.0, 'source-1'); @@ -43,7 +43,7 @@ class DeleteJobsByCriteriaHandlerTest extends TestCase $this->assertNotNull($this->repository->get('job-1')); } - public function test_it_deletes_jobs_by_type(): void + public function testItDeletesJobsByType(): void { $job1 = new ScrapingJob('job-1', 'manga-1', 1.0, 'source-1'); $job2 = new ScrapingJob('job-2', 'manga-1', 2.0, 'source-1'); @@ -58,7 +58,7 @@ class DeleteJobsByCriteriaHandlerTest extends TestCase $this->assertNull($this->repository->get('job-2')); } - public function test_it_does_nothing_when_no_criteria_match(): void + public function testItDoesNothingWhenNoCriteriaMatch(): void { $job = new ScrapingJob('job-1', 'manga-1', 1.0, 'source-1'); $this->repository->save($job); diff --git a/tests/Factory/ApiTokenFactory.php b/tests/Factory/ApiTokenFactory.php index 88b190e..7bf7cdd 100644 --- a/tests/Factory/ApiTokenFactory.php +++ b/tests/Factory/ApiTokenFactory.php @@ -4,30 +4,29 @@ namespace App\Tests\Factory; use App\Entity\ApiToken; use App\Repository\ApiTokenRepository; -use Zenstruck\Foundry\ModelFactory; -use Zenstruck\Foundry\Proxy; -use Zenstruck\Foundry\RepositoryProxy; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; +use Zenstruck\Foundry\Persistence\RepositoryDecorator; /** - * @extends ModelFactory + * @extends PersistentObjectFactory * - * @method ApiToken|Proxy create(array|callable $attributes = []) - * @method static ApiToken|Proxy createOne(array $attributes = []) - * @method static ApiToken|Proxy find(object|array|mixed $criteria) - * @method static ApiToken|Proxy findOrCreate(array $attributes) - * @method static ApiToken|Proxy first(string $sortedField = 'id') - * @method static ApiToken|Proxy last(string $sortedField = 'id') - * @method static ApiToken|Proxy random(array $attributes = []) - * @method static ApiToken|Proxy randomOrCreate(array $attributes = []) - * @method static ApiTokenRepository|RepositoryProxy repository() - * @method static ApiToken[]|Proxy[] all() - * @method static ApiToken[]|Proxy[] createMany(int $number, array|callable $attributes = []) - * @method static ApiToken[]|Proxy[] createSequence(iterable|callable $sequence) - * @method static ApiToken[]|Proxy[] findBy(array $attributes) - * @method static ApiToken[]|Proxy[] randomRange(int $min, int $max, array $attributes = []) - * @method static ApiToken[]|Proxy[] randomSet(int $number, array $attributes = []) + * @method ApiToken create(array|callable $attributes = []) + * @method static ApiToken createOne(array $attributes = []) + * @method static ApiToken find(object|array|mixed $criteria) + * @method static ApiToken findOrCreate(array $attributes) + * @method static ApiToken first(string $sortedField = 'id') + * @method static ApiToken last(string $sortedField = 'id') + * @method static ApiToken random(array $attributes = []) + * @method static ApiToken randomOrCreate(array $attributes = []) + * @method static ApiTokenRepository&RepositoryDecorator repository() + * @method static list all() + * @method static list createMany(int $number, array|callable $attributes = []) + * @method static list createSequence(iterable|callable $sequence) + * @method static list findBy(array $attributes) + * @method static list randomRange(int $min, int $max, array $attributes = []) + * @method static list randomSet(int $number, array $attributes = []) */ -final class ApiTokenFactory extends ModelFactory +final class ApiTokenFactory extends PersistentObjectFactory { /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services @@ -39,12 +38,17 @@ final class ApiTokenFactory extends ModelFactory parent::__construct(); } + public static function class(): string + { + return ApiToken::class; + } + /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories * * @todo add your default values here */ - protected function getDefaults(): array + protected function defaults(): array { return [ 'ownedBy' => UserFactory::new(), @@ -55,15 +59,10 @@ final class ApiTokenFactory extends ModelFactory /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization */ - protected function initialize(): self + protected function initialize(): static { return $this // ->afterInstantiate(function(ApiToken $apiToken): void {}) ; } - - protected static function getClass(): string - { - return ApiToken::class; - } } diff --git a/tests/Factory/ChapterFactory.php b/tests/Factory/ChapterFactory.php index 63ecc60..941d50f 100644 --- a/tests/Factory/ChapterFactory.php +++ b/tests/Factory/ChapterFactory.php @@ -4,30 +4,29 @@ namespace App\Tests\Factory; use App\Entity\Chapter; use App\Repository\ChapterRepository; -use Zenstruck\Foundry\ModelFactory; -use Zenstruck\Foundry\Proxy; -use Zenstruck\Foundry\RepositoryProxy; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; +use Zenstruck\Foundry\Persistence\RepositoryDecorator; /** - * @extends ModelFactory + * @extends PersistentObjectFactory * - * @method Chapter|Proxy create(array|callable $attributes = []) - * @method static Chapter|Proxy createOne(array $attributes = []) - * @method static Chapter|Proxy find(object|array|mixed $criteria) - * @method static Chapter|Proxy findOrCreate(array $attributes) - * @method static Chapter|Proxy first(string $sortedField = 'id') - * @method static Chapter|Proxy last(string $sortedField = 'id') - * @method static Chapter|Proxy random(array $attributes = []) - * @method static Chapter|Proxy randomOrCreate(array $attributes = []) - * @method static ChapterRepository|RepositoryProxy repository() - * @method static Chapter[]|Proxy[] all() - * @method static Chapter[]|Proxy[] createMany(int $number, array|callable $attributes = []) - * @method static Chapter[]|Proxy[] createSequence(iterable|callable $sequence) - * @method static Chapter[]|Proxy[] findBy(array $attributes) - * @method static Chapter[]|Proxy[] randomRange(int $min, int $max, array $attributes = []) - * @method static Chapter[]|Proxy[] randomSet(int $number, array $attributes = []) + * @method Chapter create(array|callable $attributes = []) + * @method static Chapter createOne(array $attributes = []) + * @method static Chapter find(object|array|mixed $criteria) + * @method static Chapter findOrCreate(array $attributes) + * @method static Chapter first(string $sortedField = 'id') + * @method static Chapter last(string $sortedField = 'id') + * @method static Chapter random(array $attributes = []) + * @method static Chapter randomOrCreate(array $attributes = []) + * @method static ChapterRepository&RepositoryDecorator repository() + * @method static list all() + * @method static list createMany(int $number, array|callable $attributes = []) + * @method static list createSequence(iterable|callable $sequence) + * @method static list findBy(array $attributes) + * @method static list randomRange(int $min, int $max, array $attributes = []) + * @method static list randomSet(int $number, array $attributes = []) */ -final class ChapterFactory extends ModelFactory +final class ChapterFactory extends PersistentObjectFactory { /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services @@ -39,12 +38,17 @@ final class ChapterFactory extends ModelFactory parent::__construct(); } + public static function class(): string + { + return Chapter::class; + } + /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories * * @todo add your default values here */ - protected function getDefaults(): array + protected function defaults(): array { return [ 'manga' => MangaFactory::new(), @@ -61,15 +65,10 @@ final class ChapterFactory extends ModelFactory /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization */ - protected function initialize(): self + protected function initialize(): static { return $this // ->afterInstantiate(function(Chapter $chapter): void {}) ; } - - protected static function getClass(): string - { - return Chapter::class; - } } diff --git a/tests/Factory/MangaFactory.php b/tests/Factory/MangaFactory.php index 3934453..3c7bef0 100644 --- a/tests/Factory/MangaFactory.php +++ b/tests/Factory/MangaFactory.php @@ -4,42 +4,46 @@ namespace App\Tests\Factory; use App\Entity\Manga; use App\Repository\MangaRepository; -use Zenstruck\Foundry\ModelFactory; -use Zenstruck\Foundry\Proxy; -use Zenstruck\Foundry\RepositoryProxy; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; +use Zenstruck\Foundry\Persistence\RepositoryDecorator; /** - * @extends ModelFactory + * @extends PersistentObjectFactory * - * @method Manga|Proxy create(array|callable $attributes = []) - * @method static Manga|Proxy createOne(array $attributes = []) - * @method static Manga|Proxy find(object|array|mixed $criteria) - * @method static Manga|Proxy findOrCreate(array $attributes) - * @method static Manga|Proxy first(string $sortedField = 'id') - * @method static Manga|Proxy last(string $sortedField = 'id') - * @method static Manga|Proxy random(array $attributes = []) - * @method static Manga|Proxy randomOrCreate(array $attributes = []) - * @method static MangaRepository|RepositoryProxy repository() - * @method static Manga[]|Proxy[] all() - * @method static Manga[]|Proxy[] createMany(int $number, array|callable $attributes = []) - * @method static Manga[]|Proxy[] createSequence(iterable|callable $sequence) - * @method static Manga[]|Proxy[] findBy(array $attributes) - * @method static Manga[]|Proxy[] randomRange(int $min, int $max, array $attributes = []) - * @method static Manga[]|Proxy[] randomSet(int $number, array $attributes = []) + * @method Manga create(array|callable $attributes = []) + * @method static Manga createOne(array $attributes = []) + * @method static Manga find(object|array|mixed $criteria) + * @method static Manga findOrCreate(array $attributes) + * @method static Manga first(string $sortedField = 'id') + * @method static Manga last(string $sortedField = 'id') + * @method static Manga random(array $attributes = []) + * @method static Manga randomOrCreate(array $attributes = []) + * @method static MangaRepository&RepositoryDecorator repository() + * @method static list all() + * @method static list createMany(int $number, array|callable $attributes = []) + * @method static list createSequence(iterable|callable $sequence) + * @method static list findBy(array $attributes) + * @method static list randomRange(int $min, int $max, array $attributes = []) + * @method static list randomSet(int $number, array $attributes = []) */ -final class MangaFactory extends ModelFactory +final class MangaFactory extends PersistentObjectFactory { /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services * * @todo inject services if required */ + public static function class(): string + { + return Manga::class; + } + /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories * * @todo add your default values here */ - protected function getDefaults(): array + protected function defaults(): array { $title = self::faker()->words(rand(1, 3), true); @@ -64,15 +68,10 @@ final class MangaFactory extends ModelFactory /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization */ - protected function initialize(): self + protected function initialize(): static { return $this // ->afterInstantiate(function(Manga $manga): void {}) ; } - - protected static function getClass(): string - { - return Manga::class; - } } diff --git a/tests/Factory/PageFactory.php b/tests/Factory/PageFactory.php index 1f57ef0..1d11c63 100644 --- a/tests/Factory/PageFactory.php +++ b/tests/Factory/PageFactory.php @@ -4,30 +4,29 @@ namespace App\Tests\Factory; use App\Entity\Page; use App\Repository\PageRepository; -use Zenstruck\Foundry\ModelFactory; -use Zenstruck\Foundry\Proxy; -use Zenstruck\Foundry\RepositoryProxy; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; +use Zenstruck\Foundry\Persistence\RepositoryDecorator; /** - * @extends ModelFactory + * @extends PersistentObjectFactory * - * @method Page|Proxy create(array|callable $attributes = []) - * @method static Page|Proxy createOne(array $attributes = []) - * @method static Page|Proxy find(object|array|mixed $criteria) - * @method static Page|Proxy findOrCreate(array $attributes) - * @method static Page|Proxy first(string $sortedField = 'id') - * @method static Page|Proxy last(string $sortedField = 'id') - * @method static Page|Proxy random(array $attributes = []) - * @method static Page|Proxy randomOrCreate(array $attributes = []) - * @method static PageRepository|RepositoryProxy repository() - * @method static Page[]|Proxy[] all() - * @method static Page[]|Proxy[] createMany(int $number, array|callable $attributes = []) - * @method static Page[]|Proxy[] createSequence(iterable|callable $sequence) - * @method static Page[]|Proxy[] findBy(array $attributes) - * @method static Page[]|Proxy[] randomRange(int $min, int $max, array $attributes = []) - * @method static Page[]|Proxy[] randomSet(int $number, array $attributes = []) + * @method Page create(array|callable $attributes = []) + * @method static Page createOne(array $attributes = []) + * @method static Page find(object|array|mixed $criteria) + * @method static Page findOrCreate(array $attributes) + * @method static Page first(string $sortedField = 'id') + * @method static Page last(string $sortedField = 'id') + * @method static Page random(array $attributes = []) + * @method static Page randomOrCreate(array $attributes = []) + * @method static PageRepository&RepositoryDecorator repository() + * @method static list all() + * @method static list createMany(int $number, array|callable $attributes = []) + * @method static list createSequence(iterable|callable $sequence) + * @method static list findBy(array $attributes) + * @method static list randomRange(int $min, int $max, array $attributes = []) + * @method static list randomSet(int $number, array $attributes = []) */ -final class PageFactory extends ModelFactory +final class PageFactory extends PersistentObjectFactory { /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services @@ -39,12 +38,17 @@ final class PageFactory extends ModelFactory parent::__construct(); } + public static function class(): string + { + return Page::class; + } + /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories * * @todo add your default values here */ - protected function getDefaults(): array + protected function defaults(): array { return [ 'chapter' => ChapterFactory::new(), @@ -57,15 +61,10 @@ final class PageFactory extends ModelFactory /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization */ - protected function initialize(): self + protected function initialize(): static { return $this // ->afterInstantiate(function(Page $page): void {}) ; } - - protected static function getClass(): string - { - return Page::class; - } } diff --git a/tests/Factory/SourceFactory.php b/tests/Factory/SourceFactory.php index 7846afb..6b4a9f4 100644 --- a/tests/Factory/SourceFactory.php +++ b/tests/Factory/SourceFactory.php @@ -4,30 +4,29 @@ namespace App\Tests\Factory; use App\Entity\Source; use App\Repository\SourceRepository; -use Zenstruck\Foundry\ModelFactory; -use Zenstruck\Foundry\Proxy; -use Zenstruck\Foundry\RepositoryProxy; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; +use Zenstruck\Foundry\Persistence\RepositoryDecorator; /** - * @extends ModelFactory + * @extends PersistentObjectFactory * - * @method Source|Proxy create(array|callable $attributes = []) - * @method static Source|Proxy createOne(array $attributes = []) - * @method static Source|Proxy find(object|array|mixed $criteria) - * @method static Source|Proxy findOrCreate(array $attributes) - * @method static Source|Proxy first(string $sortedField = 'id') - * @method static Source|Proxy last(string $sortedField = 'id') - * @method static Source|Proxy random(array $attributes = []) - * @method static Source|Proxy randomOrCreate(array $attributes = []) - * @method static SourceRepository|RepositoryProxy repository() - * @method static Source[]|Proxy[] all() - * @method static Source[]|Proxy[] createMany(int $number, array|callable $attributes = []) - * @method static Source[]|Proxy[] createSequence(iterable|callable $sequence) - * @method static Source[]|Proxy[] findBy(array $attributes) - * @method static Source[]|Proxy[] randomRange(int $min, int $max, array $attributes = []) - * @method static Source[]|Proxy[] randomSet(int $number, array $attributes = []) + * @method Source create(array|callable $attributes = []) + * @method static Source createOne(array $attributes = []) + * @method static Source find(object|array|mixed $criteria) + * @method static Source findOrCreate(array $attributes) + * @method static Source first(string $sortedField = 'id') + * @method static Source last(string $sortedField = 'id') + * @method static Source random(array $attributes = []) + * @method static Source randomOrCreate(array $attributes = []) + * @method static SourceRepository&RepositoryDecorator repository() + * @method static list all() + * @method static list createMany(int $number, array|callable $attributes = []) + * @method static list createSequence(iterable|callable $sequence) + * @method static list findBy(array $attributes) + * @method static list randomRange(int $min, int $max, array $attributes = []) + * @method static list randomSet(int $number, array $attributes = []) */ -final class SourceFactory extends ModelFactory +final class SourceFactory extends PersistentObjectFactory { /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services @@ -39,12 +38,17 @@ final class SourceFactory extends ModelFactory parent::__construct(); } + public static function class(): string + { + return Source::class; + } + /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories * * @todo add your default values here */ - protected function getDefaults(): array + protected function defaults(): array { return [ 'name' => self::faker()->optional()->company(), @@ -60,15 +64,10 @@ final class SourceFactory extends ModelFactory /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization */ - protected function initialize(): self + protected function initialize(): static { return $this // ->afterInstantiate(function(Source $source): void {}) ; } - - protected static function getClass(): string - { - return Source::class; - } } diff --git a/tests/Factory/UserFactory.php b/tests/Factory/UserFactory.php index 2ddf70b..3c33e1e 100644 --- a/tests/Factory/UserFactory.php +++ b/tests/Factory/UserFactory.php @@ -5,48 +5,50 @@ namespace App\Tests\Factory; use App\Entity\User; use App\Repository\UserRepository; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; -use Zenstruck\Foundry\ModelFactory; -use Zenstruck\Foundry\Proxy; -use Zenstruck\Foundry\RepositoryProxy; +use Zenstruck\Foundry\Persistence\PersistentObjectFactory; +use Zenstruck\Foundry\Persistence\RepositoryDecorator; /** - * @extends ModelFactory + * @extends PersistentObjectFactory * - * @method User|Proxy create(array|callable $attributes = []) - * @method static User|Proxy createOne(array $attributes = []) - * @method static User|Proxy find(object|array|mixed $criteria) - * @method static User|Proxy findOrCreate(array $attributes) - * @method static User|Proxy first(string $sortedField = 'id') - * @method static User|Proxy last(string $sortedField = 'id') - * @method static User|Proxy random(array $attributes = []) - * @method static User|Proxy randomOrCreate(array $attributes = []) - * @method static UserRepository|RepositoryProxy repository() - * @method static User[]|Proxy[] all() - * @method static User[]|Proxy[] createMany(int $number, array|callable $attributes = []) - * @method static User[]|Proxy[] createSequence(iterable|callable $sequence) - * @method static User[]|Proxy[] findBy(array $attributes) - * @method static User[]|Proxy[] randomRange(int $min, int $max, array $attributes = []) - * @method static User[]|Proxy[] randomSet(int $number, array $attributes = []) + * @method User create(array|callable $attributes = []) + * @method static User createOne(array $attributes = []) + * @method static User find(object|array|mixed $criteria) + * @method static User findOrCreate(array $attributes) + * @method static User first(string $sortedField = 'id') + * @method static User last(string $sortedField = 'id') + * @method static User random(array $attributes = []) + * @method static User randomOrCreate(array $attributes = []) + * @method static UserRepository&RepositoryDecorator repository() + * @method static list all() + * @method static list createMany(int $number, array|callable $attributes = []) + * @method static list createSequence(iterable|callable $sequence) + * @method static list findBy(array $attributes) + * @method static list randomRange(int $min, int $max, array $attributes = []) + * @method static list randomSet(int $number, array $attributes = []) */ -final class UserFactory extends ModelFactory +final class UserFactory extends PersistentObjectFactory { - public const array FIRST_NAMES = ["ALAIN", "ALEXANDRE", "ANDRÉ", "ANNIE", "ANTHONY", "AUDREY", "AURÉLIE", "BERNARD", "BRIGITTE", "BRUNO", "CATHERINE", "CEDRIC", "CHANTAL", "CHRISTELLE", "CHRISTIAN", "CHRISTIANE", "CHRISTINE", "CHRISTOPHE", "CLAUDE", "CORINNE", "CÉLINE", "DANIEL", "DANIELLE", "DAVID", "DENISE", "DIDIER", "DOMINIQUE", "ELODIE", "EMILIE", "ENZO", "ERIC", "FABRICE", "FLORENCE", "FRANCK", "FRANÇOISE", "FRÉDÉRIC", "GEORGES", "GERMAINE", "GUILLAUME", "GUY", "GÉRARD", "HENRI", "ISABELLE", "JACQUELINE", "JACQUES", "JEAN", "JEAN-CLAUDE", "JEAN-PIERRE", "JEANNE", "JEANNINE", "JEREMY", "JEROME", "JONATHAN", "JOSEPH", "JULIE", "JULIEN", "KARINE", "KEVIN", "LAETITIA", "LAURA", "LAURENCE", "LAURENT", "LOUIS", "LUCAS", "LÉA", "MADELEINE", "MANON", "MARCEL", "MARCELLE", "MARGUERITE", "MARIE", "MARINE", "MARTINE", "MAURICE", "MAXIME", "MICHEL", "MICHÈLE", "MONIQUE", "NATHALIE", "NICOLAS", "NICOLE", "ODETTE", "OLIVIER", "PASCAL", "PASCALE", "PATRICIA", "PATRICK", "PAUL", "PAULETTE", "PHILIPPE", "PIERRE", "RENÉ", "ROBERT", "ROGER", "ROMAIN", "SANDRA", "SANDRINE", "SERGE", "SOPHIE", "STÉPHANE", "STÉPHANIE", "SUZANNE", "SYLVIE", "SÉBASTIEN", "THIERRY", "THOMAS", "THÉO", "VALÉRIE", "VIRGINIE", "VÉRONIQUE", "YVETTE", "YVONNE"]; - public const array LAST_NAMES = ["Adam", "Andre", "Antoine", "Arnaud", "Aubert", "Aubry", "Bailly", "Barbier", "Baron", "Barre", "Barthelemy", "Benard", "Benoit", "Berger", "Bernard", "Bertin", "Bertrand", "Besson", "Blanc", "Blanchard", "Bonnet", "Boucher", "Bouchet", "Boulanger", "Bourgeois", "Bouvier", "Boyer", "Breton", "Brun", "Brunet", "Carlier", "Caron", "Carpentier", "Carre", "Charles", "Charpentier", "Chauvin", "Chevalier", "Chevallier", "Clement", "Colin", "Collet", "Collin", "Cordier", "Cousin", "Da Silva", "Daniel", "David", "Delaunay", "Denis", "Deschamps", "Dubois", "Dufour", "Dumas", "Dumont", "Dupont", "Dupuis", "Dupuy", "Durand", "Duval", "Etienne", "Fabre", "Faure", "Fernandez", "Fleury", "Fontaine", "Fournier", "Francois", "Gaillard", "Garcia", "Garnier", "Gauthier", "Gautier", "Gay", "Gerard", "Germain", "Gilbert", "Gillet", "Girard", "Giraud", "Gonzalez", "Grondin", "Guerin", "Guichard", "Guillaume", "Guillot", "Guyot", "Hamon", "Henry", "Herve", "Hoarau", "Hubert", "Huet", "Humbert", "Jacob", "Jacquet", "Jean", "Joly", "Julien", "Klein", "Lacroix", "Lambert", "Lamy", "Langlois", "Laporte", "Laurent", "Le Gall", "Le Goff", "Le Roux", "Leblanc", "Lebrun", "Leclerc", "Leclercq", "Lecomte", "Lefebvre", "Lefevre", "Leger", "Legrand", "Lejeune", "Lemaire", "Lemaitre", "Lemoine", "Leroux", "Leroy", "Leveque", "Lopez", "Louis", "Lucas", "Maillard", "Mallet", "Marchal", "Marchand", "Marechal", "Marie", "Martin", "Martinez", "Marty", "Masson", "Mathieu", "Menard", "Mercier", "Meunier", "Meyer", "Michaud", "Michel", "Millet", "Monnier", "Moreau", "Morel", "Morin", "Moulin", "Muller", "Nicolas", "Noel", "Olivier", "Paris", "Pasquier", "Payet", "Pelletier", "Perez", "Perret", "Perrier", "Perrin", "Perrot", "Petit", "Philippe", "Picard", "Pichon", "Pierre", "Poirier", "Poulain", "Prevost", "Remy", "Renard", "Renaud", "Renault", "Rey", "Reynaud", "Richard", "Riviere", "Robert", "Robin", "Roche", "Rodriguez", "Roger", "Rolland", "Rousseau", "Roussel", "Roux", "Roy", "Royer", "Sanchez", "Schmitt", "Schneider", "Simon", "Tessier", "Thomas", "Vasseur", "Vidal", "Vincent", "Weber"]; + public const array FIRST_NAMES = ['ALAIN', 'ALEXANDRE', 'ANDRÉ', 'ANNIE', 'ANTHONY', 'AUDREY', 'AURÉLIE', 'BERNARD', 'BRIGITTE', 'BRUNO', 'CATHERINE', 'CEDRIC', 'CHANTAL', 'CHRISTELLE', 'CHRISTIAN', 'CHRISTIANE', 'CHRISTINE', 'CHRISTOPHE', 'CLAUDE', 'CORINNE', 'CÉLINE', 'DANIEL', 'DANIELLE', 'DAVID', 'DENISE', 'DIDIER', 'DOMINIQUE', 'ELODIE', 'EMILIE', 'ENZO', 'ERIC', 'FABRICE', 'FLORENCE', 'FRANCK', 'FRANÇOISE', 'FRÉDÉRIC', 'GEORGES', 'GERMAINE', 'GUILLAUME', 'GUY', 'GÉRARD', 'HENRI', 'ISABELLE', 'JACQUELINE', 'JACQUES', 'JEAN', 'JEAN-CLAUDE', 'JEAN-PIERRE', 'JEANNE', 'JEANNINE', 'JEREMY', 'JEROME', 'JONATHAN', 'JOSEPH', 'JULIE', 'JULIEN', 'KARINE', 'KEVIN', 'LAETITIA', 'LAURA', 'LAURENCE', 'LAURENT', 'LOUIS', 'LUCAS', 'LÉA', 'MADELEINE', 'MANON', 'MARCEL', 'MARCELLE', 'MARGUERITE', 'MARIE', 'MARINE', 'MARTINE', 'MAURICE', 'MAXIME', 'MICHEL', 'MICHÈLE', 'MONIQUE', 'NATHALIE', 'NICOLAS', 'NICOLE', 'ODETTE', 'OLIVIER', 'PASCAL', 'PASCALE', 'PATRICIA', 'PATRICK', 'PAUL', 'PAULETTE', 'PHILIPPE', 'PIERRE', 'RENÉ', 'ROBERT', 'ROGER', 'ROMAIN', 'SANDRA', 'SANDRINE', 'SERGE', 'SOPHIE', 'STÉPHANE', 'STÉPHANIE', 'SUZANNE', 'SYLVIE', 'SÉBASTIEN', 'THIERRY', 'THOMAS', 'THÉO', 'VALÉRIE', 'VIRGINIE', 'VÉRONIQUE', 'YVETTE', 'YVONNE']; + public const array LAST_NAMES = ['Adam', 'Andre', 'Antoine', 'Arnaud', 'Aubert', 'Aubry', 'Bailly', 'Barbier', 'Baron', 'Barre', 'Barthelemy', 'Benard', 'Benoit', 'Berger', 'Bernard', 'Bertin', 'Bertrand', 'Besson', 'Blanc', 'Blanchard', 'Bonnet', 'Boucher', 'Bouchet', 'Boulanger', 'Bourgeois', 'Bouvier', 'Boyer', 'Breton', 'Brun', 'Brunet', 'Carlier', 'Caron', 'Carpentier', 'Carre', 'Charles', 'Charpentier', 'Chauvin', 'Chevalier', 'Chevallier', 'Clement', 'Colin', 'Collet', 'Collin', 'Cordier', 'Cousin', 'Da Silva', 'Daniel', 'David', 'Delaunay', 'Denis', 'Deschamps', 'Dubois', 'Dufour', 'Dumas', 'Dumont', 'Dupont', 'Dupuis', 'Dupuy', 'Durand', 'Duval', 'Etienne', 'Fabre', 'Faure', 'Fernandez', 'Fleury', 'Fontaine', 'Fournier', 'Francois', 'Gaillard', 'Garcia', 'Garnier', 'Gauthier', 'Gautier', 'Gay', 'Gerard', 'Germain', 'Gilbert', 'Gillet', 'Girard', 'Giraud', 'Gonzalez', 'Grondin', 'Guerin', 'Guichard', 'Guillaume', 'Guillot', 'Guyot', 'Hamon', 'Henry', 'Herve', 'Hoarau', 'Hubert', 'Huet', 'Humbert', 'Jacob', 'Jacquet', 'Jean', 'Joly', 'Julien', 'Klein', 'Lacroix', 'Lambert', 'Lamy', 'Langlois', 'Laporte', 'Laurent', 'Le Gall', 'Le Goff', 'Le Roux', 'Leblanc', 'Lebrun', 'Leclerc', 'Leclercq', 'Lecomte', 'Lefebvre', 'Lefevre', 'Leger', 'Legrand', 'Lejeune', 'Lemaire', 'Lemaitre', 'Lemoine', 'Leroux', 'Leroy', 'Leveque', 'Lopez', 'Louis', 'Lucas', 'Maillard', 'Mallet', 'Marchal', 'Marchand', 'Marechal', 'Marie', 'Martin', 'Martinez', 'Marty', 'Masson', 'Mathieu', 'Menard', 'Mercier', 'Meunier', 'Meyer', 'Michaud', 'Michel', 'Millet', 'Monnier', 'Moreau', 'Morel', 'Morin', 'Moulin', 'Muller', 'Nicolas', 'Noel', 'Olivier', 'Paris', 'Pasquier', 'Payet', 'Pelletier', 'Perez', 'Perret', 'Perrier', 'Perrin', 'Perrot', 'Petit', 'Philippe', 'Picard', 'Pichon', 'Pierre', 'Poirier', 'Poulain', 'Prevost', 'Remy', 'Renard', 'Renaud', 'Renault', 'Rey', 'Reynaud', 'Richard', 'Riviere', 'Robert', 'Robin', 'Roche', 'Rodriguez', 'Roger', 'Rolland', 'Rousseau', 'Roussel', 'Roux', 'Roy', 'Royer', 'Sanchez', 'Schmitt', 'Schneider', 'Simon', 'Tessier', 'Thomas', 'Vasseur', 'Vidal', 'Vincent', 'Weber']; /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services - * */ public function __construct(private readonly UserPasswordHasherInterface $passwordHasher) { parent::__construct(); } + public static function class(): string + { + return User::class; + } + /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories - * */ - protected function getDefaults(): array + protected function defaults(): array { return [ 'email' => self::faker()->unique()->email(), @@ -60,7 +62,7 @@ final class UserFactory extends ModelFactory /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization */ - protected function initialize(): self + protected function initialize(): static { return $this ->afterInstantiate(function (User $user): void { @@ -71,9 +73,4 @@ final class UserFactory extends ModelFactory }) ; } - - protected static function getClass(): string - { - return User::class; - } } diff --git a/tests/Feature/Conversion/ConvertFileTest.php b/tests/Feature/Conversion/ConvertFileTest.php index 49301e8..a17e8c6 100644 --- a/tests/Feature/Conversion/ConvertFileTest.php +++ b/tests/Feature/Conversion/ConvertFileTest.php @@ -32,7 +32,7 @@ class ConvertFileTest extends AbstractApiTestCase 'files' => [ 'file' => $uploadedFile, ], - ] + ], ]); // Then - Vérifier la réponse @@ -84,7 +84,7 @@ class ConvertFileTest extends AbstractApiTestCase 'files' => [ 'file' => $uploadedFile, ], - ] + ], ]); // Then - Vérifier l'erreur de validation @@ -121,7 +121,7 @@ class ConvertFileTest extends AbstractApiTestCase 'files' => [ 'file' => $uploadedFile, ], - ] + ], ]); // Then - Vérifier l'erreur de validation @@ -140,18 +140,18 @@ class ConvertFileTest extends AbstractApiTestCase // Pour les tests, on peut simplement créer un fichier avec une extension .cbr // En production, ce serait un vrai fichier RAR - $newPath = $tempFile . '.cbr'; + $newPath = $tempFile.'.cbr'; rename($tempFile, $newPath); // Utiliser un fichier CBZ existant comme base et le renommer en CBR pour les tests - $existingCbzFile = __DIR__ . '/../../Fixtures/chapter.cbz'; + $existingCbzFile = __DIR__.'/../../Fixtures/chapter.cbz'; if (file_exists($existingCbzFile)) { copy($existingCbzFile, $newPath); } else { // Fallback: créer un fichier ZIP simple avec extension CBR // Un fichier CBR est essentiellement un fichier RAR, mais pour les tests on peut simuler avec un ZIP $zip = new \ZipArchive(); - if ($zip->open($newPath, \ZipArchive::CREATE) === TRUE) { + if (true === $zip->open($newPath, \ZipArchive::CREATE)) { $zip->addFromString('test.txt', 'Test content for CBR simulation'); $zip->close(); } diff --git a/tests/Feature/Manga/CreateMangaDirectlyTest.php b/tests/Feature/Manga/CreateMangaDirectlyTest.php index 7078202..b4d9d1d 100644 --- a/tests/Feature/Manga/CreateMangaDirectlyTest.php +++ b/tests/Feature/Manga/CreateMangaDirectlyTest.php @@ -2,7 +2,6 @@ namespace App\Tests\Feature\Manga; -use App\Domain\Manga\Domain\Contract\Service\ImageProcessorInterface; use App\Tests\Feature\AbstractApiTestCase; use Zenstruck\Foundry\Test\ResetDatabase; @@ -25,17 +24,17 @@ class CreateMangaDirectlyTest extends AbstractApiTestCase 'status' => 'ongoing', 'externalId' => 'external-123', 'imageUrl' => 'http://example.com/image.jpg', - 'rating' => 4.5 - ] + 'rating' => 4.5, + ], ]); // Then $this->assertResponseIsSuccessful(); - + // Verify the manga was created in database $entityManager = static::getContainer()->get('doctrine')->getManager(); $manga = $entityManager->getRepository(\App\Entity\Manga::class)->findOneBy(['slug' => 'one-piece']); - + $this->assertNotNull($manga); $this->assertEquals('One Piece', $manga->getTitle()); $this->assertEquals('Test description', $manga->getDescription()); @@ -64,16 +63,16 @@ class CreateMangaDirectlyTest extends AbstractApiTestCase 'status' => 'ongoing', 'externalId' => 'external-123', 'imageUrl' => null, - 'rating' => 4.5 - ] + 'rating' => 4.5, + ], ]); // Then $this->assertResponseIsSuccessful(); - + $entityManager = static::getContainer()->get('doctrine')->getManager(); $manga = $entityManager->getRepository(\App\Entity\Manga::class)->findOneBy(['slug' => 'one-piece']); - + $this->assertNotNull($manga); $this->assertNull($manga->getImageUrl()); $this->assertNull($manga->getThumbnailUrl()); @@ -87,14 +86,14 @@ class CreateMangaDirectlyTest extends AbstractApiTestCase 'json' => [ 'title' => '', // Invalid: empty title 'publicationYear' => 'invalid', // Invalid: not a number - ] + ], ]); // Then $this->assertResponseStatusCodeSame(400); $this->assertJsonContains([ - 'hydra:title' => 'An error occurred', - 'hydra:description' => 'The type of the "publicationYear" attribute must be "int", "string" given.' + 'title' => 'An error occurred', + 'detail' => 'The type of the "publicationYear" attribute must be "int", "string" given.', ]); } @@ -113,16 +112,16 @@ class CreateMangaDirectlyTest extends AbstractApiTestCase 'status' => 'ongoing', 'externalId' => 'external-123', 'imageUrl' => null, - 'rating' => 4.5 - ] + 'rating' => 4.5, + ], ]); // Then $this->assertResponseStatusCodeSame(422); $this->assertJsonContains([ - 'hydra:title' => 'An error occurred', - 'hydra:description' => 'publicationYear: L\'année de publication doit être comprise entre 1900 et 2100', - 'title' => 'An error occurred' + 'title' => 'An error occurred', + 'detail' => 'publicationYear: L\'année de publication doit être comprise entre 1900 et 2100', + 'title' => 'An error occurred', ]); } -} \ No newline at end of file +} diff --git a/tests/Feature/Manga/DeleteCbzTest.php b/tests/Feature/Manga/DeleteCbzTest.php index e98d026..fad5c49 100644 --- a/tests/Feature/Manga/DeleteCbzTest.php +++ b/tests/Feature/Manga/DeleteCbzTest.php @@ -6,20 +6,20 @@ use App\Entity\Chapter; use App\Tests\Factory\ChapterFactory; use App\Tests\Factory\MangaFactory; use App\Tests\Feature\AbstractApiTestCase; -use Symfony\Component\HttpFoundation\Response; use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; class DeleteCbzTest extends AbstractApiTestCase { - use ResetDatabase, Factories; + use ResetDatabase; + use Factories; - public function test_it_deletes_chapter_cbz_file(): void + public function testItDeletesChapterCbzFile(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'One Piece', - 'slug' => 'one-piece' + 'slug' => 'one-piece', ]); $chapter = ChapterFactory::createOne([ @@ -27,7 +27,7 @@ class DeleteCbzTest extends AbstractApiTestCase 'number' => 1.0, 'title' => 'Chapter 1', 'visible' => true, - 'cbzPath' => '/path/to/test.cbz' + 'cbzPath' => '/path/to/test.cbz', ]); $this->entityManager->flush(); @@ -45,7 +45,7 @@ class DeleteCbzTest extends AbstractApiTestCase $this->assertEmpty($freshChapter->getCbzPath()); } - public function test_it_returns_404_for_non_existent_chapter(): void + public function testItReturns404ForNonExistentChapter(): void { // When static::createClient()->request('DELETE', '/api/manga/chapters/999999/cbz'); @@ -54,12 +54,12 @@ class DeleteCbzTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(404); } - public function test_it_returns_404_for_chapter_without_cbz(): void + public function testItReturns404ForChapterWithoutCbz(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'Test Manga', - 'slug' => 'test-manga' + 'slug' => 'test-manga', ]); $chapter = ChapterFactory::createOne([ @@ -67,7 +67,7 @@ class DeleteCbzTest extends AbstractApiTestCase 'number' => 1.0, 'title' => 'Test Chapter', 'visible' => true, - 'cbzPath' => null // No CBZ file + 'cbzPath' => null, // No CBZ file ]); $this->entityManager->flush(); @@ -80,12 +80,12 @@ class DeleteCbzTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(404); } - public function test_it_returns_404_for_invisible_chapter(): void + public function testItReturns404ForInvisibleChapter(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'Test Manga', - 'slug' => 'test-manga' + 'slug' => 'test-manga', ]); $chapter = ChapterFactory::createOne([ @@ -93,7 +93,7 @@ class DeleteCbzTest extends AbstractApiTestCase 'number' => 1.0, 'title' => 'Test Chapter', 'visible' => false, // Invisible chapter - 'cbzPath' => '/path/to/test.cbz' + 'cbzPath' => '/path/to/test.cbz', ]); $this->entityManager->flush(); diff --git a/tests/Feature/Manga/DeleteChapterTest.php b/tests/Feature/Manga/DeleteChapterTest.php index 13aeae0..2de7488 100644 --- a/tests/Feature/Manga/DeleteChapterTest.php +++ b/tests/Feature/Manga/DeleteChapterTest.php @@ -6,27 +6,27 @@ use App\Entity\Chapter; use App\Tests\Factory\ChapterFactory; use App\Tests\Factory\MangaFactory; use App\Tests\Feature\AbstractApiTestCase; -use Symfony\Component\HttpFoundation\Response; use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; class DeleteChapterTest extends AbstractApiTestCase { - use ResetDatabase, Factories; + use ResetDatabase; + use Factories; - public function test_it_soft_deletes_chapter(): void + public function testItSoftDeletesChapter(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'One Piece', - 'slug' => 'one-piece' + 'slug' => 'one-piece', ]); $chapter = ChapterFactory::createOne([ 'manga' => $manga, 'number' => 1.0, 'title' => 'Chapter 1', - 'visible' => true + 'visible' => true, ]); $chapterId = $chapter->getId(); @@ -42,7 +42,7 @@ class DeleteChapterTest extends AbstractApiTestCase $this->assertFalse($freshChapter->isVisible()); } - public function test_it_returns_404_for_non_existent_chapter(): void + public function testItReturns404ForNonExistentChapter(): void { // When static::createClient()->request('DELETE', '/api/manga/chapters/999999'); @@ -51,19 +51,19 @@ class DeleteChapterTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(404); } - public function test_it_returns_404_for_already_soft_deleted_chapter(): void + public function testItReturns404ForAlreadySoftDeletedChapter(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'Test Manga', - 'slug' => 'test-manga' + 'slug' => 'test-manga', ]); $chapter = ChapterFactory::createOne([ 'manga' => $manga, 'number' => 1.0, 'title' => 'Test Chapter', - 'visible' => false // Already soft deleted + 'visible' => false, // Already soft deleted ]); $this->entityManager->flush(); diff --git a/tests/Feature/Manga/DeleteMangaTest.php b/tests/Feature/Manga/DeleteMangaTest.php index fc109c0..1311415 100644 --- a/tests/Feature/Manga/DeleteMangaTest.php +++ b/tests/Feature/Manga/DeleteMangaTest.php @@ -2,25 +2,25 @@ namespace App\Tests\Feature\Manga; -use App\Entity\Manga; use App\Entity\Chapter; -use App\Tests\Factory\MangaFactory; +use App\Entity\Manga; use App\Tests\Factory\ChapterFactory; +use App\Tests\Factory\MangaFactory; use App\Tests\Feature\AbstractApiTestCase; -use Symfony\Component\HttpFoundation\Response; use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ResetDatabase; class DeleteMangaTest extends AbstractApiTestCase { - use ResetDatabase, Factories; + use ResetDatabase; + use Factories; - public function test_it_deletes_manga_with_chapters(): void + public function testItDeletesMangaWithChapters(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'One Piece', - 'slug' => 'one-piece' + 'slug' => 'one-piece', ]); // Create chapters for the manga @@ -28,21 +28,21 @@ class DeleteMangaTest extends AbstractApiTestCase 'manga' => $manga, 'number' => 1.0, 'title' => 'Chapter 1', - 'visible' => true + 'visible' => true, ]); ChapterFactory::createMany(2, [ 'manga' => $manga, 'number' => 2.0, 'title' => 'Chapter 2', - 'visible' => true + 'visible' => true, ]); ChapterFactory::createMany(1, [ 'manga' => $manga, 'number' => 3.0, 'title' => 'Chapter 3', - 'visible' => true + 'visible' => true, ]); $mangaId = $manga->getId(); @@ -66,7 +66,7 @@ class DeleteMangaTest extends AbstractApiTestCase $this->assertCount(0, $chaptersAfter); } - public function test_it_returns_404_for_non_existent_manga(): void + public function testItReturns404ForNonExistentManga(): void { // When static::createClient()->request('DELETE', '/api/mangas/999999'); @@ -74,5 +74,4 @@ class DeleteMangaTest extends AbstractApiTestCase // Then $this->assertResponseStatusCodeSame(404); } - } diff --git a/tests/Feature/Manga/DownloadCbzTest.php b/tests/Feature/Manga/DownloadCbzTest.php index f5da5ca..f2a7973 100644 --- a/tests/Feature/Manga/DownloadCbzTest.php +++ b/tests/Feature/Manga/DownloadCbzTest.php @@ -2,7 +2,6 @@ namespace App\Tests\Feature\Manga; -use App\Entity\Chapter; use App\Tests\Factory\ChapterFactory; use App\Tests\Factory\MangaFactory; use App\Tests\Feature\AbstractApiTestCase; @@ -12,14 +11,15 @@ use Zenstruck\Foundry\Test\ResetDatabase; class DownloadCbzTest extends AbstractApiTestCase { - use ResetDatabase, Factories; + use ResetDatabase; + use Factories; - public function test_it_downloads_chapter_cbz(): void + public function testItDownloadsChapterCbz(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'One Piece', - 'slug' => 'one-piece' + 'slug' => 'one-piece', ]); $chapter = ChapterFactory::createOne([ @@ -27,7 +27,7 @@ class DownloadCbzTest extends AbstractApiTestCase 'number' => 1.0, 'title' => 'Chapter 1', 'visible' => true, - 'cbzPath' => '/app/tests/Shared/Files/test-chapter.cbz' + 'cbzPath' => '/app/tests/Shared/Files/test-chapter.cbz', ]); $chapterId = $chapter->getId(); @@ -44,7 +44,7 @@ class DownloadCbzTest extends AbstractApiTestCase $this->assertStringContainsString('test-chapter.cbz', $response->headers->get('Content-Disposition')); } - public function test_it_returns_404_for_non_existent_chapter(): void + public function testItReturns404ForNonExistentChapter(): void { // When static::createClient()->request('GET', '/api/manga/chapters/999999/download'); @@ -53,12 +53,12 @@ class DownloadCbzTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(404); } - public function test_it_returns_404_for_chapter_without_cbz(): void + public function testItReturns404ForChapterWithoutCbz(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'Test Manga', - 'slug' => 'test-manga' + 'slug' => 'test-manga', ]); $chapter = ChapterFactory::createOne([ @@ -66,7 +66,7 @@ class DownloadCbzTest extends AbstractApiTestCase 'number' => 1.0, 'title' => 'Test Chapter', 'visible' => true, - 'cbzPath' => null // No CBZ file + 'cbzPath' => null, // No CBZ file ]); $this->entityManager->flush(); diff --git a/tests/Feature/Manga/DownloadVolumeTest.php b/tests/Feature/Manga/DownloadVolumeTest.php index 5dcf605..6266912 100644 --- a/tests/Feature/Manga/DownloadVolumeTest.php +++ b/tests/Feature/Manga/DownloadVolumeTest.php @@ -2,7 +2,6 @@ namespace App\Tests\Feature\Manga; -use App\Entity\Chapter; use App\Tests\Factory\ChapterFactory; use App\Tests\Factory\MangaFactory; use App\Tests\Feature\AbstractApiTestCase; @@ -12,14 +11,15 @@ use Zenstruck\Foundry\Test\ResetDatabase; class DownloadVolumeTest extends AbstractApiTestCase { - use ResetDatabase, Factories; + use ResetDatabase; + use Factories; - public function test_it_downloads_volume_cbz(): void + public function testItDownloadsVolumeCbz(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'One Piece', - 'slug' => 'one-piece' + 'slug' => 'one-piece', ]); // Create chapters for volume 1 @@ -27,7 +27,7 @@ class DownloadVolumeTest extends AbstractApiTestCase 'manga' => $manga, 'volume' => 1, 'visible' => true, - 'cbzPath' => __DIR__ . '/../../Shared/Files/test-chapter.cbz' + 'cbzPath' => __DIR__.'/../../Shared/Files/test-chapter.cbz', ]); $mangaId = $manga->getId(); @@ -43,7 +43,7 @@ class DownloadVolumeTest extends AbstractApiTestCase $this->assertStringContainsString('one-piece_vol1.cbz', $contentDisposition); } - public function test_it_returns_404_when_manga_not_found(): void + public function testItReturns404WhenMangaNotFound(): void { // Act static::createClient()->request('GET', '/api/mangas/999999/volumes/1/download'); @@ -52,12 +52,12 @@ class DownloadVolumeTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND); } - public function test_it_returns_404_when_volume_not_found(): void + public function testItReturns404WhenVolumeNotFound(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'One Piece', - 'slug' => 'one-piece' + 'slug' => 'one-piece', ]); $mangaId = $manga->getId(); @@ -69,12 +69,12 @@ class DownloadVolumeTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND); } - public function test_it_returns_404_when_no_available_chapters_in_volume(): void + public function testItReturns404WhenNoAvailableChaptersInVolume(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'One Piece', - 'slug' => 'one-piece' + 'slug' => 'one-piece', ]); // Create chapters for volume 1 but all without CBZ files @@ -82,7 +82,7 @@ class DownloadVolumeTest extends AbstractApiTestCase 'manga' => $manga, 'volume' => 1, 'visible' => true, - 'cbzPath' => null // No CBZ files + 'cbzPath' => null, // No CBZ files ]); $mangaId = $manga->getId(); @@ -94,12 +94,12 @@ class DownloadVolumeTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND); } - public function test_it_only_includes_visible_chapters_with_cbz(): void + public function testItOnlyIncludesVisibleChaptersWithCbz(): void { // Arrange $manga = MangaFactory::createOne([ 'title' => 'One Piece', - 'slug' => 'one-piece' + 'slug' => 'one-piece', ]); // Create a mix of chapters @@ -108,7 +108,7 @@ class DownloadVolumeTest extends AbstractApiTestCase 'volume' => 1, 'number' => 1.0, 'visible' => true, - 'cbzPath' => __DIR__ . '/../../Shared/Files/test-chapter.cbz' + 'cbzPath' => __DIR__.'/../../Shared/Files/test-chapter.cbz', ]); ChapterFactory::createOne([ @@ -116,7 +116,7 @@ class DownloadVolumeTest extends AbstractApiTestCase 'volume' => 1, 'number' => 2.0, 'visible' => false, // Soft deleted - 'cbzPath' => __DIR__ . '/../../Shared/Files/test-chapter.cbz' + 'cbzPath' => __DIR__.'/../../Shared/Files/test-chapter.cbz', ]); ChapterFactory::createOne([ @@ -124,7 +124,7 @@ class DownloadVolumeTest extends AbstractApiTestCase 'volume' => 1, 'number' => 3.0, 'visible' => true, - 'cbzPath' => null // No CBZ + 'cbzPath' => null, // No CBZ ]); ChapterFactory::createOne([ @@ -132,7 +132,7 @@ class DownloadVolumeTest extends AbstractApiTestCase 'volume' => 1, 'number' => 4.0, 'visible' => true, - 'cbzPath' => __DIR__ . '/../../Shared/Files/test-chapter.cbz' + 'cbzPath' => __DIR__.'/../../Shared/Files/test-chapter.cbz', ]); $mangaId = $manga->getId(); diff --git a/tests/Feature/Manga/EditMangaTest.php b/tests/Feature/Manga/EditMangaTest.php index d3fb19c..5afa244 100644 --- a/tests/Feature/Manga/EditMangaTest.php +++ b/tests/Feature/Manga/EditMangaTest.php @@ -24,8 +24,8 @@ class EditMangaTest extends AbstractApiTestCase 'status' => 'ongoing', 'externalId' => 'external-123', 'imageUrl' => 'http://example.com/image.jpg', - 'rating' => 4.5 - ] + 'rating' => 4.5, + ], ]); $this->assertResponseIsSuccessful(); @@ -37,7 +37,7 @@ class EditMangaTest extends AbstractApiTestCase $mangaId = $createdManga->getId(); // When - Edit the manga - $response = $client->request('PUT', '/api/mangas/' . $mangaId . '/edit', [ + $response = $client->request('PUT', '/api/mangas/'.$mangaId.'/edit', [ 'json' => [ 'title' => 'One Piece Updated', 'description' => 'Updated description', @@ -46,8 +46,8 @@ class EditMangaTest extends AbstractApiTestCase 'genres' => ['action', 'adventure', 'comedy'], 'status' => 'completed', 'rating' => 4.8, - 'alternativeSlugs' => ['onepiece', 'op'] - ] + 'alternativeSlugs' => ['onepiece', 'op'], + ], ]); // Then @@ -80,8 +80,8 @@ class EditMangaTest extends AbstractApiTestCase 'author' => 'Eiichiro Oda', 'publicationYear' => 1997, 'genres' => ['action', 'adventure'], - 'status' => 'ongoing' - ] + 'status' => 'ongoing', + ], ]); $this->assertResponseIsSuccessful(); @@ -93,14 +93,14 @@ class EditMangaTest extends AbstractApiTestCase $mangaId = $createdManga->getId(); // When - Try to edit with invalid data - $client->request('PUT', '/api/mangas/' . $mangaId . '/edit', [ + $client->request('PUT', '/api/mangas/'.$mangaId.'/edit', [ 'json' => [ 'title' => '', // Invalid: empty title 'publicationYear' => 2200, // Invalid: year > 2100 'genres' => [], // Invalid: empty genres 'status' => 'invalid-status', // Invalid status - 'rating' => 6.0 // Invalid: rating > 5 - ] + 'rating' => 6.0, // Invalid: rating > 5 + ], ]); // Then @@ -113,8 +113,8 @@ class EditMangaTest extends AbstractApiTestCase $client = static::createClient(); $client->request('PUT', '/api/mangas/9999999/edit', [ 'json' => [ - 'title' => 'Updated Title' - ] + 'title' => 'Updated Title', + ], ]); // Then @@ -134,8 +134,8 @@ class EditMangaTest extends AbstractApiTestCase 'publicationYear' => 1997, 'genres' => ['action', 'adventure'], 'status' => 'ongoing', - 'rating' => 4.5 - ] + 'rating' => 4.5, + ], ]); $this->assertResponseIsSuccessful(); @@ -147,11 +147,11 @@ class EditMangaTest extends AbstractApiTestCase $mangaId = $createdManga->getId(); // When - Edit only title and rating - $client->request('PUT', '/api/mangas/' . $mangaId . '/edit', [ + $client->request('PUT', '/api/mangas/'.$mangaId.'/edit', [ 'json' => [ 'title' => 'One Piece - Updated Title Only', - 'rating' => 4.9 - ] + 'rating' => 4.9, + ], ]); // Then diff --git a/tests/Feature/Manga/EditMultipleChaptersTest.php b/tests/Feature/Manga/EditMultipleChaptersTest.php index 334fec2..f28fac1 100644 --- a/tests/Feature/Manga/EditMultipleChaptersTest.php +++ b/tests/Feature/Manga/EditMultipleChaptersTest.php @@ -4,14 +4,14 @@ namespace Tests\Feature\Manga; use App\Entity\Chapter; use App\Entity\Manga; -use Zenstruck\Foundry\Test\ResetDatabase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Zenstruck\Foundry\Test\ResetDatabase; class EditMultipleChaptersTest extends WebTestCase { use ResetDatabase; - public function test_it_edits_multiple_chapters(): void + public function testItEditsMultipleChapters(): void { // Given $client = static::createClient(); @@ -46,19 +46,19 @@ class EditMultipleChaptersTest extends WebTestCase [ 'id' => (string) $chapter1->getId(), 'title' => 'New Title 1', - 'volume' => 2 + 'volume' => 2, ], [ 'id' => (string) $chapter2->getId(), 'title' => null, - 'volume' => 3 - ] - ] + 'volume' => 3, + ], + ], ]; // When $client->request('POST', '/api/chapters/batch-edit', [], [], [ - 'CONTENT_TYPE' => 'application/json' + 'CONTENT_TYPE' => 'application/json', ], json_encode($data)); // Then @@ -76,7 +76,7 @@ class EditMultipleChaptersTest extends WebTestCase $this->assertEquals(3, $updatedChapter2->getVolume()); } - public function test_it_returns_404_when_chapter_not_found(): void + public function testItReturns404WhenChapterNotFound(): void { // Given $client = static::createClient(); @@ -86,21 +86,21 @@ class EditMultipleChaptersTest extends WebTestCase [ 'id' => '999', 'title' => 'New Title', - 'volume' => 1 - ] - ] + 'volume' => 1, + ], + ], ]; // When $client->request('POST', '/api/chapters/batch-edit', [], [], [ - 'CONTENT_TYPE' => 'application/json' + 'CONTENT_TYPE' => 'application/json', ], json_encode($data)); // Then $this->assertResponseStatusCodeSame(404); } - public function test_it_validates_required_fields(): void + public function testItValidatesRequiredFields(): void { // Given $client = static::createClient(); @@ -109,15 +109,15 @@ class EditMultipleChaptersTest extends WebTestCase 'chapters' => [ [ 'title' => 'New Title', - 'volume' => 1 + 'volume' => 1, // id manquant - ] - ] + ], + ], ]; // When $client->request('POST', '/api/chapters/batch-edit', [], [], [ - 'CONTENT_TYPE' => 'application/json' + 'CONTENT_TYPE' => 'application/json', ], json_encode($data)); // Then diff --git a/tests/Feature/Manga/FetchMangaChaptersTest.php b/tests/Feature/Manga/FetchMangaChaptersTest.php index cd00e6c..4b30f74 100644 --- a/tests/Feature/Manga/FetchMangaChaptersTest.php +++ b/tests/Feature/Manga/FetchMangaChaptersTest.php @@ -49,8 +49,8 @@ class FetchMangaChaptersTest extends AbstractApiTestCase static::createClient()->request('POST', '/api/manga/chapters/fetch', [ 'json' => [ - 'mangaId' => $mangaId - ] + 'mangaId' => $mangaId, + ], ]); $this->assertResponseStatusCodeSame(202); @@ -65,8 +65,8 @@ class FetchMangaChaptersTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', '/api/manga/chapters/fetch', [ 'json' => [ - 'mangaId' => '' - ] + 'mangaId' => '', + ], ]); $this->assertResponseStatusCodeSame(422); @@ -74,9 +74,9 @@ class FetchMangaChaptersTest extends AbstractApiTestCase 'violations' => [ [ 'propertyPath' => 'mangaId', - 'message' => 'L\'identifiant du manga est obligatoire' - ] - ] + 'message' => 'L\'identifiant du manga est obligatoire', + ], + ], ]); } diff --git a/tests/Feature/Manga/FindMangaMatchByFilenameTest.php b/tests/Feature/Manga/FindMangaMatchByFilenameTest.php index aa3a736..bff4aa3 100644 --- a/tests/Feature/Manga/FindMangaMatchByFilenameTest.php +++ b/tests/Feature/Manga/FindMangaMatchByFilenameTest.php @@ -12,7 +12,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase { use ResetDatabase; - public function test_it_finds_exact_match_by_filename(): void + public function testItFindsExactMatchByFilename(): void { // Given $this->createManga('One Piece', 'one-piece'); @@ -21,8 +21,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-matches', [ 'query' => [ - 'filename' => 'one-piece_vol108_ch1094.cbz' - ] + 'filename' => 'one-piece_vol108_ch1094.cbz', + ], ]); // Then @@ -37,10 +37,9 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $this->assertEquals(1094.0, $data['matches'][0]['chapterNumber']); $this->assertEquals(108, $data['matches'][0]['volumeNumber']); $this->assertGreaterThan(0, $data['matches'][0]['matchScore']); - } - public function test_it_returns_empty_matches_when_no_manga_found(): void + public function testItReturnsEmptyMatchesWhenNoMangaFound(): void { // Given - no manga in database @@ -48,8 +47,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-matches', [ 'query' => [ - 'filename' => 'unknown-manga_vol1_ch1.cbz' - ] + 'filename' => 'unknown-manga_vol1_ch1.cbz', + ], ]); // Then @@ -60,7 +59,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $this->assertCount(0, $data['matches']); } - public function test_it_returns_bad_request_when_filename_is_missing(): void + public function testItReturnsBadRequestWhenFilenameIsMissing(): void { // When $client = static::createClient(); @@ -70,7 +69,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(400); } - public function test_it_extracts_chapter_and_volume_correctly(): void + public function testItExtractsChapterAndVolumeCorrectly(): void { // Given $this->createManga('Attack on Titan', 'attack-on-titan'); @@ -79,8 +78,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-matches', [ 'query' => [ - 'filename' => 'attack-on-titan_vol32_ch130.cbz' - ] + 'filename' => 'attack-on-titan_vol32_ch130.cbz', + ], ]); // Then @@ -90,7 +89,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $this->assertNotEmpty($data['matches']); } - public function test_it_handles_filename_with_only_volume(): void + public function testItHandlesFilenameWithOnlyVolume(): void { // Given $this->createManga('Naruto', 'naruto'); @@ -99,8 +98,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-matches', [ 'query' => [ - 'filename' => 'naruto_vol50.cbz' - ] + 'filename' => 'naruto_vol50.cbz', + ], ]); // Then @@ -111,7 +110,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $this->assertEquals('Naruto', $data['matches'][0]['title']); } - public function test_it_handles_filename_with_only_chapter(): void + public function testItHandlesFilenameWithOnlyChapter(): void { // Given $this->createManga('Bleach', 'bleach'); @@ -120,8 +119,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-matches', [ 'query' => [ - 'filename' => 'bleach_ch200.cbz' - ] + 'filename' => 'bleach_ch200.cbz', + ], ]); // Then @@ -132,7 +131,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $this->assertEquals('Bleach', $data['matches'][0]['title']); } - public function test_it_sorts_matches_by_score(): void + public function testItSortsMatchesByScore(): void { // Given $this->createManga('One Piece', 'one-piece'); @@ -143,8 +142,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-matches', [ 'query' => [ - 'filename' => 'one-piece_vol108_ch1094.cbz' - ] + 'filename' => 'one-piece_vol108_ch1094.cbz', + ], ]); // Then @@ -158,13 +157,13 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $this->assertEquals('One Piece', $data['matches'][0]['title']); // Vérifier que les scores sont triés par ordre décroissant - $scores = array_map(fn($match) => $match['matchScore'], $data['matches']); + $scores = array_map(fn ($match) => $match['matchScore'], $data['matches']); $sortedScores = $scores; rsort($sortedScores); $this->assertEquals($sortedScores, $scores); } - public function test_it_handles_alternative_slugs(): void + public function testItHandlesAlternativeSlugs(): void { // Given $manga = $this->createManga('Shingeki no Kyojin', 'shingeki-no-kyojin'); @@ -175,8 +174,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-matches', [ 'query' => [ - 'filename' => 'attack-on-titan_vol1_ch1.cbz' - ] + 'filename' => 'attack-on-titan_vol1_ch1.cbz', + ], ]); // Then @@ -189,7 +188,7 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $this->assertContains('attack-on-titan', $data['matches'][0]['alternativeSlugs']); } - public function test_it_provides_possible_titles_variants(): void + public function testItProvidesPossibleTitlesVariants(): void { // Given $this->createManga('Dragon Ball', 'dragon-ball'); @@ -198,8 +197,8 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-matches', [ 'query' => [ - 'filename' => 'dragon-ball_vol1_ch5.cbz' - ] + 'filename' => 'dragon-ball_vol1_ch5.cbz', + ], ]); // Then @@ -233,4 +232,3 @@ class FindMangaMatchByFilenameTest extends AbstractApiTestCase return $manga; } } - diff --git a/tests/Feature/Manga/GetMangaBySlugTest.php b/tests/Feature/Manga/GetMangaBySlugTest.php index 6ccc011..12b164b 100644 --- a/tests/Feature/Manga/GetMangaBySlugTest.php +++ b/tests/Feature/Manga/GetMangaBySlugTest.php @@ -2,7 +2,6 @@ namespace App\Tests\Feature\Manga; -use App\Entity\Manga; use App\Tests\Factory\MangaFactory; use App\Tests\Feature\AbstractApiTestCase; use Symfony\Component\HttpFoundation\Response; @@ -11,7 +10,8 @@ use Zenstruck\Foundry\Test\ResetDatabase; class GetMangaBySlugTest extends AbstractApiTestCase { - use ResetDatabase, Factories; + use ResetDatabase; + use Factories; public function testGetMangaBySlugReturnsCorrectManga(): void { @@ -27,7 +27,7 @@ class GetMangaBySlugTest extends AbstractApiTestCase 'imageUrl' => 'https://example.com/image.jpg', 'thumbnailUrl' => 'https://example.com/thumbnail.jpg', 'rating' => 4.5, - 'monitored' => true + 'monitored' => true, ]); // Act @@ -58,4 +58,4 @@ class GetMangaBySlugTest extends AbstractApiTestCase // Assert $this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND); } -} \ No newline at end of file +} diff --git a/tests/Feature/Manga/GetMangaChaptersTest.php b/tests/Feature/Manga/GetMangaChaptersTest.php index 5d99ebe..2cf9e3d 100644 --- a/tests/Feature/Manga/GetMangaChaptersTest.php +++ b/tests/Feature/Manga/GetMangaChaptersTest.php @@ -28,7 +28,7 @@ class GetMangaChaptersTest extends AbstractApiTestCase // When $client = static::createClient(); - $response = $client->request('GET', '/api/mangas/' . $manga->getId() . '/chapters'); + $response = $client->request('GET', '/api/mangas/'.$manga->getId().'/chapters'); // Then $this->assertResponseIsSuccessful(); @@ -38,7 +38,7 @@ class GetMangaChaptersTest extends AbstractApiTestCase 'limit' => 20, 'hasNextPage' => false, 'hasPreviousPage' => false, - 'items' => [] + 'items' => [], ]); } @@ -50,18 +50,18 @@ class GetMangaChaptersTest extends AbstractApiTestCase // When $client = static::createClient(); - $response = $client->request('GET', '/api/mangas/' . $manga->getId() . '/chapters', [ + $response = $client->request('GET', '/api/mangas/'.$manga->getId().'/chapters', [ 'query' => [ 'page' => 2, 'limit' => 10, - 'sortOrder' => 'desc' - ] + 'sortOrder' => 'desc', + ], ]); // Then $this->assertResponseIsSuccessful(); $data = $response->toArray(); - + $this->assertCount(10, $data['items']); $this->assertEquals(25, $data['total']); $this->assertEquals(2, $data['page']); @@ -69,7 +69,7 @@ class GetMangaChaptersTest extends AbstractApiTestCase $this->assertTrue($data['hasNextPage']); $this->assertTrue($data['hasPreviousPage']); - $numbers = array_map(fn($item) => $item['number'], $data['items']); + $numbers = array_map(fn ($item) => $item['number'], $data['items']); $expectedNumbers = $numbers; rsort($expectedNumbers); @@ -99,12 +99,12 @@ class GetMangaChaptersTest extends AbstractApiTestCase { $entityManager = static::getContainer()->get('doctrine')->getManager(); - for ($i = 1; $i <= $count; $i++) { + for ($i = 1; $i <= $count; ++$i) { $chapter = new Chapter(); $chapter->setManga($manga) ->setNumber($i) ->setTitle("Chapter $i") - ->setVolume((int)ceil($i / 10)) + ->setVolume((int) ceil($i / 10)) ->setVisible(true); $entityManager->persist($chapter); @@ -112,4 +112,4 @@ class GetMangaChaptersTest extends AbstractApiTestCase $entityManager->flush(); } -} \ No newline at end of file +} diff --git a/tests/Feature/Manga/GetMangaListTest.php b/tests/Feature/Manga/GetMangaListTest.php index dc7f1f7..39df6a6 100644 --- a/tests/Feature/Manga/GetMangaListTest.php +++ b/tests/Feature/Manga/GetMangaListTest.php @@ -11,7 +11,8 @@ use Zenstruck\Foundry\Test\ResetDatabase; class GetMangaListTest extends AbstractApiTestCase { - use ResetDatabase, Factories; + use ResetDatabase; + use Factories; public function testGetEmptyMangaList(): void { @@ -27,7 +28,7 @@ class GetMangaListTest extends AbstractApiTestCase 'limit' => 20, 'hasNextPage' => false, 'hasPreviousPage' => false, - 'items' => [] + 'items' => [], ]); } @@ -41,8 +42,8 @@ class GetMangaListTest extends AbstractApiTestCase $response = $client->request('GET', '/api/mangas', [ 'query' => [ 'page' => 2, - 'limit' => 10 - ] + 'limit' => 10, + ], ]); // Then @@ -69,8 +70,8 @@ class GetMangaListTest extends AbstractApiTestCase $response = $client->request('GET', '/api/mangas', [ 'query' => [ 'sortBy' => 'title', - 'sortOrder' => 'asc' - ] + 'sortOrder' => 'asc', + ], ]); // Then @@ -113,7 +114,7 @@ class GetMangaListTest extends AbstractApiTestCase private function createMangas(int $count): void { - for ($i = 1; $i <= $count; $i++) { + for ($i = 1; $i <= $count; ++$i) { $this->createManga("Manga $i"); } } diff --git a/tests/Feature/Manga/GetMangaTest.php b/tests/Feature/Manga/GetMangaTest.php index 72e9a1e..1684432 100644 --- a/tests/Feature/Manga/GetMangaTest.php +++ b/tests/Feature/Manga/GetMangaTest.php @@ -43,7 +43,7 @@ class GetMangaTest extends AbstractApiTestCase // When $client = static::createClient(); - $response = $client->request('GET', '/api/mangas/by-id/' . $manga->getId()); + $response = $client->request('GET', '/api/mangas/by-id/'.$manga->getId()); // Then $this->assertResponseIsSuccessful(); @@ -58,7 +58,7 @@ class GetMangaTest extends AbstractApiTestCase 'status' => 'ongoing', 'externalId' => 'external-123', 'imageUrl' => 'http://example.com/image.jpg', - 'rating' => 4.5 + 'rating' => 4.5, ]); } -} \ No newline at end of file +} diff --git a/tests/Feature/Manga/ImportChapterTest.php b/tests/Feature/Manga/ImportChapterTest.php index 68c2a52..5724633 100644 --- a/tests/Feature/Manga/ImportChapterTest.php +++ b/tests/Feature/Manga/ImportChapterTest.php @@ -9,7 +9,7 @@ class ImportChapterTest extends WebTestCase { private const API_ENDPOINT = '/api/chapters/import'; - public function test_it_returns_404_when_manga_not_found(): void + public function testItReturns404WhenMangaNotFound(): void { $client = static::createClient(); $file = $this->createValidCbzFile(); @@ -19,7 +19,7 @@ class ImportChapterTest extends WebTestCase self::API_ENDPOINT, [ 'mangaId' => 'non-existent-manga-id', - 'chapterNumber' => '1.5' + 'chapterNumber' => '1.5', ], ['file' => $file], ['CONTENT_TYPE' => 'multipart/form-data'] @@ -30,7 +30,7 @@ class ImportChapterTest extends WebTestCase $this->assertEquals('Manga not found', $response['error']); } - public function test_it_returns_422_when_manga_id_is_missing(): void + public function testItReturns422WhenMangaIdIsMissing(): void { $client = static::createClient(); $file = $this->createValidCbzFile(); @@ -39,7 +39,7 @@ class ImportChapterTest extends WebTestCase 'POST', self::API_ENDPOINT, [ - 'chapterNumber' => '1.5' + 'chapterNumber' => '1.5', ], ['file' => $file], ['CONTENT_TYPE' => 'multipart/form-data'] @@ -50,7 +50,7 @@ class ImportChapterTest extends WebTestCase $this->assertStringContainsString('mangaId is required', $response[0]['message']); } - public function test_it_returns_422_when_chapter_number_is_missing(): void + public function testItReturns422WhenChapterNumberIsMissing(): void { $client = static::createClient(); $file = $this->createValidCbzFile(); @@ -59,7 +59,7 @@ class ImportChapterTest extends WebTestCase 'POST', self::API_ENDPOINT, [ - 'mangaId' => 'some-manga-id' + 'mangaId' => 'some-manga-id', ], ['file' => $file], ['CONTENT_TYPE' => 'multipart/form-data'] @@ -70,7 +70,7 @@ class ImportChapterTest extends WebTestCase $this->assertStringContainsString('chapterNumber is required', $response[0]['message']); } - public function test_it_returns_422_when_file_is_missing(): void + public function testItReturns422WhenFileIsMissing(): void { $client = static::createClient(); @@ -79,7 +79,7 @@ class ImportChapterTest extends WebTestCase self::API_ENDPOINT, [ 'mangaId' => 'some-manga-id', - 'chapterNumber' => '1.5' + 'chapterNumber' => '1.5', ], [], ['CONTENT_TYPE' => 'multipart/form-data'] @@ -90,7 +90,7 @@ class ImportChapterTest extends WebTestCase $this->assertStringContainsString('Please upload a file', $response[0]['message']); } - public function test_it_returns_422_when_file_is_not_cbz(): void + public function testItReturns422WhenFileIsNotCbz(): void { $client = static::createClient(); @@ -103,7 +103,7 @@ class ImportChapterTest extends WebTestCase self::API_ENDPOINT, [ 'mangaId' => 'some-manga-id', - 'chapterNumber' => '1.5' + 'chapterNumber' => '1.5', ], ['file' => $file], ['CONTENT_TYPE' => 'multipart/form-data'] @@ -120,7 +120,7 @@ class ImportChapterTest extends WebTestCase unlink($tmpFile); $zip = new \ZipArchive(); - if ($zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { + if (true !== $zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) { throw new \RuntimeException('Cannot create test CBZ file'); } diff --git a/tests/Feature/Manga/ImportVolumeTest.php b/tests/Feature/Manga/ImportVolumeTest.php index 60bc56c..0a665e5 100644 --- a/tests/Feature/Manga/ImportVolumeTest.php +++ b/tests/Feature/Manga/ImportVolumeTest.php @@ -9,7 +9,7 @@ class ImportVolumeTest extends WebTestCase { private const API_ENDPOINT = '/api/volumes/import'; - public function test_it_returns_404_when_manga_not_found(): void + public function testItReturns404WhenMangaNotFound(): void { $client = static::createClient(); $file = $this->createValidCbzFile(); @@ -19,7 +19,7 @@ class ImportVolumeTest extends WebTestCase self::API_ENDPOINT, [ 'mangaId' => 'non-existent-manga-id', - 'volumeNumber' => '1' + 'volumeNumber' => '1', ], ['file' => $file], ['CONTENT_TYPE' => 'multipart/form-data'] @@ -30,7 +30,7 @@ class ImportVolumeTest extends WebTestCase $this->assertEquals('Manga not found', $response['error']); } - public function test_it_returns_422_when_manga_id_is_missing(): void + public function testItReturns422WhenMangaIdIsMissing(): void { $client = static::createClient(); $file = $this->createValidCbzFile(); @@ -39,7 +39,7 @@ class ImportVolumeTest extends WebTestCase 'POST', self::API_ENDPOINT, [ - 'volumeNumber' => '1' + 'volumeNumber' => '1', ], ['file' => $file], ['CONTENT_TYPE' => 'multipart/form-data'] @@ -50,7 +50,7 @@ class ImportVolumeTest extends WebTestCase $this->assertStringContainsString('mangaId is required', $response[0]['message']); } - public function test_it_returns_422_when_volume_number_is_missing(): void + public function testItReturns422WhenVolumeNumberIsMissing(): void { $client = static::createClient(); $file = $this->createValidCbzFile(); @@ -59,7 +59,7 @@ class ImportVolumeTest extends WebTestCase 'POST', self::API_ENDPOINT, [ - 'mangaId' => 'some-manga-id' + 'mangaId' => 'some-manga-id', ], ['file' => $file], ['CONTENT_TYPE' => 'multipart/form-data'] @@ -70,7 +70,7 @@ class ImportVolumeTest extends WebTestCase $this->assertStringContainsString('volumeNumber is required', $response[0]['message']); } - public function test_it_returns_422_when_file_is_missing(): void + public function testItReturns422WhenFileIsMissing(): void { $client = static::createClient(); @@ -79,7 +79,7 @@ class ImportVolumeTest extends WebTestCase self::API_ENDPOINT, [ 'mangaId' => 'some-manga-id', - 'volumeNumber' => '1' + 'volumeNumber' => '1', ], [], ['CONTENT_TYPE' => 'multipart/form-data'] @@ -90,7 +90,7 @@ class ImportVolumeTest extends WebTestCase $this->assertStringContainsString('Please upload a file', $response[0]['message']); } - public function test_it_returns_422_when_file_is_not_cbz(): void + public function testItReturns422WhenFileIsNotCbz(): void { $client = static::createClient(); @@ -103,7 +103,7 @@ class ImportVolumeTest extends WebTestCase self::API_ENDPOINT, [ 'mangaId' => 'some-manga-id', - 'volumeNumber' => '1' + 'volumeNumber' => '1', ], ['file' => $file], ['CONTENT_TYPE' => 'multipart/form-data'] @@ -120,7 +120,7 @@ class ImportVolumeTest extends WebTestCase unlink($tmpFile); $zip = new \ZipArchive(); - if ($zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { + if (true !== $zip->open($tmpFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) { throw new \RuntimeException('Cannot create test CBZ file'); } diff --git a/tests/Feature/Manga/SearchMangaTest.php b/tests/Feature/Manga/SearchMangaTest.php index 1c8e3d7..2487194 100644 --- a/tests/Feature/Manga/SearchMangaTest.php +++ b/tests/Feature/Manga/SearchMangaTest.php @@ -16,15 +16,15 @@ class SearchMangaTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-search', [ 'query' => [ - 'q' => '' - ] + 'q' => '', + ], ]); // Then $this->assertResponseStatusCodeSame(400); $this->assertJsonContains([ - 'hydra:title' => 'An error occurred', - 'hydra:description' => 'Le terme de recherche doit contenir au moins 3 caractères' + 'title' => 'An error occurred', + 'detail' => 'Le terme de recherche doit contenir au moins 3 caractères', ]); } @@ -34,15 +34,15 @@ class SearchMangaTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-search', [ 'query' => [ - 'q' => 'on' - ] + 'q' => 'on', + ], ]); // Then $this->assertResponseStatusCodeSame(400); $this->assertJsonContains([ - 'hydra:title' => 'An error occurred', - 'hydra:description' => 'Le terme de recherche doit contenir au moins 3 caractères' + 'title' => 'An error occurred', + 'detail' => 'Le terme de recherche doit contenir au moins 3 caractères', ]); } @@ -57,8 +57,8 @@ class SearchMangaTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-search', [ 'query' => [ - 'q' => 'one' - ] + 'q' => 'one', + ], ]); // Then @@ -66,9 +66,8 @@ class SearchMangaTest extends AbstractApiTestCase $data = $response->toArray(); - - $this->assertCount(2, $data['items']['hydra:member']); - $titles = array_map(fn($item) => $item['title'], $data['items']['hydra:member']); + $this->assertCount(2, $data['items']['member']); + $titles = array_map(fn ($item) => $item['title'], $data['items']['member']); $this->assertContains('One Piece', $titles); $this->assertContains('One Punch Man', $titles); } @@ -83,17 +82,17 @@ class SearchMangaTest extends AbstractApiTestCase $client = static::createClient(); $response = $client->request('GET', '/api/manga-search', [ 'query' => [ - 'q' => 'dragon' - ] + 'q' => 'dragon', + ], ]); // Then $this->assertResponseIsSuccessful(); $data = $response->toArray(); - $this->assertCount(1, $data['items']['hydra:member']); - $this->assertEquals('Dragon Ball', $data['items']['hydra:member'][0]['title']); - $this->assertEquals('dragon-ball', $data['items']['hydra:member'][0]['slug']); + $this->assertCount(1, $data['items']['member']); + $this->assertEquals('Dragon Ball', $data['items']['member'][0]['title']); + $this->assertEquals('dragon-ball', $data['items']['member'][0]['slug']); } // public function testSearchMangaWithPagination(): void diff --git a/tests/Feature/Manga/ToggleMonitoringTest.php b/tests/Feature/Manga/ToggleMonitoringTest.php index cd27388..c43ff93 100644 --- a/tests/Feature/Manga/ToggleMonitoringTest.php +++ b/tests/Feature/Manga/ToggleMonitoringTest.php @@ -39,8 +39,8 @@ class ToggleMonitoringTest extends AbstractApiTestCase // Act static::createClient()->request('POST', "/api/manga/{$mangaId}/monitoring/toggle", [ 'json' => [ - 'enabled' => true - ] + 'enabled' => true, + ], ]); // Assert @@ -78,8 +78,8 @@ class ToggleMonitoringTest extends AbstractApiTestCase // Act static::createClient()->request('POST', "/api/manga/{$mangaId}/monitoring/toggle", [ 'json' => [ - 'enabled' => false - ] + 'enabled' => false, + ], ]); // Assert @@ -96,8 +96,8 @@ class ToggleMonitoringTest extends AbstractApiTestCase // Act & Assert static::createClient()->request('POST', '/api/manga/99999/monitoring/toggle', [ 'json' => [ - 'enabled' => true - ] + 'enabled' => true, + ], ]); $this->assertResponseStatusCodeSame(404); @@ -127,7 +127,7 @@ class ToggleMonitoringTest extends AbstractApiTestCase // Act & Assert static::createClient()->request('POST', "/api/manga/{$mangaId}/monitoring/toggle", [ - 'json' => [] + 'json' => [], ]); $this->assertResponseStatusCodeSame(422); @@ -135,9 +135,9 @@ class ToggleMonitoringTest extends AbstractApiTestCase 'violations' => [ [ 'propertyPath' => 'enabled', - 'message' => 'Le champ enabled est obligatoire' - ] - ] + 'message' => 'Le champ enabled est obligatoire', + ], + ], ]); } @@ -166,8 +166,8 @@ class ToggleMonitoringTest extends AbstractApiTestCase // Act & Assert static::createClient()->request('POST', "/api/manga/{$mangaId}/monitoring/toggle", [ 'json' => [ - 'enabled' => 'invalid' - ] + 'enabled' => 'invalid', + ], ]); $this->assertResponseStatusCodeSame(422); @@ -175,9 +175,9 @@ class ToggleMonitoringTest extends AbstractApiTestCase 'violations' => [ [ 'propertyPath' => 'enabled', - 'message' => 'Cette valeur doit être de type bool.' - ] - ] + 'message' => 'Cette valeur doit être de type bool.', + ], + ], ]); } diff --git a/tests/Feature/Reader/GetChapterContextTest.php b/tests/Feature/Reader/GetChapterContextTest.php index 2995390..62796e4 100644 --- a/tests/Feature/Reader/GetChapterContextTest.php +++ b/tests/Feature/Reader/GetChapterContextTest.php @@ -45,23 +45,23 @@ final class GetChapterContextTest extends AbstractApiTestCase ]); // Act - static::createClient()->request('GET', '/api/reader/chapter/' . $chapter1->getId()); + static::createClient()->request('GET', '/api/reader/chapter/'.$chapter1->getId()); // Assert $this->assertResponseIsSuccessful(); $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); $this->assertJsonContains([ - 'id' => (string)$chapter1->getId(), - 'mangaId' => (string)$manga->getId(), + 'id' => (string) $chapter1->getId(), + 'mangaId' => (string) $manga->getId(), 'title' => 'Chapter 1', 'number' => 1, 'totalPages' => 0, 'navigation' => [ - 'hydra:member' => [ - null, (string)$chapter2->getId(), + 'member' => [ + null, (string) $chapter2->getId(), ], - ] + ], ]); } @@ -75,8 +75,8 @@ final class GetChapterContextTest extends AbstractApiTestCase $this->assertResponseHeaderSame('content-type', 'application/problem+json; charset=utf-8'); $this->assertJsonContains([ - 'hydra:title' => 'An error occurred', - 'hydra:description' => 'Le chapitre 0 n\'existe pas', + 'title' => 'An error occurred', + 'detail' => 'Le chapitre 0 n\'existe pas', ]); } } diff --git a/tests/Feature/Reader/GetChapterPagesTest.php b/tests/Feature/Reader/GetChapterPagesTest.php index 735b4e6..635fd44 100644 --- a/tests/Feature/Reader/GetChapterPagesTest.php +++ b/tests/Feature/Reader/GetChapterPagesTest.php @@ -9,7 +9,6 @@ use App\Tests\Factory\MangaFactory; use App\Tests\Feature\AbstractApiTestCase; use Symfony\Component\HttpFoundation\Response; use Zenstruck\Foundry\Test\ResetDatabase; -use ZipArchive; final class GetChapterPagesTest extends AbstractApiTestCase { @@ -23,16 +22,16 @@ final class GetChapterPagesTest extends AbstractApiTestCase parent::setUp(); // Extraire quelques images du CBZ dans un dossier temporaire - $this->pagesDirectory = sys_get_temp_dir() . '/mangarr-test-pages-' . uniqid(); + $this->pagesDirectory = sys_get_temp_dir().'/mangarr-test-pages-'.uniqid(); mkdir($this->pagesDirectory); - $zip = new ZipArchive(); - $zip->open(__DIR__ . '/../../Fixtures/chapter.cbz'); + $zip = new \ZipArchive(); + $zip->open(__DIR__.'/../../Fixtures/chapter.cbz'); $zip->extractTo($this->pagesDirectory, ['007.jpg', '008.jpg']); $zip->close(); $manga = MangaFactory::createOne([ 'title' => 'Test Manga', - 'slug' => 'test-manga' + 'slug' => 'test-manga', ]); $chapter = ChapterFactory::createOne([ @@ -41,7 +40,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase 'number' => 1.0, 'volume' => 1, 'visible' => true, - 'pagesDirectory' => $this->pagesDirectory + 'pagesDirectory' => $this->pagesDirectory, ]); $this->chapterId = $chapter->getId(); @@ -51,7 +50,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase { parent::tearDown(); - foreach (glob($this->pagesDirectory . '/*') as $file) { + foreach (glob($this->pagesDirectory.'/*') as $file) { unlink($file); } rmdir($this->pagesDirectory); @@ -63,7 +62,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND); $this->assertJsonContains([ - 'detail' => 'Le chapitre 999 n\'existe pas' + 'detail' => 'Le chapitre 999 n\'existe pas', ]); } @@ -99,8 +98,8 @@ final class GetChapterPagesTest extends AbstractApiTestCase $response = static::createClient()->request('GET', "/api/reader/chapter/{$this->chapterId}/pages", [ 'query' => [ 'page' => 1, - 'itemsPerPage' => 5 - ] + 'itemsPerPage' => 5, + ], ]); $this->assertResponseIsSuccessful(); @@ -133,7 +132,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase // Créer un chapitre sans fichier CBZ $manga = MangaFactory::createOne([ 'title' => 'Empty Manga', - 'slug' => 'empty-manga' + 'slug' => 'empty-manga', ]); $emptyChapter = ChapterFactory::createOne([ @@ -142,7 +141,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase 'number' => 1.0, 'volume' => 1, 'visible' => true, - 'cbzPath' => null + 'cbzPath' => null, ]); $response = static::createClient()->request('GET', "/api/reader/chapter/{$emptyChapter->getId()}/pages"); @@ -155,8 +154,8 @@ final class GetChapterPagesTest extends AbstractApiTestCase { $response = static::createClient()->request('GET', "/api/reader/chapter/{$this->chapterId}/pages", [ 'query' => [ - 'page' => -1 - ] + 'page' => -1, + ], ]); $this->assertResponseIsSuccessful(); @@ -167,11 +166,11 @@ final class GetChapterPagesTest extends AbstractApiTestCase { $response = static::createClient()->request('GET', "/api/reader/chapter/{$this->chapterId}/pages", [ 'query' => [ - 'itemsPerPage' => 0 - ] + 'itemsPerPage' => 0, + ], ]); - //TODO: Corriger la fonctionnalité de pagination pour que l'endpoint retourne une erreur 400 quand itemsPerPage est 0 (division par zéro) + // TODO: Corriger la fonctionnalité de pagination pour que l'endpoint retourne une erreur 400 quand itemsPerPage est 0 (division par zéro) $this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR); // L'endpoint retourne une erreur 500 quand itemsPerPage est 0 (division par zéro) } @@ -180,7 +179,7 @@ final class GetChapterPagesTest extends AbstractApiTestCase { $response = static::createClient()->request('GET', '/api/reader/chapter/invalid-id/pages'); - //TODO: Corriger le cas où l'ID est invalide + // TODO: Corriger le cas où l'ID est invalide $this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR); // L'endpoint retourne une erreur 500 quand l'ID est invalide } diff --git a/tests/Feature/Scraping/Factory/ScrapingJobFactory.php b/tests/Feature/Scraping/Factory/ScrapingJobFactory.php index bce8ca8..00c400a 100644 --- a/tests/Feature/Scraping/Factory/ScrapingJobFactory.php +++ b/tests/Feature/Scraping/Factory/ScrapingJobFactory.php @@ -12,8 +12,9 @@ use Ramsey\Uuid\Uuid; class ScrapingJobFactory { public function __construct( - private readonly ScrapingJobRepositoryInterface $repository - ) {} + private readonly ScrapingJobRepositoryInterface $repository, + ) { + } public function createJob(array $attributes = []): ScrapingJob { @@ -48,4 +49,4 @@ class ScrapingJobFactory $reflection->setAccessible(true); $reflection->setValue($job, $status); } -} \ No newline at end of file +} diff --git a/tests/Feature/Scraping/GetMangaPreferredSourcesTest.php b/tests/Feature/Scraping/GetMangaPreferredSourcesTest.php index 532dacf..ae9e462 100644 --- a/tests/Feature/Scraping/GetMangaPreferredSourcesTest.php +++ b/tests/Feature/Scraping/GetMangaPreferredSourcesTest.php @@ -70,7 +70,7 @@ final class GetMangaPreferredSourcesTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR); $this->assertJsonContains([ - 'detail' => 'Manga not found with ID: 999999' + 'detail' => 'Manga not found with ID: 999999', ]); } diff --git a/tests/Feature/Scraping/ScrapingStatusTest.php b/tests/Feature/Scraping/ScrapingStatusTest.php index a072ad9..dfeab8f 100644 --- a/tests/Feature/Scraping/ScrapingStatusTest.php +++ b/tests/Feature/Scraping/ScrapingStatusTest.php @@ -4,13 +4,10 @@ namespace App\Tests\Feature\Scraping; use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; use App\Domain\Scraping\Domain\Model\ScrapingJob; -use App\Domain\Scraping\Domain\Model\ScrapingStatus; -use App\Domain\Scraping\Domain\Model\ValueObject\ImageUrl; -use App\Domain\Scraping\Domain\Model\ValueObject\PageNumber; -use Ramsey\Uuid\Uuid; -use Symfony\Component\Messenger\MessageBusInterface; use App\Domain\Shared\Domain\Contract\JobRepositoryInterface; use App\Domain\Shared\Domain\Model\JobStatus; +use Ramsey\Uuid\Uuid; +use Symfony\Component\Messenger\MessageBusInterface; class ScrapingStatusTest extends ApiTestCase { @@ -45,12 +42,12 @@ class ScrapingStatusTest extends ApiTestCase $responseData = $response->toArray(); - $this->assertArrayHasKey('hydra:member', $responseData); - $this->assertIsArray($responseData['hydra:member']); - $this->assertCount(1, $responseData['hydra:member']); + $this->assertArrayHasKey('member', $responseData); + $this->assertIsArray($responseData['member']); + $this->assertCount(1, $responseData['member']); - $jobData = $responseData['hydra:member'][0]; - $this->assertEquals('/api/jobs/' . $jobId, $jobData['@id']); + $jobData = $responseData['member'][0]; + $this->assertEquals('/api/jobs/'.$jobId, $jobData['@id']); $this->assertEquals('Job', $jobData['@type']); $this->assertEquals($jobId, $jobData['id']); $this->assertEquals('scraping_job', $jobData['type']); @@ -58,7 +55,7 @@ class ScrapingStatusTest extends ApiTestCase $this->assertEquals([ 'mangaId' => 'manga-123', 'chapterNumber' => 1, - 'sourceId' => 'source-789' + 'sourceId' => 'source-789', ], $jobData['context']); } @@ -66,7 +63,7 @@ class ScrapingStatusTest extends ApiTestCase { // When $response = static::createClient()->request('GET', '/api/jobs/non-existent-id?status=in_progress', [ - 'headers' => ['Accept' => 'application/json'] + 'headers' => ['Accept' => 'application/json'], ]); // Then diff --git a/tests/Feature/Scraping/SetMangaPreferredSourcesTest.php b/tests/Feature/Scraping/SetMangaPreferredSourcesTest.php index 903619a..7b00ebd 100644 --- a/tests/Feature/Scraping/SetMangaPreferredSourcesTest.php +++ b/tests/Feature/Scraping/SetMangaPreferredSourcesTest.php @@ -4,9 +4,9 @@ declare(strict_types=1); namespace App\Tests\Feature\Scraping; +use App\Domain\Scraping\Domain\Contract\Repository\MangaRepositoryInterface; use App\Entity\ContentSource; use App\Entity\Manga; -use App\Domain\Scraping\Domain\Contract\Repository\MangaRepositoryInterface; use App\Tests\Feature\AbstractApiTestCase; use Symfony\Component\HttpFoundation\Response; use Zenstruck\Foundry\Test\ResetDatabase; @@ -72,13 +72,13 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', '/api/mangas/999999/preferred-sources', [ 'json' => [ - 'sourceIds' => [(string) $this->source1Id] - ] + 'sourceIds' => [(string) $this->source1Id], + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR); $this->assertJsonContains([ - 'detail' => 'Manga not found with ID: 999999' + 'detail' => 'Manga not found with ID: 999999', ]); } @@ -86,13 +86,13 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [ 'json' => [ - 'sourceIds' => ['999999'] - ] + 'sourceIds' => ['999999'], + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR); $this->assertJsonContains([ - 'detail' => 'One or more sources do not exist or are not active' + 'detail' => 'One or more sources do not exist or are not active', ]); } @@ -100,8 +100,8 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [ 'json' => [ - 'sourceIds' => [(string) $this->source1Id, (string) $this->source2Id] - ] + 'sourceIds' => [(string) $this->source1Id, (string) $this->source2Id], + ], ]); $this->assertResponseIsSuccessful(); @@ -127,8 +127,8 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase // Modifier les sources préférées $response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [ 'json' => [ - 'sourceIds' => [(string) $this->source2Id] - ] + 'sourceIds' => [(string) $this->source2Id], + ], ]); $this->assertResponseIsSuccessful(); @@ -143,8 +143,8 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [ 'json' => [ - 'sourceIds' => [] - ] + 'sourceIds' => [], + ], ]); $this->assertResponseIsSuccessful(); @@ -159,11 +159,11 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [ 'json' => [ - 'sourceIds' => ['invalid-id', '123'] - ] + 'sourceIds' => ['invalid-id', '123'], + ], ]); - //TODO: Corriger le cas où l'ID est invalide + // TODO: Corriger le cas où l'ID est invalide $this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR); } @@ -171,11 +171,11 @@ final class SetMangaPreferredSourcesTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', "/api/mangas/{$this->mangaId}/preferred-sources", [ 'json' => [ - 'invalidField' => 'value' - ] + 'invalidField' => 'value', + ], ]); - //TODO: Corriger le cas où le format de la requête est invalide + // TODO: Corriger le cas où le format de la requête est invalide $this->assertResponseStatusCodeSame(Response::HTTP_OK); } } diff --git a/tests/Feature/Scraping/TestScraperConfigurationTest.php b/tests/Feature/Scraping/TestScraperConfigurationTest.php index 7c292f8..99aa82f 100644 --- a/tests/Feature/Scraping/TestScraperConfigurationTest.php +++ b/tests/Feature/Scraping/TestScraperConfigurationTest.php @@ -77,7 +77,7 @@ class TestScraperConfigurationTest extends AbstractApiTestCase $this->assertGreaterThan(0, count($responseData['errors'])); } - public function testTestScraperConfigurationWithInvalidSelector(): void + public function testTestScraperConfigurationWithInvalidSelector(): void { // Given - Configuration avec un sélecteur CSS qui ne trouvera rien $payload = [ @@ -165,7 +165,7 @@ class TestScraperConfigurationTest extends AbstractApiTestCase ]); } - public function testTestScraperConfigurationWithUnsupportedScrapingType(): void + public function testTestScraperConfigurationWithUnsupportedScrapingType(): void { // Given - Type de scraping non supporté $payload = [ diff --git a/tests/Feature/Setting/CreateContentSourceTest.php b/tests/Feature/Setting/CreateContentSourceTest.php index bbd4ab3..84367a7 100644 --- a/tests/Feature/Setting/CreateContentSourceTest.php +++ b/tests/Feature/Setting/CreateContentSourceTest.php @@ -26,11 +26,11 @@ final class CreateContentSourceTest extends AbstractApiTestCase 'scrapingType' => 'html', 'imageSelector' => '.chapter-image img', 'nextPageSelector' => '.next-page', - 'chapterSelector' => '.chapter-list a' + 'chapterSelector' => '.chapter-list a', ]; $response = static::createClient()->request('POST', '/api/content-sources', [ - 'json' => $sourceData + 'json' => $sourceData, ]); $this->assertResponseIsSuccessful(); @@ -44,12 +44,14 @@ final class CreateContentSourceTest extends AbstractApiTestCase // Vérifier que la source a été sauvegardée en base $source = $this->entityManager->find(ContentSource::class, $sourceId); - if ($source === null) { + if (null === $source) { // L'ID peut ne pas correspondre, vérifions juste que l'opération s'est bien passée $this->assertIsNumeric($responseContent); + return; } $this->assertEquals($sourceData['baseUrl'], $source->getBaseUrl()); + return; } @@ -74,8 +76,8 @@ final class CreateContentSourceTest extends AbstractApiTestCase 'json' => [ 'baseUrl' => '', 'chapterUrlFormat' => '', - 'scrapingType' => '' - ] + 'scrapingType' => '', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -87,8 +89,8 @@ final class CreateContentSourceTest extends AbstractApiTestCase 'json' => [ 'baseUrl' => 'invalid-url', 'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}', - 'scrapingType' => 'html' - ] + 'scrapingType' => 'html', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -100,8 +102,8 @@ final class CreateContentSourceTest extends AbstractApiTestCase 'json' => [ 'baseUrl' => 'https://mangadex.org', 'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}', - 'scrapingType' => 'invalid-type' - ] + 'scrapingType' => 'invalid-type', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -115,11 +117,11 @@ final class CreateContentSourceTest extends AbstractApiTestCase 'scrapingType' => 'html', 'imageSelector' => '.chapter-image img', 'nextPageSelector' => '.next-page', - 'chapterSelector' => '.chapter-list a' + 'chapterSelector' => '.chapter-list a', ]; $response = static::createClient()->request('POST', '/api/content-sources', [ - 'json' => $sourceData + 'json' => $sourceData, ]); $this->assertResponseIsSuccessful(); @@ -129,6 +131,7 @@ final class CreateContentSourceTest extends AbstractApiTestCase $responseContent = $response->getContent(); if (is_numeric($responseContent)) { $this->assertIsNumeric($responseContent); + return; } @@ -146,11 +149,11 @@ final class CreateContentSourceTest extends AbstractApiTestCase 'scrapingType' => 'javascript', 'imageSelector' => '.page-image img', 'nextPageSelector' => '.next-button', - 'chapterSelector' => '.chapter-link' + 'chapterSelector' => '.chapter-link', ]; $response = static::createClient()->request('POST', '/api/content-sources', [ - 'json' => $sourceData + 'json' => $sourceData, ]); $this->assertResponseIsSuccessful(); @@ -160,6 +163,7 @@ final class CreateContentSourceTest extends AbstractApiTestCase $responseContent = $response->getContent(); if (is_numeric($responseContent)) { $this->assertIsNumeric($responseContent); + return; } @@ -171,8 +175,39 @@ final class CreateContentSourceTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', '/api/content-sources', [ 'json' => [ - 'invalidField' => 'value' - ] + 'invalidField' => 'value', + ], + ]); + + $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); + } + + public function testItAcceptsTestChapterNumberAsFloat(): void + { + $response = static::createClient()->request('POST', '/api/content-sources', [ + 'json' => [ + 'baseUrl' => 'https://mangadex.org', + 'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}', + 'scrapingType' => 'html', + 'testSlug' => 'one-piece', + 'testChapterNumber' => 1.5, + ], + ]); + + $this->assertResponseStatusCodeSame(Response::HTTP_CREATED); + } + + public function testItRejectsTestChapterNumberAsEmptyStringWith422(): void + { + // Cas réel du formulaire Vue : le champ vide envoie "" au lieu de null. + // Doit retourner 422 (erreur de validation) et non 400 (données malformées). + $response = static::createClient()->request('POST', '/api/content-sources', [ + 'json' => [ + 'baseUrl' => 'https://mangadex.org', + 'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}', + 'scrapingType' => 'html', + 'testChapterNumber' => '', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); diff --git a/tests/Feature/Setting/ExportContentSourceTest.php b/tests/Feature/Setting/ExportContentSourceTest.php index 2b21de5..baef7ef 100644 --- a/tests/Feature/Setting/ExportContentSourceTest.php +++ b/tests/Feature/Setting/ExportContentSourceTest.php @@ -24,8 +24,6 @@ final class ExportContentSourceTest extends AbstractApiTestCase $this->assertResponseIsSuccessful(); $data = $response->toArray(); - - $this->assertIsArray($data); // L'endpoint retourne un format Hydra mais les données semblent vides // Pour l'instant, vérifions juste que la réponse est un tableau @@ -63,7 +61,7 @@ final class ExportContentSourceTest extends AbstractApiTestCase // L'endpoint retourne un format Hydra mais les données semblent vides // Pour l'instant, vérifions juste que la réponse est un tableau $this->assertArrayHasKey('@type', $data); - $this->assertEquals('hydra:Collection', $data['@type']); + $this->assertEquals('Collection', $data['@type']); } public function testItExportsSourcesWithNullOptionalFields(): void @@ -89,13 +87,13 @@ final class ExportContentSourceTest extends AbstractApiTestCase // L'endpoint retourne un format Hydra mais les données semblent vides // Pour l'instant, vérifions juste que la réponse est un tableau $this->assertArrayHasKey('@type', $data); - $this->assertEquals('hydra:Collection', $data['@type']); + $this->assertEquals('Collection', $data['@type']); } public function testItExportsLargeNumberOfSources(): void { // Création de plusieurs sources - for ($i = 1; $i <= 25; $i++) { + for ($i = 1; $i <= 25; ++$i) { $source = new ContentSource(); $source->setBaseUrl("https://source{$i}.com") ->setChapterUrlFormat("https://source{$i}.com/chapter/{id}") @@ -117,7 +115,7 @@ final class ExportContentSourceTest extends AbstractApiTestCase // L'endpoint retourne un format Hydra mais les données semblent vides // Pour l'instant, vérifions juste que la réponse est un tableau $this->assertArrayHasKey('@type', $data); - $this->assertEquals('hydra:Collection', $data['@type']); + $this->assertEquals('Collection', $data['@type']); } public function testItExportsSourcesInCorrectFormat(): void @@ -152,6 +150,6 @@ final class ExportContentSourceTest extends AbstractApiTestCase // L'endpoint retourne un format Hydra mais les données semblent vides // Pour l'instant, vérifions juste que la réponse est un tableau $this->assertArrayHasKey('@type', $data); - $this->assertEquals('hydra:Collection', $data['@type']); + $this->assertEquals('Collection', $data['@type']); } } diff --git a/tests/Feature/Setting/GetContentSourceTest.php b/tests/Feature/Setting/GetContentSourceTest.php index 964384a..ab8574b 100644 --- a/tests/Feature/Setting/GetContentSourceTest.php +++ b/tests/Feature/Setting/GetContentSourceTest.php @@ -40,7 +40,7 @@ final class GetContentSourceTest extends AbstractApiTestCase $this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND); $this->assertJsonContains([ - 'detail' => 'ContentSource with id 999999 not found' + 'detail' => 'ContentSource with id 999999 not found', ]); } diff --git a/tests/Feature/Setting/ImportContentSourceTest.php b/tests/Feature/Setting/ImportContentSourceTest.php index 1aafb81..16a2c49 100644 --- a/tests/Feature/Setting/ImportContentSourceTest.php +++ b/tests/Feature/Setting/ImportContentSourceTest.php @@ -28,7 +28,7 @@ final class ImportContentSourceTest extends AbstractApiTestCase 'scrapingType' => 'html', 'imageSelector' => '.chapter-image img', 'nextPageSelector' => '.next-page', - 'chapterSelector' => '.chapter-list a' + 'chapterSelector' => '.chapter-list a', ], [ 'baseUrl' => 'https://mangakakalot.com', @@ -36,13 +36,13 @@ final class ImportContentSourceTest extends AbstractApiTestCase 'scrapingType' => 'javascript', 'imageSelector' => '.page-image img', 'nextPageSelector' => '.next-button', - 'chapterSelector' => '.chapter-link' - ] - ] + 'chapterSelector' => '.chapter-link', + ], + ], ]; $response = static::createClient()->request('POST', '/api/content-sources/import', [ - 'json' => $importData + 'json' => $importData, ]); $this->assertResponseIsSuccessful(); @@ -52,7 +52,7 @@ final class ImportContentSourceTest extends AbstractApiTestCase $sources = $this->entityManager->getRepository(ContentSource::class)->findAll(); $this->assertCount(2, $sources); - $baseUrls = array_map(fn($source) => $source->getBaseUrl(), $sources); + $baseUrls = array_map(fn ($source) => $source->getBaseUrl(), $sources); $this->assertContains('https://mangadex.org', $baseUrls); $this->assertContains('https://mangakakalot.com', $baseUrls); } @@ -65,10 +65,10 @@ final class ImportContentSourceTest extends AbstractApiTestCase [ 'baseUrl' => '', 'chapterUrlFormat' => '', - 'scrapingType' => '' - ] - ] - ] + 'scrapingType' => '', + ], + ], + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST); @@ -85,10 +85,10 @@ final class ImportContentSourceTest extends AbstractApiTestCase 'scrapingType' => 'html', 'imageSelector' => '.image', 'nextPageSelector' => '.next', - 'chapterSelector' => '.chapter' - ] - ] - ] + 'chapterSelector' => '.chapter', + ], + ], + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_CREATED); @@ -105,10 +105,10 @@ final class ImportContentSourceTest extends AbstractApiTestCase 'scrapingType' => 'invalid-type', 'imageSelector' => '.image', 'nextPageSelector' => '.next', - 'chapterSelector' => '.chapter' - ] - ] - ] + 'chapterSelector' => '.chapter', + ], + ], + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_CREATED); @@ -118,8 +118,8 @@ final class ImportContentSourceTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', '/api/content-sources/import', [ 'json' => [ - 'contentSources' => 'not-an-array' - ] + 'contentSources' => 'not-an-array', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST); @@ -129,8 +129,8 @@ final class ImportContentSourceTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', '/api/content-sources/import', [ 'json' => [ - 'contentSources' => [] - ] + 'contentSources' => [], + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -140,8 +140,8 @@ final class ImportContentSourceTest extends AbstractApiTestCase { $response = static::createClient()->request('POST', '/api/content-sources/import', [ 'json' => [ - 'invalidField' => [] - ] + 'invalidField' => [], + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -157,13 +157,13 @@ final class ImportContentSourceTest extends AbstractApiTestCase 'scrapingType' => 'html', 'imageSelector' => '.simple-image', 'nextPageSelector' => '.simple-next', - 'chapterSelector' => '.simple-chapter' - ] - ] + 'chapterSelector' => '.simple-chapter', + ], + ], ]; $response = static::createClient()->request('POST', '/api/content-sources/import', [ - 'json' => $importData + 'json' => $importData, ]); $this->assertResponseIsSuccessful(); @@ -171,7 +171,7 @@ final class ImportContentSourceTest extends AbstractApiTestCase // Vérifier que la source a été créée $source = $this->entityManager->getRepository(ContentSource::class)->findOneBy([ - 'baseUrl' => 'https://simple-source.com' + 'baseUrl' => 'https://simple-source.com', ]); $this->assertNotNull($source); $this->assertEquals('html', $source->getScrapingType()); @@ -183,21 +183,21 @@ final class ImportContentSourceTest extends AbstractApiTestCase public function testItHandlesLargeImport(): void { $contentSources = []; - for ($i = 1; $i <= 10; $i++) { + for ($i = 1; $i <= 10; ++$i) { $contentSources[] = [ 'baseUrl' => "https://source{$i}.com", 'chapterUrlFormat' => "https://source{$i}.com/chapter/{id}", 'scrapingType' => 'html', 'imageSelector' => ".source{$i}-image img", 'nextPageSelector' => ".source{$i}-next", - 'chapterSelector' => ".source{$i}-chapter a" + 'chapterSelector' => ".source{$i}-chapter a", ]; } $response = static::createClient()->request('POST', '/api/content-sources/import', [ 'json' => [ - 'contentSources' => $contentSources - ] + 'contentSources' => $contentSources, + ], ]); $this->assertResponseIsSuccessful(); diff --git a/tests/Feature/Setting/ListContentSourceTest.php b/tests/Feature/Setting/ListContentSourceTest.php index 76ceb8b..cc7d2b8 100644 --- a/tests/Feature/Setting/ListContentSourceTest.php +++ b/tests/Feature/Setting/ListContentSourceTest.php @@ -24,10 +24,10 @@ final class ListContentSourceTest extends AbstractApiTestCase $this->assertResponseIsSuccessful(); $data = $response->toArray(); - $this->assertArrayHasKey('hydra:member', $data); - $this->assertCount(0, $data['hydra:member']); - $this->assertArrayHasKey('hydra:totalItems', $data); - $this->assertEquals(0, $data['hydra:totalItems']); + $this->assertArrayHasKey('member', $data); + $this->assertCount(0, $data['member']); + $this->assertArrayHasKey('totalItems', $data); + $this->assertEquals(0, $data['totalItems']); } public function testItReturnsAllSources(): void @@ -58,13 +58,13 @@ final class ListContentSourceTest extends AbstractApiTestCase $this->assertResponseIsSuccessful(); $data = $response->toArray(); - $this->assertArrayHasKey('hydra:member', $data); - $this->assertCount(2, $data['hydra:member']); - $this->assertArrayHasKey('hydra:totalItems', $data); - $this->assertEquals(2, $data['hydra:totalItems']); + $this->assertArrayHasKey('member', $data); + $this->assertCount(2, $data['member']); + $this->assertArrayHasKey('totalItems', $data); + $this->assertEquals(2, $data['totalItems']); // Vérifier la structure d'une source - $firstSource = $data['hydra:member'][0]; + $firstSource = $data['member'][0]; $this->assertArrayHasKey('id', $firstSource); $this->assertArrayHasKey('baseUrl', $firstSource); $this->assertArrayHasKey('chapterUrlFormat', $firstSource); @@ -75,7 +75,7 @@ final class ListContentSourceTest extends AbstractApiTestCase $this->assertArrayHasKey('cleanBaseUrl', $firstSource); // Vérifier que les URLs sont bien présentes - $baseUrls = array_column($data['hydra:member'], 'baseUrl'); + $baseUrls = array_column($data['member'], 'baseUrl'); $this->assertContains('https://mangadex.org', $baseUrls); $this->assertContains('https://mangakakalot.com', $baseUrls); } @@ -83,7 +83,7 @@ final class ListContentSourceTest extends AbstractApiTestCase public function testItReturnsSourcesWithPagination(): void { // Création de plusieurs sources - for ($i = 1; $i <= 25; $i++) { + for ($i = 1; $i <= 25; ++$i) { $source = new ContentSource(); $source->setBaseUrl("https://source{$i}.com") ->setChapterUrlFormat("https://source{$i}.com/chapter/{id}") @@ -99,20 +99,20 @@ final class ListContentSourceTest extends AbstractApiTestCase $response = static::createClient()->request('GET', '/api/content-sources', [ 'query' => [ 'page' => 2, - 'itemsPerPage' => 10 - ] + 'itemsPerPage' => 10, + ], ]); $this->assertResponseIsSuccessful(); $data = $response->toArray(); - $this->assertArrayHasKey('hydra:member', $data); - $this->assertArrayHasKey('hydra:totalItems', $data); - $this->assertEquals(25, $data['hydra:totalItems']); + $this->assertArrayHasKey('member', $data); + $this->assertArrayHasKey('totalItems', $data); + $this->assertEquals(25, $data['totalItems']); // Vérifier la pagination - l'endpoint peut retourner toutes les sources // même avec des paramètres de pagination - $this->assertGreaterThanOrEqual(10, count($data['hydra:member'])); - $this->assertLessThanOrEqual(25, count($data['hydra:member'])); + $this->assertGreaterThanOrEqual(10, count($data['member'])); + $this->assertLessThanOrEqual(25, count($data['member'])); } } diff --git a/tests/Feature/Setting/UpdateContentSourceTest.php b/tests/Feature/Setting/UpdateContentSourceTest.php index be7a050..d3eac5e 100644 --- a/tests/Feature/Setting/UpdateContentSourceTest.php +++ b/tests/Feature/Setting/UpdateContentSourceTest.php @@ -40,13 +40,13 @@ final class UpdateContentSourceTest extends AbstractApiTestCase 'json' => [ 'baseUrl' => 'https://updated.com', 'chapterUrlFormat' => 'https://updated.com/chapter/{id}', - 'scrapingType' => 'html' - ] + 'scrapingType' => 'html', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND); $this->assertJsonContains([ - 'detail' => 'ContentSource with id 999999 not found' + 'detail' => 'ContentSource with id 999999 not found', ]); } @@ -58,11 +58,11 @@ final class UpdateContentSourceTest extends AbstractApiTestCase 'scrapingType' => 'javascript', 'imageSelector' => '.updated-image img', 'nextPageSelector' => '.updated-next', - 'chapterSelector' => '.updated-chapter a' + 'chapterSelector' => '.updated-chapter a', ]; $response = static::createClient()->request('PUT', "/api/content-sources/{$this->sourceId}", [ - 'json' => $updatedData + 'json' => $updatedData, ]); $this->assertResponseIsSuccessful(); @@ -72,6 +72,7 @@ final class UpdateContentSourceTest extends AbstractApiTestCase $responseContent = $response->getContent(); if (is_numeric($responseContent)) { $this->assertIsNumeric($responseContent); + return; } @@ -96,8 +97,8 @@ final class UpdateContentSourceTest extends AbstractApiTestCase 'json' => [ 'baseUrl' => '', 'chapterUrlFormat' => '', - 'scrapingType' => '' - ] + 'scrapingType' => '', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -109,8 +110,8 @@ final class UpdateContentSourceTest extends AbstractApiTestCase 'json' => [ 'baseUrl' => 'invalid-url', 'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}', - 'scrapingType' => 'html' - ] + 'scrapingType' => 'html', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -122,8 +123,8 @@ final class UpdateContentSourceTest extends AbstractApiTestCase 'json' => [ 'baseUrl' => 'https://mangadex.org', 'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}', - 'scrapingType' => 'invalid-type' - ] + 'scrapingType' => 'invalid-type', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); @@ -137,11 +138,11 @@ final class UpdateContentSourceTest extends AbstractApiTestCase 'scrapingType' => 'html', 'imageSelector' => '.updated-image img', 'nextPageSelector' => '.updated-next-page', - 'chapterSelector' => '.updated-chapter-list a' + 'chapterSelector' => '.updated-chapter-list a', ]; $response = static::createClient()->request('PUT', "/api/content-sources/{$this->sourceId}", [ - 'json' => $updatedData + 'json' => $updatedData, ]); $this->assertResponseIsSuccessful(); @@ -150,6 +151,7 @@ final class UpdateContentSourceTest extends AbstractApiTestCase $responseContent = $response->getContent(); if (is_numeric($responseContent)) { $this->assertIsNumeric($responseContent); + return; } @@ -169,8 +171,8 @@ final class UpdateContentSourceTest extends AbstractApiTestCase 'json' => [ 'baseUrl' => 'https://test.com', 'chapterUrlFormat' => 'https://test.com/chapter/{id}', - 'scrapingType' => 'html' - ] + 'scrapingType' => 'html', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND); @@ -180,8 +182,53 @@ final class UpdateContentSourceTest extends AbstractApiTestCase { $response = static::createClient()->request('PUT', "/api/content-sources/{$this->sourceId}", [ 'json' => [ - 'invalidField' => 'value' - ] + 'invalidField' => 'value', + ], + ]); + + $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); + } + + public function testItAcceptsTestChapterNumberAsFloat(): void + { + $response = static::createClient()->request('PUT', "/api/content-sources/{$this->sourceId}", [ + 'json' => [ + 'baseUrl' => 'https://mangadex.org', + 'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}', + 'scrapingType' => 'html', + 'testSlug' => 'one-piece', + 'testChapterNumber' => 1.5, + ], + ]); + + $this->assertResponseIsSuccessful(); + } + + public function testItAcceptsTestChapterNumberAsNull(): void + { + $response = static::createClient()->request('PUT', "/api/content-sources/{$this->sourceId}", [ + 'json' => [ + 'baseUrl' => 'https://mangadex.org', + 'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}', + 'scrapingType' => 'html', + 'testChapterNumber' => null, + ], + ]); + + $this->assertResponseIsSuccessful(); + } + + public function testItRejectsTestChapterNumberAsEmptyStringWith422(): void + { + // Cas réel du formulaire Vue : le champ vide envoie "" au lieu de null. + // Doit retourner 422 (erreur de validation) et non 400 (données malformées). + $response = static::createClient()->request('PUT', "/api/content-sources/{$this->sourceId}", [ + 'json' => [ + 'baseUrl' => 'https://mangadex.org', + 'chapterUrlFormat' => 'https://mangadex.org/chapter/{id}', + 'scrapingType' => 'html', + 'testChapterNumber' => '', + ], ]); $this->assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); diff --git a/tests/Shared/Adapter/InMemoryEventDispatcher.php b/tests/Shared/Adapter/InMemoryEventDispatcher.php index bc2f668..9a0e339 100644 --- a/tests/Shared/Adapter/InMemoryEventDispatcher.php +++ b/tests/Shared/Adapter/InMemoryEventDispatcher.php @@ -29,15 +29,16 @@ class InMemoryEventDispatcher implements EventDispatcherInterface /** * @template T of object + * * @param class-string $eventClass + * * @return array */ public function getDispatchedEventsOfType(string $eventClass): array { return array_filter( $this->dispatchedEvents, - fn(object $event) => $event instanceof $eventClass + fn (object $event) => $event instanceof $eventClass ); } } - diff --git a/tests/Shared/Adapter/InMemoryMessageBus.php b/tests/Shared/Adapter/InMemoryMessageBus.php index 66c89ef..c83cabb 100644 --- a/tests/Shared/Adapter/InMemoryMessageBus.php +++ b/tests/Shared/Adapter/InMemoryMessageBus.php @@ -13,6 +13,7 @@ class InMemoryMessageBus implements MessageBusInterface public function dispatch(object $message, array $stamps = []): Envelope { self::$messages[] = $message; + return new Envelope($message); } @@ -33,6 +34,7 @@ class InMemoryMessageBus implements MessageBusInterface return true; } } + return false; } }