diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..aed2bc5 --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +--- + +name: "Merge Request" +about: "Template de MR" +title: "[#NUMERO_TICKET] TITRE TICKET" +ref: "main" + +--- + +| Numéro du ticket | Titre du ticket | +|------------------|-----------------| +| | | + +## Description de la PR + +## Modification du .env + +## Check list + +- [ ] Pas de régression +- [ ] TU/TI/TF rédigée +- [ ] TU/TI/TF OK +- [ ] CHANGELOG modifié diff --git a/.gitea/workflows/auto-tag-develop.yml b/.gitea/workflows/auto-tag-develop.yml new file mode 100644 index 0000000..5bb4ac5 --- /dev/null +++ b/.gitea/workflows/auto-tag-develop.yml @@ -0,0 +1,45 @@ +name: Auto Tag Develop + +on: + push: + branches: + - develop + +jobs: + tag: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.RELEASE_TOKEN }} + persist-credentials: true + + - name: Create next tag v0.0.X + shell: bash + run: | + set -euo pipefail + + # Skip if current commit already has a v0.0.* tag + if git tag --points-at HEAD | grep -qE '^v0\.0\.'; then + echo "Tag already exists on this commit. Skipping." + exit 0 + fi + + last_tag="$(git tag -l 'v0.0.*' --sort=-v:refname | head -n1 || true)" + if [ -z "$last_tag" ]; then + next_tag="v0.0.1" + else + patch="${last_tag##v0.0.}" + if ! [[ "$patch" =~ ^[0-9]+$ ]]; then + echo "Unexpected tag format: $last_tag" >&2 + exit 1 + fi + next_tag="v0.0.$((patch + 1))" + fi + + git config user.name "gitea-actions" + git config user.email "gitea-actions@local" + git tag "$next_tag" + git push origin "$next_tag" diff --git a/.gitea/workflows/release-artefact.yml b/.gitea/workflows/release-artefact.yml new file mode 100644 index 0000000..6abf770 --- /dev/null +++ b/.gitea/workflows/release-artefact.yml @@ -0,0 +1,43 @@ +name: Build Release Artefact + +on: + push: + tags: + - "v0.0.*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.4" + extensions: mbstring, intl, xml, curl, zip + + - name: Install backend deps (prod) + env: + APP_ENV: prod + APP_DEBUG: "0" + run: composer install --no-dev --optimize-autoloader --no-interaction --no-scripts + + - name: Build artefact + shell: bash + run: | + set -euo pipefail + mkdir -p release + tar --exclude=.git --exclude=.gitea -czf "release/ednotif-bundle-${GITHUB_REF_NAME}.tar.gz" \ + . + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + files: release/ednotif-bundle-${{ github.ref_name }}.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} diff --git a/.idea/Soap-bundle.iml b/.idea/Soap-bundle.iml index 1bdaf5d..6dd76b1 100644 --- a/.idea/Soap-bundle.iml +++ b/.idea/Soap-bundle.iml @@ -56,6 +56,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/php.xml b/.idea/php.xml index 437b4a7..58c2b13 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -12,57 +12,77 @@ - + - - - + + - + + + + + + + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..04b76c2 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,56 @@ +in('src') + ->notName('Kernel.php') +; + +$rules = [ + '@Symfony' => true, + '@PSR12' => true, + '@PHP84Migration' => true, + '@PER-CS' => true, + '@PhpCsFixer' => true, + 'strict_param' => true, + 'strict_comparison' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'binary_operator_spaces' => [ + 'operators' => [ + '=' => 'align_single_space_minimal', + '||' => 'align_single_space_minimal', + '=>' => 'align_single_space_minimal', + ], + ], + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'modernize_strpos' => true, // needs PHP 8+ or polyfill + 'no_superfluous_phpdoc_tags' => true, + 'echo_tag_syntax' => true, + 'semicolon_after_instruction' => true, + 'combine_consecutive_unsets' => true, + 'ternary_to_null_coalescing' => true, + 'declare_strict_types' => true, + 'operator_linebreak' => [ + 'position' => 'beginning', + ], + 'no_unused_imports' => true, + 'single_line_throw' => false, + 'php_unit_test_class_requires_covers' => false, +]; + +$config = new Config(); + +return $config + ->setRiskyAllowed(true) + ->setRules($rules) + ->setFinder($finder) +; diff --git a/commit-msg b/commit-msg new file mode 100644 index 0000000..901d964 --- /dev/null +++ b/commit-msg @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +MSG_FILE="${1}" +FIRST_LINE="$(head -n 1 "$MSG_FILE" | tr -d '\r')" + +# Autoriser commits auto-générés par git +if [[ "$FIRST_LINE" =~ ^Merge\ ]]; then + exit 0 +fi + +# Types autorisés (MINUSCULES uniquement) +# Optionnel: scope => feat(auth) : ... +REGEX='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9._-]+\))?\ :\ .+' + +if [[ ! "$FIRST_LINE" =~ $REGEX ]]; then + echo "❌ Message de commit invalide." + echo "" + echo "➡️ Format attendu : () : " + echo "➡️ Types autorisés (minuscules uniquement) :" + echo " build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test" + echo "" + echo "✅ Exemples :" + echo " feat : add login page" + echo " fix(auth) : prevent null token crash" + echo " docs : update README" + echo "" + echo "❌ Exemple refusé :" + echo " Feat : add login page" + exit 1 +fi diff --git a/composer.json b/composer.json index c1f5d86..14184d5 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "Malio/ednotif-bundle", + "name": "malio/ednotif-bundle", "description": "Client EDNOTIF (Guichet + wsIpBNotif) pour Symfony", "type": "symfony-bundle", "version": "0.0.1", @@ -27,6 +27,7 @@ } }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.92", "phpunit/phpunit": "^12.0", "symfony/phpunit-bridge": "^8.0" }, diff --git a/composer.lock b/composer.lock index ec7a773..e8c381a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2ea08ae20540bc012297beb885d74903", + "content-hash": "2a9c3edf53b3ae827236eb3a2dc64cc8", "packages": [ { "name": "psr/cache", @@ -1969,6 +1969,504 @@ } ], "packages-dev": [ + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "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", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.92.5", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58", + "reference": "260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.3", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.5", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.3", + "php": "^7.4 || ^8.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", + "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.7", + "infection/infection": "^0.31", + "justinrainbow/json-schema": "^6.6", + "keradus/cli-executor": "^2.3", + "mikey179/vfsstream": "^1.6.12", + "php-coveralls/php-coveralls": "^2.9", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", + "phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.46", + "symfony/polyfill-php85": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.26 || ^7.4.0 || ^8.0", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/**/Internal/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.92.5" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2026-01-08T21:57:37+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.13.4", @@ -2644,6 +3142,532 @@ ], "time": "2026-01-16T16:28:10+00:00" }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.7", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.7" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-12-23T15:25:20+00:00" + }, + { + "name": "react/dns", + "version": "v1.14.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", + "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.14.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-18T19:34:28+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-17T20:46:25+00:00" + }, + { + "name": "react/promise", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.12.28 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-08-19T18:57:03+00:00" + }, + { + "name": "react/socket", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", + "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.17.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2025-11-19T20:47:34+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, { "name": "sebastian/cli-parser", "version": "4.2.0", @@ -3593,6 +4617,96 @@ ], "time": "2024-10-20T05:08:20+00:00" }, + { + "name": "symfony/console", + "version": "v8.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", + "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", + "symfony/service-contracts": "^2.5|^3", + "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": "^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": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v8.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-23T14:52:06+00:00" + }, { "name": "symfony/phpunit-bridge", "version": "v8.0.3", @@ -3678,6 +4792,638 @@ ], "time": "2025-12-10T13:10:54+00:00" }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "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\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/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-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "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\\Php81\\": "" + }, + "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.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/process", + "version": "v8.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "0cbbd88ec836f8757641c651bb995335846abb78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78", + "reference": "0cbbd88ec836f8757641c651bb995335846abb78", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v8.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-19T10:01:18+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v8.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/67df1914c6ccd2d7b52f70d40cf2aea02159d942", + "reference": "67df1914c6ccd2d7b52f70d40cf2aea02159d942", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v8.0.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-08-04T07:36:47+00:00" + }, + { + "name": "symfony/string", + "version": "v8.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "shasum": "" + }, + "require": { + "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/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": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v8.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-01T09:13:36+00:00" + }, { "name": "theseer/tokenizer", "version": "2.0.1", diff --git a/config/services.php b/config/services.php index 26a8a8b..5802cdb 100644 --- a/config/services.php +++ b/config/services.php @@ -2,31 +2,37 @@ declare(strict_types=1); -use Malio\EdnotifBundle\Api\BovinApi; -use Malio\EdnotifBundle\Api\BovinApiInterface; use Malio\EdnotifBundle\Auth\TokenProvider; -use Malio\EdnotifBundle\Soap\SoapClientFactory; +use Malio\EdnotifBundle\Bovin\Api\BovinApi; +use Malio\EdnotifBundle\Bovin\Api\BovinApiInterface; +use Malio\EdnotifBundle\Bovin\Mapper\AnimalFileMapper; +use Malio\EdnotifBundle\Shared\Soap\SoapClientFactory; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + use function Symfony\Component\DependencyInjection\Loader\Configurator\service; return static function (ContainerConfigurator $container): void { $services = $container->services() ->defaults() ->autowire() - ->autoconfigure(); + ->autoconfigure() + ; $services->set(SoapClientFactory::class) - ->arg('$soapOptions', '%ednotif.soap_options%'); + ->arg('$soapOptions', '%ednotif.soap_options%') + ; - // SoapClient Guichet $services->set('ednotif.soap.guichet', SoapClient::class) ->factory([service(SoapClientFactory::class), 'create']) - ->args(['%ednotif.guichet_wsdl%']); + ->args(['%ednotif.guichet_wsdl%']) + ; - // SoapClient Métier - $services->set('ednotif.soap.metier', SoapClient::class) + $services->set('ednotif.soap.business', SoapClient::class) ->factory([service(SoapClientFactory::class), 'create']) - ->args(['%ednotif.metier_wsdl%']); + ->args(['%ednotif.metier_wsdl%']) + ; + + $services->set(AnimalFileMapper::class); $services->set(TokenProvider::class) ->args([ @@ -38,13 +44,18 @@ return static function (ContainerConfigurator $container): void { '%ednotif.password%', '%ednotif.token_ttl_seconds%', service('cache.app'), - ]); + ]) + ; $services->set(BovinApi::class) ->args([ service(TokenProvider::class), - service('ednotif.soap.metier'), - ]); + service('ednotif.soap.business'), + service(AnimalFileMapper::class), + '%ednotif.exploitation_country_code%', + '%ednotif.exploitation_number%', + ]) + ; $services->alias(BovinApiInterface::class, BovinApi::class)->public(); }; diff --git a/docker-compose.yml b/docker-compose.yml index 69ed830..71400a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,12 @@ services: dockerfile: Dockerfile args: DOCKER_PHP_VERSION: ${DOCKER_PHP_VERSION} + CURRENT_UID: ${CURRENT_UID} + CURRENT_GID: ${CURRENT_GID} environment: PHP_IDE_CONFIG: serverName=${DOCKER_APP_NAME}-docker + XDEBUG_CLIENT_HOST: ${XDEBUG_CLIENT_HOST:-host.docker.internal} + XDEBUG_CONFIG: client_host=${XDEBUG_CLIENT_HOST:-host.docker.internal} client_port=9003 extra_hosts: - "host.docker.internal:${CLIENT_HOST:-0.0.0.0}" volumes: diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 08b140c..9f5d7e4 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -14,12 +14,26 @@ RUN apt-get update && apt-get install -y \ && docker-php-ext-install soap \ && docker-php-ext-install dom -# Install Composer -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer +# installation de composer +RUN rm -rf /var/cache/apk/* && rm -rf /tmp/* && \ + curl --insecure https://getcomposer.org/composer.phar -o /usr/bin/composer && chmod +x /usr/bin/composer # install Symfony Flex in the CI environment RUN composer global config --no-plugins allow-plugins.symfony/flex true RUN composer global require --no-progress --no-scripts --no-plugins symfony/flex -# Set working directory -WORKDIR /app +###> User ### +ARG CURRENT_UID +ARG CURRENT_GID +# mapping du user host avec www-data +RUN usermod -o -u ${CURRENT_UID} www-data && groupmod -o -g ${CURRENT_GID} www-data +RUN chown www-data:www-data -R /var/www/* +RUN chown www-data:www-data -R /var/www/.* +###< User ### + +RUN rm -rf \ + /var/lib/apt/lists/* \ + /tmp/* \ + /var/tmp/* + +WORKDIR /app \ No newline at end of file diff --git a/makefile b/makefile index 200b98c..e1db21b 100644 --- a/makefile +++ b/makefile @@ -40,11 +40,17 @@ restart: env-init $(DOCKER_COMPOSE) down CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d -install: composer-install +install: copy-git-hook composer-install # Supprime tout est réinstalle tout (Attention ça supprime la bdd aussi) reset: delete_built_dir remove_orphans build-without-cache start wait install +copy-git-hook: + $(EXEC_PHP) cp pre-commit .git/hooks/ + $(EXEC_PHP) cp commit-msg .git/hooks/ + $(EXEC_PHP) chmod a+x .git/hooks/pre-commit + $(EXEC_PHP) chmod a+x .git/hooks/commit-msg + composer-install: $(EXEC_PHP) composer install @@ -67,5 +73,17 @@ build-without-cache: shell: $(EXEC_PHP_INTERACTIVE) bash +shell-root: + $(EXEC_PHP_INTERACTIVE_ROOT) bash + +# Lance php fixer +php-cs-fixer-all: + $(EXEC_PHP) php vendor/bin/php-cs-fixer fix + +# Utilisé par le pre-commit pour fix les fichiers modifiés +php-cs-fixer-allow-risky: + @echo "Fixing files: $(FILES)" + $(EXEC_PHP_CS_FIXER) fix --config=.php-cs-fixer.dist.php --allow-risky=yes $(FILES) + wait: sleep 10 \ No newline at end of file diff --git a/pre-commit b/pre-commit new file mode 100644 index 0000000..3270079 --- /dev/null +++ b/pre-commit @@ -0,0 +1,28 @@ +#!/bin/sh + +echo "######### Pre-commit hook start #############" +echo "--- php-cs-fixer pre commit hook start ---" + +FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.php$') +# Vérifier s'il y a des fichiers PHP modifiés +if [ -n "$FILES" ]; then + echo "Running PHP CS Fixer on staged PHP files..." + + # Convertir la liste des fichiers en une chaîne séparée par des espaces + FILES_LIST="" + for FILE in $FILES; do + FILES_LIST="$FILES_LIST $FILE" + done + + # Exécuter la cible make pour PHP CS Fixer + make php-cs-fixer-allow-risky FILES="$FILES_LIST" + + # Ajouter les fichiers corrigés au commit + git add $FILES +else + echo "No PHP files to fix." +fi +echo "--- php-cs-fixer pre commit hook finish---" + +echo "All checks passed. Proceeding with commit." +exit 0 diff --git a/src/Api/BovinApi.php b/src/Api/BovinApi.php deleted file mode 100644 index 8f4853b..0000000 --- a/src/Api/BovinApi.php +++ /dev/null @@ -1,112 +0,0 @@ -tokenProvider->getToken(); - - $payload = [[ - 'JetonAuthentification' => $token, - 'Exploitation' => [ - 'CodePays' => $codePays, - 'NumeroExploitation' => $exploitationNumero, - ], - 'Bovin' => [ - 'CodePays' => $codePays, - 'NumeroNational' => $numeroNational, - ], - ]]; - - try { - /** @var object $response */ - $response = $this->metierClient->__soapCall('IpBGetDossierAnimal', $payload); - } catch (SoapFault $e) { - // Si c’est un souci de jeton, tu peux invalider et retenter une fois (optionnel) - throw new \RuntimeException('SOAP Fault lors de IpBGetDossierAnimal: ' . $e->getMessage(), 0, $e); - } - - $rs = $response->ReponseStandard ?? null; - $ok = is_object($rs) && (($rs->Resultat ?? false) === true); - - if (!$ok) { - $anom = $rs->Anomalie ?? null; - $code = (string)($anom->Code ?? 'UNKNOWN'); - $sev = (int)($anom->Severite ?? 1); - $msg = (string)($anom->Message ?? 'Appel EDNOTIF refusé'); - throw new EdnotifException($code, $sev, $msg); - } - - $identite = []; - $periodes = []; - - $bovinNode = $response->ReponseSpecifique->Bovin ?? null; - if (is_object($bovinNode)) { - $identiteObj = $bovinNode->IdentiteBovin ?? null; - if (is_object($identiteObj)) { - $identite = $this->objectToArray($identiteObj); - } - - $pp = $bovinNode->PeriodesPresences->PeriodePresence ?? null; - foreach ($this->normalizeList($pp) as $periode) { - if (!is_object($periode)) { - continue; - } - $entree = is_object($periode->Entree ?? null) ? $this->objectToArray($periode->Entree) : []; - $sortie = is_object($periode->Sortie ?? null) ? $this->objectToArray($periode->Sortie) : null; - - $row = ['entree' => $entree]; - if ($sortie !== null) { - $row['sortie'] = $sortie; - } - $periodes[] = $row; - } - } - - return new DossierAnimalDto( - numeroNational: $numeroNational, - identiteBovin: $identite, - periodesPresence: $periodes, - rawResponse: $response, - ); - } - - /** - * @return list - */ - private function normalizeList(mixed $value): array - { - if ($value === null) { - return []; - } - if (is_array($value)) { - return $value; - } - return [$value]; - } - - /** - * @return array - */ - private function objectToArray(object $obj): array - { - // conversion simple (suffisante pour démarrer) - return json_decode(json_encode($obj, JSON_THROW_ON_ERROR), true, 512, JSON_THROW_ON_ERROR); - } -} diff --git a/src/Api/BovinApiInterface.php b/src/Api/BovinApiInterface.php deleted file mode 100644 index 68df8e8..0000000 --- a/src/Api/BovinApiInterface.php +++ /dev/null @@ -1,16 +0,0 @@ -getCacheKey(); - $item = $this->cachePool->getItem($cacheKey); + $item = $this->cachePool->getItem($cacheKey); if ($item->isHit()) { $token = $item->get(); - if (is_string($token) && $token !== '') { + if (is_string($token) && '' !== $token) { return $token; } } @@ -44,6 +48,9 @@ final class TokenProvider return $token; } + /** + * @throws InvalidArgumentException + */ public function invalidateToken(): void { $this->cachePool->deleteItem($this->getCacheKey()); @@ -52,16 +59,16 @@ final class TokenProvider private function createToken(): string { $profil = array_filter([ - 'Entreprise' => $this->entreprise, - 'Zone' => $this->zone, + 'Entreprise' => $this->entreprise, + 'Zone' => $this->zone, 'Application' => $this->application, - ], static fn ($v) => $v !== null && $v !== ''); + ], static fn ($v) => null !== $v && '' !== $v); $payload = [ 'Identification' => [ - 'UserId' => $this->login, + 'UserId' => $this->login, 'Password' => $this->password, - 'Profil' => $profil, + 'Profil' => $profil, ], ]; @@ -69,7 +76,7 @@ final class TokenProvider /** @var object $response */ $response = $this->guichetClient->__soapCall('tkCreateIdentification', [$payload]); } catch (SoapFault $e) { - throw new \RuntimeException('SOAP Fault lors de tkCreateIdentification: ' . $e->getMessage(), 0, $e); + throw new RuntimeException('SOAP Fault lors de tkCreateIdentification: '.$e->getMessage(), 0, $e); } $rs = $response->ReponseStandard ?? null; @@ -77,15 +84,16 @@ final class TokenProvider if (!$ok) { $anom = $rs->Anomalie ?? null; - $code = (string)($anom->Code ?? 'UNKNOWN'); - $sev = (int)($anom->Severite ?? 1); - $msg = (string)($anom->Message ?? 'Authentification refusée'); + $code = (string) ($anom->Code ?? 'UNKNOWN'); + $sev = (int) ($anom->Severite ?? 1); + $msg = (string) ($anom->Message ?? 'Authentification refusée'); + throw new EdnotifException($code, $sev, $msg); } $token = $response->Jeton ?? null; - if (!is_string($token) || $token === '') { - throw new \RuntimeException('Guichet: réponse OK mais Jeton absent.'); + if (!is_string($token) || '' === $token) { + throw new RuntimeException('Guichet: réponse OK mais Jeton absent.'); } return $token; @@ -93,6 +101,6 @@ final class TokenProvider private function getCacheKey(): string { - return 'ednotif.token.' . hash('sha256', $this->entreprise . '|' . $this->login); + return 'ednotif.token.'.hash('sha256', $this->entreprise.'|'.$this->login); } } diff --git a/src/Bovin/Api/BovinApi.php b/src/Bovin/Api/BovinApi.php new file mode 100644 index 0000000..9b9ff67 --- /dev/null +++ b/src/Bovin/Api/BovinApi.php @@ -0,0 +1,64 @@ +tokenProvider->getToken(); + + $requestPayload = [[ + 'JetonAuthentification' => $token, + 'Exploitation' => [ + 'CodePays' => $this->exploitationCountryCode, + 'NumeroExploitation' => $this->exploitationNumber, + ], + 'Bovin' => [ + 'CodePays' => $countryCode, + 'NumeroNational' => $nationalNumber, + ], + ]]; + + try { + /** @var object $soapResponse */ + $soapResponse = $this->businessClient->__soapCall('IpBGetDossierAnimal', $requestPayload); + } catch (SoapFault $soapFault) { + throw new RuntimeException('SOAP Fault on IpBGetDossierAnimal: '.$soapFault->getMessage(), 0, $soapFault); + } + + // Throw uniquement si Resultat=false (erreur métier) + $standardResponseNode = $soapResponse->ReponseStandard ?? null; + $isOk = is_object($standardResponseNode) && (($standardResponseNode->Resultat ?? false) === true); + + if (!$isOk) { + $anomalyNode = is_object($standardResponseNode) ? ($standardResponseNode->Anomalie ?? null) : null; + + throw new EdnotifException( + codeAnomalie: (string) ($anomalyNode->Code ?? 'UNKNOWN'), + severite: (int) ($anomalyNode->Severite ?? 1), + message: (string) ($anomalyNode->Message ?? 'EDNOTIF error') + ); + } + + return $this->bovinDossierMapper->map($soapResponse); + } +} diff --git a/src/Bovin/Api/BovinApiInterface.php b/src/Bovin/Api/BovinApiInterface.php new file mode 100644 index 0000000..f162fda --- /dev/null +++ b/src/Bovin/Api/BovinApiInterface.php @@ -0,0 +1,12 @@ + $presencePeriods + */ + public function __construct( + public StandardResponseDto $standardResponse, + public ?BovinIdentificationDto $identification, + public array $presencePeriods, + public ?object $rawSoapResponse, // pour garder 100% des data + ) {} +} diff --git a/src/Bovin/Dto/BovinIdentificationDto.php b/src/Bovin/Dto/BovinIdentificationDto.php new file mode 100644 index 0000000..4e9b48c --- /dev/null +++ b/src/Bovin/Dto/BovinIdentificationDto.php @@ -0,0 +1,20 @@ + $identiteBovin + * @param array $identiteBovin * @param list, sortie?: array}> $periodesPresence */ public function __construct( @@ -15,6 +15,5 @@ final readonly class DossierAnimalDto public array $identiteBovin, public array $periodesPresence, public object $rawResponse, - ) { - } + ) {} } diff --git a/src/Bovin/Dto/ExploitationRef.php b/src/Bovin/Dto/ExploitationRef.php new file mode 100644 index 0000000..710c8f8 --- /dev/null +++ b/src/Bovin/Dto/ExploitationRef.php @@ -0,0 +1,13 @@ +mapStandardResponse($soapResponse->ReponseStandard ?? null); + + $specificResponseNode = $soapResponse->ReponseSpecifique ?? null; + $bovinNode = is_object($specificResponseNode) ? ($specificResponseNode->Bovin ?? null) : null; + + $identification = null; + $presencePeriods = []; + + if (is_object($bovinNode)) { + $identificationNode = $bovinNode->IdentiteBovin ?? null; + if (is_object($identificationNode)) { + $identification = $this->mapIdentification($identificationNode); + } + + $presencePeriodsNode = $bovinNode->PeriodesPresences->PeriodePresence ?? null; + foreach ($this->normalizeToList($presencePeriodsNode) as $presencePeriodNode) { + if (!is_object($presencePeriodNode)) { + continue; + } + $presencePeriods[] = $this->mapPresencePeriod($presencePeriodNode); + } + } + + return new AnimalFileDto( + standardResponse: $standardResponse, + identification: $identification, + presencePeriods: $presencePeriods, + rawSoapResponse: $soapResponse + ); + } + + private function mapStandardResponse(mixed $standardResponseNode): StandardResponseDto + { + $result = (bool) ($standardResponseNode->Resultat ?? false); + + $anomalyNode = $standardResponseNode->Anomalie ?? null; + $anomaly = null; + + if (is_object($anomalyNode)) { + $anomaly = new AnomalyDto( + code: $this->toNullableString($anomalyNode->Code ?? null), + severity: $this->toNullableInt($anomalyNode->Severite ?? null), + message: $this->toNullableString($anomalyNode->Message ?? null), + ); + } + + return new StandardResponseDto($result, $anomaly); + } + + private function mapIdentification(object $identificationNode): BovinIdentificationDto + { + $bovinRef = $this->mapBovinRef($identificationNode->Bovin ?? null); + + $birthDate = null; + $birthDateNode = $identificationNode->DateNaissance ?? null; + if (is_object($birthDateNode)) { + $birthDate = new DateValueDto( + date: $this->toNullableDate($birthDateNode->Date ?? null), + completenessFlag: $this->toNullableString($birthDateNode->TemoinCompletude ?? null), + ); + } + + $motherCarrier = $this->mapParentInfo($identificationNode->MerePorteuse ?? null); + $fatherIpg = $this->mapParentInfo($identificationNode->PereIPG ?? null); + $birthExploitation = $this->mapExploitationRef($identificationNode->ExploitationNaissance ?? null); + + return new BovinIdentificationDto( + bovin: $bovinRef, + sex: $this->toNullableString($identificationNode->Sexe ?? null), + breedType: $this->toNullableString($identificationNode->TypeRacial ?? null), + birthDate: $birthDate, + workNumber: $this->toNullableString($identificationNode->NumeroTravail ?? null), + isFilie: $this->toNullableBool($identificationNode->StatutFilie ?? null), + motherCarrier: $motherCarrier, + fatherIpg: $fatherIpg, + birthExploitation: $birthExploitation, + ); + } + + private function mapPresencePeriod(object $presencePeriodNode): PresencePeriodDto + { + $entryNode = $presencePeriodNode->Entree ?? null; + $exitNode = $presencePeriodNode->Sortie ?? null; + + $entryMovement = is_object($entryNode) ? $this->mapMovement($entryNode, 'entry') : null; + $exitMovement = is_object($exitNode) ? $this->mapMovement($exitNode, 'exit') : null; + + return new PresencePeriodDto( + entry: $entryMovement, + exit: $exitMovement, + ); + } + + private function mapMovement(object $movementNode, string $direction): MovementDto + { + $dateValue = null; + $causeValue = null; + + if ('entry' === $direction) { + // SOAP: DateEntree / CauseEntree + $dateValue = $movementNode->DateEntree ?? ($movementNode->Date ?? ($movementNode->DateMouvement ?? null)); + $causeValue = $movementNode->CauseEntree ?? null; + } else { + // SOAP (souvent): DateSortie / CauseSortie + $dateValue = $movementNode->DateSortie ?? ($movementNode->Date ?? ($movementNode->DateMouvement ?? null)); + $causeValue = $movementNode->CauseSortie ?? null; + } + + $exploitationRef = $this->mapExploitationRef($movementNode->Exploitation ?? null); + + return new MovementDto( + date: $this->toNullableDate($dateValue), + cause: $this->toNullableString($causeValue), + exploitation: $exploitationRef, + ); + } + + private function mapParentInfo(mixed $parentNode): ?ParentInfoDto + { + if (!is_object($parentNode)) { + return null; + } + + $bovinRef = $this->mapBovinRef($parentNode->Bovin ?? null); + + return new ParentInfoDto( + bovin: $bovinRef, + breedType: $this->toNullableString($parentNode->TypeRacial ?? null), + ); + } + + private function mapBovinRef(mixed $bovinNode): ?BovinRef + { + if (!is_object($bovinNode)) { + return null; + } + + return new BovinRef( + countryCode: $this->toNullableString($bovinNode->CodePays ?? null), + nationalNumber: $this->toNullableString($bovinNode->NumeroNational ?? null), + ); + } + + private function mapExploitationRef(mixed $exploitationNode): ?ExploitationRef + { + if (!is_object($exploitationNode)) { + return null; + } + + return new ExploitationRef( + countryCode: $this->toNullableString($exploitationNode->CodePays ?? null), + exploitationNumber: $this->toNullableString($exploitationNode->NumeroExploitation ?? null), + ); + } + + /** @return list */ + private function normalizeToList(mixed $value): array + { + if (null === $value) { + return []; + } + + return is_array($value) ? $value : [$value]; + } + + private function toNullableString(mixed $value): ?string + { + if (null === $value) { + return null; + } + $stringValue = trim((string) $value); + + return '' === $stringValue ? null : $stringValue; + } + + private function toNullableInt(mixed $value): ?int + { + if (null === $value) { + return null; + } + if (is_int($value)) { + return $value; + } + if (is_numeric($value)) { + return (int) $value; + } + + return null; + } + + private function toNullableBool(mixed $value): ?bool + { + if (null === $value) { + return null; + } + + return (bool) $value; + } + + private function toNullableDate(mixed $value): ?DateTimeImmutable + { + if (!is_string($value) || '' === trim($value)) { + return null; + } + + try { + return new DateTimeImmutable($value); + } catch (Throwable) { + return null; + } + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 8665e59..b404ebc 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -7,6 +7,9 @@ namespace Malio\EdnotifBundle\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; +use const SOAP_SINGLE_ELEMENT_ARRAYS; +use const WSDL_CACHE_BOTH; + final class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder(): TreeBuilder @@ -26,6 +29,9 @@ final class Configuration implements ConfigurationInterface ->scalarNode('login')->cannotBeEmpty()->isRequired()->end() ->scalarNode('password')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('exploitation_number')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('exploitation_country_code')->defaultValue('FR')->end() + ->integerNode('token_ttl_seconds')->min(30)->defaultValue(900)->end() ->arrayNode('soap_options') @@ -34,11 +40,12 @@ final class Configuration implements ConfigurationInterface ->booleanNode('trace')->defaultFalse()->end() ->booleanNode('exceptions')->defaultTrue()->end() ->integerNode('connection_timeout')->min(1)->defaultValue(15)->end() - ->integerNode('cache_wsdl')->defaultValue(\WSDL_CACHE_BOTH)->end() - ->integerNode('features')->defaultValue(\SOAP_SINGLE_ELEMENT_ARRAYS)->end() + ->integerNode('cache_wsdl')->defaultValue(WSDL_CACHE_BOTH)->end() + ->integerNode('features')->defaultValue(SOAP_SINGLE_ELEMENT_ARRAYS)->end() ->end() ->end() - ->end(); + ->end() + ; return $treeBuilder; } diff --git a/src/DependencyInjection/EdnotifExtension.php b/src/DependencyInjection/EdnotifExtension.php index a78f0e8..f908104 100644 --- a/src/DependencyInjection/EdnotifExtension.php +++ b/src/DependencyInjection/EdnotifExtension.php @@ -14,6 +14,7 @@ final class EdnotifExtension extends Extension public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); + /** @var array{ * guichet_wsdl:string, * metier_wsdl:string, @@ -35,13 +36,16 @@ final class EdnotifExtension extends Extension $container->setParameter('ednotif.zone', $config['zone']); $container->setParameter('ednotif.application', $config['application']); + $container->setParameter('ednotif.exploitation_number', $config['exploitation_number']); + $container->setParameter('ednotif.exploitation_country_code', $config['exploitation_country_code']); + $container->setParameter('ednotif.login', $config['login']); $container->setParameter('ednotif.password', $config['password']); $container->setParameter('ednotif.token_ttl_seconds', $config['token_ttl_seconds']); $container->setParameter('ednotif.soap_options', $config['soap_options']); - $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../../config')); + $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../../config')); $loader->load('services.php'); } } diff --git a/src/EdnotifBundle.php b/src/EdnotifBundle.php index f3d3679..d965220 100644 --- a/src/EdnotifBundle.php +++ b/src/EdnotifBundle.php @@ -6,6 +6,4 @@ namespace Malio\EdnotifBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; -final class EdnotifBundle extends Bundle -{ -} +final class EdnotifBundle extends Bundle {} diff --git a/src/Shared/Dto/AnomalyDto.php b/src/Shared/Dto/AnomalyDto.php new file mode 100644 index 0000000..78acad5 --- /dev/null +++ b/src/Shared/Dto/AnomalyDto.php @@ -0,0 +1,14 @@ + $soapOptions */ - public function __construct(private array $soapOptions = []) - { - } + public function __construct(private array $soapOptions = []) {} public function create(string $wsdl): SoapClient {