From e7caf1a61f45d46a830e4764d1dee302d67d1432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 28 Oct 2026 13:34:35 -0300 Subject: [PATCH 1/6] feat: add opt-in Composer metadata normalization --- .changelog/composer-metadata-normalization.md | 6 + AGENTS.md | 4 + README.md | 2 + composer.json | 4 + composer.lock | 2996 +++++++++++++---- docs/architecture.rst | 21 + docs/composer-normalization.rst | 139 + docs/index.rst | 1 + docs/instruction-map.rst | 4 + src/Composer/MetadataNormalizer.php | 43 + src/Composer/NormalizationRunner.php | 93 + src/Console/Application.php | 3 + .../Command/NormalizeComposerCommand.php | 46 + .../StandardsServiceProvider.php | 5 + tests/Integration/NormalizeComposerTest.php | 201 ++ 15 files changed, 2924 insertions(+), 644 deletions(-) create mode 100644 .changelog/composer-metadata-normalization.md create mode 100644 docs/composer-normalization.rst create mode 100644 src/Composer/MetadataNormalizer.php create mode 100644 src/Composer/NormalizationRunner.php create mode 100644 src/Console/Command/NormalizeComposerCommand.php create mode 100644 tests/Integration/NormalizeComposerTest.php diff --git a/.changelog/composer-metadata-normalization.md b/.changelog/composer-metadata-normalization.md new file mode 100644 index 0000000..520b2f5 --- /dev/null +++ b/.changelog/composer-metadata-normalization.md @@ -0,0 +1,6 @@ +--- +category: added +type: minor +--- + +Add opt-in `normalize-composer` metadata checking and explicit `--apply`, using the composer-normalize library while retaining literal dependency constraints and significant ordering. Keep PHP check/fix unchanged; never execute Composer scripts/plugins or update locks, and report stale hashes before/after explicit manifest writes. diff --git a/AGENTS.md b/AGENTS.md index 199f035..c73cdb8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,9 @@ reusables under `.github/workflows/code-standards*.yml`, own event workflows use - `check` MUST NOT pass `--fix`, update dependencies, execute application tests, publish or change source. Tool caches may live in the system temporary directory. - `fix` is explicit, local and potentially partial on failure; inspect its diff. +- `normalize-composer` is opt-in metadata checking; only `--apply` writes + `composer.json`. Never maintain locks, activate plugins or run Composer scripts + from normalization. Preserve literal constraints and significant ordering. - No implicit risky migration, PHP-version migration or semantic refactor profile. Future semantic work requires an explicit consumer target PHP and opt-in. - PR checks use `pull_request`, exact head/base, merge-base comparison and Git's @@ -75,6 +78,7 @@ for the exact local base/head before publication; do not claim remote CI evidenc - [Policy comparison](docs/policy.rst): evidence, rule differences and extraction gates. - [Explicit Rector](docs/rector.rst): opt-in native semantic preview/application; executable configuration owns target PHP, rules, source paths and caches. - [Magic-number analysis](docs/magic-numbers.rst): opt-in native PHPMND selection, policy options, output and exit contracts; separate from check/fix. +- [Composer normalization](docs/composer-normalization.rst): opt-in metadata check/application, constraint preservation and lock-hash diagnostics; separate from PHP check/fix. - [Resolved preset](docs/resolved-preset.rst): compiled rules, golden contracts and dependency-update review. - [Legacy fixer comparison](docs/legacy-fixer-comparison.rst): fixed-source inventory, loaded dependencies and remaining profile decisions. - [PHPDoc comparison](docs/phpdoc-comparison.rst): actual custom-rule registration, source contracts and remaining golden decisions. diff --git a/README.md b/README.md index 41598d4..764a7c5 100644 --- a/README.md +++ b/README.md @@ -183,11 +183,13 @@ its input contract independently of Composer package constraints. | --- | --- | | `code-standards check` | Parse selected PHP and check style without changing source. | | `code-standards fix` | Explicitly format selected source, then verify; failures may leave partial changes. | +| [`code-standards normalize-composer`](docs/composer-normalization.rst) | Check Composer metadata without writing; `--apply` changes only `composer.json` and reports stale lock hashes. | | `code-standards refactor --config=rector.php` | Preview native semantic rules; only `--apply` permits writes, followed by verification. | | [`code-standards magic-numbers`](docs/magic-numbers.rst) | Analyze selected source with opt-in PHPMND, independently of check/fix; findings keep a nonzero status. | | `code-standards refactor --config=rector.php --format=json` | Emit native JSON for preview only; JSON cannot be combined with `--apply`. | | `ECSConfig::configure(?callable $customize)` | Return the native ECS builder; apply the optional callback last. | | `help check` / `help fix` / `help refactor` / `help magic-numbers` | Show the installed Symfony option definitions. | +| `help normalize-composer` | Show metadata normalization options and explicit write authority. | Internal CLI services are implementation details. Supported configuration uses native ECS rule imports, skips and configured rules; there is no second DSL. diff --git a/composer.json b/composer.json index 48e1c30..7d6ae27 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,11 @@ "require": { "php": "^8.3", "composer-runtime-api": "^2.2", + "composer/composer": "~2.10.3", "container-interop/service-provider": "^0.4.1", + "ergebnis/json": "~1.7.0", + "ergebnis/json-normalizer": "~4.11.0", + "ergebnis/json-printer": "~3.9.1", "fast-forward/container": "~1.10.1", "phpstan/phpstan": "~2.1.51", "povils/phpmnd": "^3.6.1", diff --git a/composer.lock b/composer.lock index 4d43637..f15cd5d 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": "cd1bb7c704f0ec3e3704269699029fd4", + "content-hash": "f7c485cdfe131ac676da76d9646ec71c", "packages": [ { "name": "brick/varexporter", @@ -56,86 +56,113 @@ "time": "2026-01-06T22:56:00+00:00" }, { - "name": "container-interop/service-provider", - "version": "v0.4.1", + "name": "composer/ca-bundle", + "version": "1.6.0", "source": { "type": "git", - "url": "https://github.com/container-interop/service-provider.git", - "reference": "e04441ca21ef03e10dce70b0af29269281eec6dc" + "url": "https://github.com/composer/ca-bundle.git", + "reference": "2c99cf2e92516985dea4914812b93d9daf01cdd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/container-interop/service-provider/zipball/e04441ca21ef03e10dce70b0af29269281eec6dc", - "reference": "e04441ca21ef03e10dce70b0af29269281eec6dc", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/2c99cf2e92516985dea4914812b93d9daf01cdd6", + "reference": "2c99cf2e92516985dea4914812b93d9daf01cdd6", "shasum": "" }, "require": { - "psr/container": "^1.0 || ^2.0" + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, "autoload": { "psr-4": { - "Interop\\Container\\": "src/" + "Composer\\CaBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Promoting container interoperability through standard service providers", - "homepage": "https://github.com/container-interop/service-provider", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], "support": { - "issues": "https://github.com/container-interop/service-provider/issues", - "source": "https://github.com/container-interop/service-provider/tree/v0.4.1" + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.6.0" }, - "time": "2023-12-14T14:50:12+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-10-20T20:14:05+00:00" }, { - "name": "fast-forward/config", - "version": "v1.7.3", + "name": "composer/class-map-generator", + "version": "1.8.0", "source": { "type": "git", - "url": "https://github.com/php-fast-forward/config.git", - "reference": "2b5ade2cc6a7141c0e7906a555978b9bfafa1edd" + "url": "https://github.com/composer/class-map-generator.git", + "reference": "c0efbeb2f3fd7e23c7f180a59765c0b48a31fbc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fast-forward/config/zipball/2b5ade2cc6a7141c0e7906a555978b9bfafa1edd", - "reference": "2b5ade2cc6a7141c0e7906a555978b9bfafa1edd", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/c0efbeb2f3fd7e23c7f180a59765c0b48a31fbc4", + "reference": "c0efbeb2f3fd7e23c7f180a59765c0b48a31fbc4", "shasum": "" }, "require": { - "brick/varexporter": "^0.5 || ^0.6 || ^0.7", - "laminas/laminas-config-aggregator": "^1.13", - "php": "^8.3", - "psr/container": "^1.0 || ^2.0", - "psr/simple-cache": "^3.0" + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" }, "require-dev": { - "fast-forward/dev-tools": "dev-main", - "symfony/cache": "^7.4" - }, - "suggest": { - "llm/skills": "Optionally synchronize the bundled agent skill in consumer projects." + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" }, "type": "library", "extra": { - "skills": { - "source": "resources/skills" - }, - "grumphp": { - "config-default-path": "vendor/fast-forward/dev-tools/grumphp.yml" - }, "branch-alias": { "dev-main": "1.x-dev" } }, "autoload": { - "files": [ - "src/functions.php" - ], "psr-4": { - "FastForward\\Config\\": "src/" + "Composer\\ClassMapGenerator\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -144,72 +171,103 @@ ], "authors": [ { - "name": "Felipe Sayão Lobato Abreu", - "email": "github@mentordosnerds.com" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" } ], - "description": "Fast Forward Config utility classes", - "homepage": "https://php-fast-forward.github.io/config/", + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], "support": { - "issues": "https://github.com/php-fast-forward/config/issues", - "source": "https://github.com/php-fast-forward/config" + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.8.0" }, "funding": [ { - "url": "https://www.paypal.com/donate/?business=JLDAF45XZ8D84", + "url": "https://packagist.com", "type": "custom" }, { - "url": "https://github.com/sponsors/php-fast-forward", + "url": "https://github.com/composer", "type": "github" } ], - "time": "2026-10-20T15:00:07+00:00" + "time": "2026-09-25T15:24:19+00:00" }, { - "name": "fast-forward/container", - "version": "v1.10.1", + "name": "composer/composer", + "version": "2.10.3", "source": { "type": "git", - "url": "https://github.com/php-fast-forward/container.git", - "reference": "a75dda2c8a9bd9cb2ef5bb25abc1a6b09de8a298" + "url": "https://github.com/composer/composer.git", + "reference": "f0de0bf90226853b841672f086d8b58b02332504" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fast-forward/container/zipball/a75dda2c8a9bd9cb2ef5bb25abc1a6b09de8a298", - "reference": "a75dda2c8a9bd9cb2ef5bb25abc1a6b09de8a298", + "url": "https://api.github.com/repos/composer/composer/zipball/f0de0bf90226853b841672f086d8b58b02332504", + "reference": "f0de0bf90226853b841672f086d8b58b02332504", "shasum": "" }, "require": { - "container-interop/service-provider": "^0.4.1", - "fast-forward/config": "^1.6", - "php": "^8.3", - "psr/container": "^1.1 || ^2.0" + "composer/ca-bundle": "^1.5", + "composer/class-map-generator": "^1.4.0", + "composer/metadata-minifier": "^1.0", + "composer/pcre": "^2.3 || ^3.3", + "composer/semver": "^3.3", + "composer/spdx-licenses": "^1.5.7", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "justinrainbow/json-schema": "^6.5.1", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "react/promise": "^3.3", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "seld/signal-handler": "^2.0", + "symfony/console": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0", + "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.1.10 || ^8.0", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", + "symfony/polyfill-php84": "^1.30", + "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0" }, "require-dev": { - "fast-forward/dev-tools": "dev-main" + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0", + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" }, "suggest": { - "llm/skills": "Automatically synchronize the bundled agent skill in consumer projects." + "ext-curl": "Provides HTTP support (will fallback to PHP streams if missing)", + "ext-openssl": "Enables access to repositories and packages over HTTPS", + "ext-zip": "Allows direct extraction of ZIP archives (unzip/7z binaries will be used instead if available)", + "ext-zlib": "Enables gzip for HTTP requests" }, + "bin": [ + "bin/composer" + ], "type": "library", "extra": { - "skills": { - "source": "resources/skills" - }, - "grumphp": { - "config-default-path": "vendor/fast-forward/dev-tools/grumphp.yml" + "phpstan": { + "includes": [ + "phpstan/rules.neon" + ] }, "branch-alias": { - "dev-main": "1.x-dev" + "dev-main": "2.10-dev" } }, "autoload": { - "files": [ - "src/functions.php" - ], "psr-4": { - "FastForward\\Container\\": "src/" + "Composer\\": "src/Composer/" } }, "notification-url": "https://packagist.org/downloads/", @@ -218,289 +276,366 @@ ], "authors": [ { - "name": "Felipe Sayão Lobato Abreu", - "email": "github@mentordosnerds.com" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" } ], - "description": "Fast Forward PSR-11 Container utility classes", - "homepage": "https://php-fast-forward.github.io/container/", + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], "support": { - "issues": "https://github.com/php-fast-forward/container/issues", - "source": "https://github.com/php-fast-forward/container" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/composer/issues", + "security": "https://github.com/composer/composer/security/policy", + "source": "https://github.com/composer/composer/tree/2.10.3" }, "funding": [ { - "url": "https://github.com/php-fast-forward", + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", "type": "github" } ], - "time": "2026-10-18T08:00:17+00:00" + "time": "2026-08-27T11:34:23+00:00" }, { - "name": "laminas/laminas-config-aggregator", - "version": "1.20.0", + "name": "composer/metadata-minifier", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-config-aggregator.git", - "reference": "75331b17a9ba72c5fa8679988ef08a588ddd86af" + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "8e86142e3ade750b837d55e55f0cdc786d8da006" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-config-aggregator/zipball/75331b17a9ba72c5fa8679988ef08a588ddd86af", - "reference": "75331b17a9ba72c5fa8679988ef08a588ddd86af", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/8e86142e3ade750b837d55e55f0cdc786d8da006", + "reference": "8e86142e3ade750b837d55e55f0cdc786d8da006", "shasum": "" }, "require": { - "brick/varexporter": "^0.5.0 || ^0.4.0 || ^0.6.0 || ^0.7.0", - "laminas/laminas-stdlib": "^3.18.0", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", - "webimpress/safe-writer": "^2.2.0" - }, - "conflict": { - "nikic/php-parser": "<4.12", - "zendframework/zend-config-aggregator": "*" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "laminas/laminas-coding-standard": "~3.1.0", - "laminas/laminas-config": "^3.10.1", - "phpunit/phpunit": "^11.5.42", - "psalm/plugin-phpunit": "^0.19.5", - "vimeo/psalm": "^6.13.1" - }, - "suggest": { - "laminas/laminas-config": "Allows loading configuration from XML, INI, YAML, and JSON files", - "laminas/laminas-config-aggregator-modulemanager": "Allows loading configuration from laminas-mvc Module classes", - "laminas/laminas-config-aggregator-parameters": "Allows usage of templated parameters within your configuration" + "composer/composer": "^2", + "phpstan/phpstan": "^1", + "symfony/phpunit-bridge": "^4.2 || ^5 || ^6 || ^7" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, "autoload": { "psr-4": { - "Laminas\\ConfigAggregator\\": "src/" + "Composer\\MetadataMinifier\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Lightweight library for collecting and merging configuration from different sources", - "homepage": "https://laminas.dev", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", "keywords": [ - "config-aggregator", - "laminas" + "composer", + "compression" ], "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-config-aggregator/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-config-aggregator/issues", - "rss": "https://github.com/laminas/laminas-config-aggregator/releases.atom", - "source": "https://github.com/laminas/laminas-config-aggregator" + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.1" }, "funding": [ { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" } ], - "time": "2026-06-01T15:37:55+00:00" + "time": "2026-06-30T11:34:59+00:00" }, { - "name": "laminas/laminas-stdlib", - "version": "3.21.0", + "name": "composer/pcre", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-stdlib.git", - "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096" + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/b1c81514cfe158aadf724c42b34d3d0a8164c096", - "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + "php": "^7.4 || ^8.0" }, "conflict": { - "zendframework/zend-stdlib": "*" + "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "laminas/laminas-coding-standard": "^3.1.0", - "phpbench/phpbench": "^1.4.1", - "phpunit/phpunit": "^11.5.42", - "psalm/plugin-phpunit": "^0.19.5", - "vimeo/psalm": "^6.13.1" + "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": { - "Laminas\\Stdlib\\": "src/" + "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "SPL extensions, array utilities, error handlers, and more", - "homepage": "https://laminas.dev", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", "keywords": [ - "laminas", - "stdlib" + "PCRE", + "preg", + "regex", + "regular expression" ], "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-stdlib/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-stdlib/issues", - "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", - "source": "https://github.com/laminas/laminas-stdlib" + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" + "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": "2025-10-11T18:13:12+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { - "name": "nikic/php-parser", - "version": "v5.9.0", + "name": "composer/semver", + "version": "3.5.2", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def" + "url": "https://github.com/composer/semver.git", + "reference": "9dfce66f1e97360ea5e6d3e38ab14277e4cc591e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def", - "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def", + "url": "https://api.github.com/repos/composer/semver/zipball/9dfce66f1e97360ea5e6d3e38ab14277e4cc591e", + "reference": "9dfce66f1e97360ea5e6d3e38ab14277e4cc591e", "shasum": "" }, "require": { - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, - "bin": [ - "bin/php-parse" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "5.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" + "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "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": "A PHP parser written in PHP", + "description": "Version comparison library that offers utilities, version constraint parsing and validation.", "keywords": [ - "parser", - "php" + "semantic", + "semver", + "validation", + "versioning" ], "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.5.2" }, - "time": "2026-09-13T18:51:52+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-10-22T15:37:26+00:00" }, { - "name": "php-parallel-lint/php-console-color", - "version": "v1.0.1", + "name": "composer/spdx-licenses", + "version": "1.6.0", "source": { "type": "git", - "url": "https://github.com/php-parallel-lint/PHP-Console-Color.git", - "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88" + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "5ecd0cb4177696f9fd48f1605dda81db3dee7889" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Color/zipball/7adfefd530aa2d7570ba87100a99e2483a543b88", - "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/5ecd0cb4177696f9fd48f1605dda81db3dee7889", + "reference": "5ecd0cb4177696f9fd48f1605dda81db3dee7889", "shasum": "" }, "require": { - "php": ">=5.3.2" - }, - "replace": { - "jakub-onderka/php-console-color": "*" + "php": "^7.2 || ^8.0" }, "require-dev": { - "php-parallel-lint/php-code-style": "^2.0", - "php-parallel-lint/php-parallel-lint": "^1.0", - "php-parallel-lint/php-var-dump-check": "0.*", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^6.4.25 || ^7.3.3 || ^8.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, "autoload": { "psr-4": { - "PHP_Parallel_Lint\\PhpConsoleColor\\": "src/" + "Composer\\Spdx\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "MIT" ], "authors": [ { - "name": "Jakub Onderka", - "email": "jakub.onderka@gmail.com" + "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": "Simple library for creating colored console ouput.", + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], "support": { - "issues": "https://github.com/php-parallel-lint/PHP-Console-Color/issues", - "source": "https://github.com/php-parallel-lint/PHP-Console-Color/tree/v1.0.1" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.6.0" }, - "time": "2021-12-25T06:49:29+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-04-08T20:18:39+00:00" }, { - "name": "php-parallel-lint/php-console-highlighter", - "version": "v1.0.0", + "name": "composer/xdebug-handler", + "version": "3.0.5", "source": { "type": "git", - "url": "https://github.com/php-parallel-lint/PHP-Console-Highlighter.git", - "reference": "5b4803384d3303cf8e84141039ef56c8a123138d" + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Highlighter/zipball/5b4803384d3303cf8e84141039ef56c8a123138d", - "reference": "5b4803384d3303cf8e84141039ef56c8a123138d", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=5.3.2", - "php-parallel-lint/php-console-color": "^1.0.1" - }, - "replace": { - "jakub-onderka/php-console-highlighter": "*" + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" }, "require-dev": { - "php-parallel-lint/php-code-style": "^2.0", - "php-parallel-lint/php-parallel-lint": "^1.0", - "php-parallel-lint/php-var-dump-check": "0.*", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { "psr-4": { - "PHP_Parallel_Lint\\PhpConsoleHighlighter\\": "src/" + "Composer\\XdebugHandler\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -509,165 +644,200 @@ ], "authors": [ { - "name": "Jakub Onderka", - "email": "acci@acci.cz", - "homepage": "http://www.acci.cz/" + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" } ], - "description": "Highlight PHP code in terminal", + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], "support": { - "issues": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues", - "source": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/tree/v1.0.0" + "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" }, - "time": "2022-02-18T08:23:19+00:00" + "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": "phpstan/phpstan", - "version": "2.1.51", + "name": "container-interop/service-provider", + "version": "v0.4.1", + "source": { + "type": "git", + "url": "https://github.com/container-interop/service-provider.git", + "reference": "e04441ca21ef03e10dce70b0af29269281eec6dc" + }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc3b523c45e714c70de2ac5113b958223b55dc59", - "reference": "dc3b523c45e714c70de2ac5113b958223b55dc59", + "url": "https://api.github.com/repos/container-interop/service-provider/zipball/e04441ca21ef03e10dce70b0af29269281eec6dc", + "reference": "e04441ca21ef03e10dce70b0af29269281eec6dc", "shasum": "" }, "require": { - "php": "^7.4|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" + "psr/container": "^1.0 || ^2.0" }, - "bin": [ - "phpstan", - "phpstan.phar" - ], "type": "library", "autoload": { - "files": [ - "bootstrap.php" - ] + "psr-4": { + "Interop\\Container\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], + "description": "Promoting container interoperability through standard service providers", + "homepage": "https://github.com/container-interop/service-provider", "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" + "issues": "https://github.com/container-interop/service-provider/issues", + "source": "https://github.com/container-interop/service-provider/tree/v0.4.1" }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2026-04-21T18:22:01+00:00" + "time": "2023-12-14T14:50:12+00:00" }, { - "name": "phpunit/php-timer", - "version": "8.0.0", + "name": "ergebnis/json", + "version": "1.7.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + "url": "https://github.com/ergebnis/json.git", + "reference": "829ce8b25c5e452249492c824f1d227777709403" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", - "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "url": "https://api.github.com/repos/ergebnis/json/zipball/829ce8b25c5e452249492c824f1d227777709403", + "reference": "829ce8b25c5e452249492c824f1d227777709403", "shasum": "" }, "require": { - "php": ">=8.3" + "ext-json": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" }, "require-dev": { - "phpunit/phpunit": "^12.0" + "ergebnis/data-provider": "^3.6.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.63.3", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.25.0", + "ergebnis/rector-rules": "^1.18.4", + "fakerphp/faker": "^1.24.1", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.2.14", + "phpstan/phpstan-deprecation-rules": "^2.0.5", + "phpstan/phpstan-phpunit": "^2.0.18", + "phpstan/phpstan-strict-rules": "^2.0.12", + "phpunit/phpunit": "^9.6.24", + "rector/rector": "^2.6.7" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "8.0-dev" + "dev-main": "1.7-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Ergebnis\\Json\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Provides a Json value object for representing a valid JSON string.", + "homepage": "https://github.com/ergebnis/json", "keywords": [ - "timer" + "json" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + "issues": "https://github.com/ergebnis/json/issues", + "security": "https://github.com/ergebnis/json/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json" }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2025-02-07T04:59:38+00:00" + "time": "2026-09-24T19:42:16+00:00" }, { - "name": "povils/phpmnd", - "version": "v3.6.1", + "name": "ergebnis/json-normalizer", + "version": "4.11.0", "source": { "type": "git", - "url": "https://github.com/povils/phpmnd.git", - "reference": "2002af50f627a03d94d01529a3eb02754a23578d" + "url": "https://github.com/ergebnis/json-normalizer.git", + "reference": "db539052df534709ca899620844bf046d0fa34f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/povils/phpmnd/zipball/2002af50f627a03d94d01529a3eb02754a23578d", - "reference": "2002af50f627a03d94d01529a3eb02754a23578d", + "url": "https://api.github.com/repos/ergebnis/json-normalizer/zipball/db539052df534709ca899620844bf046d0fa34f2", + "reference": "db539052df534709ca899620844bf046d0fa34f2", "shasum": "" }, "require": { - "composer-runtime-api": "^2.0", - "nikic/php-parser": "^4.18 || ^5.0", - "php": "^7.4 || ^8.0", - "php-parallel-lint/php-console-highlighter": "^1.0", - "phpunit/php-timer": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0", - "symfony/console": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0", - "symfony/finder": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0" + "ergebnis/json": "^1.2.0", + "ergebnis/json-pointer": "^3.4.0", + "ergebnis/json-printer": "^3.5.0", + "ergebnis/json-schema-validator": "^4.2.0", + "ext-json": "*", + "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" }, "require-dev": { - "phpunit/phpunit": "^9.6", - "squizlabs/php_codesniffer": "^2.8.1 || ^3.5" + "composer/semver": "^3.4.4", + "ergebnis/data-provider": "^3.6.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.63.3", + "ergebnis/phpunit-slow-test-detector": "^2.25.0", + "ergebnis/rector-rules": "^1.18.4", + "fakerphp/faker": "^1.24.1", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.2.15", + "phpstan/phpstan-deprecation-rules": "^2.0.5", + "phpstan/phpstan-phpunit": "^2.0.18", + "phpstan/phpstan-strict-rules": "^2.0.12", + "phpunit/phpunit": "^9.6.19", + "rector/rector": "^2.6.7" + }, + "suggest": { + "composer/semver": "If you want to use ComposerJsonNormalizer or VersionConstraintNormalizer" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.11-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } }, - "bin": [ - "bin/phpmnd" - ], - "type": "application", "autoload": { "psr-4": { - "Povils\\PHPMND\\": "src/" + "Ergebnis\\Json\\Normalizer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -676,43 +846,71 @@ ], "authors": [ { - "name": "Povilas Susinskas", - "email": "povilassusinskas@gmail.com" + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" } ], - "description": "A tool to detect Magic numbers in codebase", + "description": "Provides generic and vendor-specific normalizers for normalizing JSON documents.", + "homepage": "https://github.com/ergebnis/json-normalizer", + "keywords": [ + "json", + "normalizer" + ], "support": { - "issues": "https://github.com/povils/phpmnd/issues", - "source": "https://github.com/povils/phpmnd/tree/v3.6.1" + "issues": "https://github.com/ergebnis/json-normalizer/issues", + "security": "https://github.com/ergebnis/json-normalizer/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-normalizer" }, - "time": "2026-02-21T20:13:10+00:00" + "time": "2026-09-24T19:42:25+00:00" }, { - "name": "psr/container", - "version": "2.0.2", + "name": "ergebnis/json-pointer", + "version": "3.9.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "url": "https://github.com/ergebnis/json-pointer.git", + "reference": "414a90039cdf458eeb71297315a2be6de195697a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/ergebnis/json-pointer/zipball/414a90039cdf458eeb71297315a2be6de195697a", + "reference": "414a90039cdf458eeb71297315a2be6de195697a", "shasum": "" }, "require": { - "php": ">=7.4.0" + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" + }, + "require-dev": { + "ergebnis/data-provider": "^3.6.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.63.3", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.25.0", + "ergebnis/rector-rules": "^1.18.3", + "fakerphp/faker": "^1.24.1", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.2.14", + "phpstan/phpstan-deprecation-rules": "^2.0.5", + "phpstan/phpstan-phpunit": "^2.0.18", + "phpstan/phpstan-strict-rules": "^2.0.12", + "phpunit/phpunit": "^9.6.34", + "rector/rector": "^2.6.7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-main": "3.9-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" } }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "Ergebnis\\Json\\Pointer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -721,51 +919,73 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Provides an abstraction of a JSON pointer.", + "homepage": "https://github.com/ergebnis/json-pointer", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "RFC6901", + "json", + "pointer" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "issues": "https://github.com/ergebnis/json-pointer/issues", + "security": "https://github.com/ergebnis/json-pointer/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-pointer" }, - "time": "2021-11-05T16:47:00+00:00" + "time": "2026-09-24T19:42:34+00:00" }, { - "name": "psr/simple-cache", - "version": "3.0.0", + "name": "ergebnis/json-printer", + "version": "3.9.1", "source": { "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + "url": "https://github.com/ergebnis/json-printer.git", + "reference": "a9bbf3816ca39382d6fb4b4ad58018e5ea4f3cb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "url": "https://api.github.com/repos/ergebnis/json-printer/zipball/a9bbf3816ca39382d6fb4b4ad58018e5ea4f3cb4", + "reference": "a9bbf3816ca39382d6fb4b4ad58018e5ea4f3cb4", "shasum": "" }, "require": { - "php": ">=8.0.0" + "ext-json": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" + }, + "require-dev": { + "ergebnis/data-provider": "^3.7.0", + "ergebnis/license": "^2.8.0", + "ergebnis/php-cs-fixer-config": "^6.64.1", + "ergebnis/phpunit-slow-test-detector": "^2.26.0", + "ergebnis/rector-rules": "^1.18.5", + "fakerphp/faker": "^1.24.0", + "infection/infection": "~0.26.6", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.2.16", + "phpstan/phpstan-deprecation-rules": "^2.0.5", + "phpstan/phpstan-phpunit": "^2.1.1", + "phpstan/phpstan-strict-rules": "^2.1.0", + "phpunit/phpunit": "^9.6.21", + "rector/rector": "^2.7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-main": "3.9-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" } }, "autoload": { "psr-4": { - "Psr\\SimpleCache\\": "src/" + "Ergebnis\\Json\\Printer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -774,135 +994,1721 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" } ], - "description": "Common interfaces for simple caching", + "description": "Provides a JSON printer, allowing for flexible indentation.", + "homepage": "https://github.com/ergebnis/json-printer", "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" + "formatter", + "json", + "printer" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + "issues": "https://github.com/ergebnis/json-printer/issues", + "security": "https://github.com/ergebnis/json-printer/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-printer" }, - "time": "2021-10-29T13:26:27+00:00" + "time": "2026-10-21T19:02:11+00:00" }, { - "name": "rector/rector", - "version": "2.4.2", + "name": "ergebnis/json-schema-validator", + "version": "4.6.0", "source": { "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946" + "url": "https://github.com/ergebnis/json-schema-validator.git", + "reference": "228bf86d963c06c863a3143c9d8531bb3ef72fbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/e645b6463c6a88ea5b44b17d3387d35a912c7946", + "url": "https://api.github.com/repos/ergebnis/json-schema-validator/zipball/228bf86d963c06c863a3143c9d8531bb3ef72fbe", + "reference": "228bf86d963c06c863a3143c9d8531bb3ef72fbe", + "shasum": "" + }, + "require": { + "ergebnis/json": "^1.2.0", + "ergebnis/json-pointer": "^3.4.0", + "ext-json": "*", + "justinrainbow/json-schema": "^5.2.12 || ^6.0.0", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" + }, + "require-dev": { + "ergebnis/data-provider": "^3.6.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.63.3", + "ergebnis/phpunit-slow-test-detector": "^2.25.0", + "ergebnis/rector-rules": "^1.18.4", + "fakerphp/faker": "^1.24.0", + "infection/infection": "~0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.2.15", + "phpstan/phpstan-deprecation-rules": "^2.0.5", + "phpstan/phpstan-phpunit": "^2.0.18", + "phpstan/phpstan-strict-rules": "^2.0.12", + "phpunit/phpunit": "^9.6.20", + "rector/rector": "^2.6.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.6-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\Json\\SchemaValidator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a JSON schema validator, building on top of justinrainbow/json-schema.", + "homepage": "https://github.com/ergebnis/json-schema-validator", + "keywords": [ + "json", + "schema", + "validator" + ], + "support": { + "issues": "https://github.com/ergebnis/json-schema-validator/issues", + "security": "https://github.com/ergebnis/json-schema-validator/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/json-schema-validator" + }, + "time": "2026-09-24T19:42:52+00:00" + }, + { + "name": "fast-forward/config", + "version": "v1.7.3", + "source": { + "type": "git", + "url": "https://github.com/php-fast-forward/config.git", + "reference": "2b5ade2cc6a7141c0e7906a555978b9bfafa1edd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fast-forward/config/zipball/2b5ade2cc6a7141c0e7906a555978b9bfafa1edd", + "reference": "2b5ade2cc6a7141c0e7906a555978b9bfafa1edd", + "shasum": "" + }, + "require": { + "brick/varexporter": "^0.5 || ^0.6 || ^0.7", + "laminas/laminas-config-aggregator": "^1.13", + "php": "^8.3", + "psr/container": "^1.0 || ^2.0", + "psr/simple-cache": "^3.0" + }, + "require-dev": { + "fast-forward/dev-tools": "dev-main", + "symfony/cache": "^7.4" + }, + "suggest": { + "llm/skills": "Optionally synchronize the bundled agent skill in consumer projects." + }, + "type": "library", + "extra": { + "skills": { + "source": "resources/skills" + }, + "grumphp": { + "config-default-path": "vendor/fast-forward/dev-tools/grumphp.yml" + }, + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastForward\\Config\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Felipe Sayão Lobato Abreu", + "email": "github@mentordosnerds.com" + } + ], + "description": "Fast Forward Config utility classes", + "homepage": "https://php-fast-forward.github.io/config/", + "support": { + "issues": "https://github.com/php-fast-forward/config/issues", + "source": "https://github.com/php-fast-forward/config" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate/?business=JLDAF45XZ8D84", + "type": "custom" + }, + { + "url": "https://github.com/sponsors/php-fast-forward", + "type": "github" + } + ], + "time": "2026-10-20T15:00:07+00:00" + }, + { + "name": "fast-forward/container", + "version": "v1.10.1", + "source": { + "type": "git", + "url": "https://github.com/php-fast-forward/container.git", + "reference": "a75dda2c8a9bd9cb2ef5bb25abc1a6b09de8a298" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fast-forward/container/zipball/a75dda2c8a9bd9cb2ef5bb25abc1a6b09de8a298", + "reference": "a75dda2c8a9bd9cb2ef5bb25abc1a6b09de8a298", + "shasum": "" + }, + "require": { + "container-interop/service-provider": "^0.4.1", + "fast-forward/config": "^1.6", + "php": "^8.3", + "psr/container": "^1.1 || ^2.0" + }, + "require-dev": { + "fast-forward/dev-tools": "dev-main" + }, + "suggest": { + "llm/skills": "Automatically synchronize the bundled agent skill in consumer projects." + }, + "type": "library", + "extra": { + "skills": { + "source": "resources/skills" + }, + "grumphp": { + "config-default-path": "vendor/fast-forward/dev-tools/grumphp.yml" + }, + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastForward\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Felipe Sayão Lobato Abreu", + "email": "github@mentordosnerds.com" + } + ], + "description": "Fast Forward PSR-11 Container utility classes", + "homepage": "https://php-fast-forward.github.io/container/", + "support": { + "issues": "https://github.com/php-fast-forward/container/issues", + "source": "https://github.com/php-fast-forward/container" + }, + "funding": [ + { + "url": "https://github.com/php-fast-forward", + "type": "github" + } + ], + "time": "2026-10-18T08:00:17+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.14.0", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "bae415e135dd68260c2d020ba392850231e9b1ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/bae415e135dd68260c2d020ba392850231e9b1ab", + "reference": "bae415e135dd68260c2d020ba392850231e9b1ab", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-json": "*", + "marc-mabe/php-enum": "^4.4", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "dev-main", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Danny van der Sluijs", + "email": "danny.vandersluijs@icloud.com", + "role": "Maintainer" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.14.0" + }, + "time": "2026-10-24T08:53:34+00:00" + }, + { + "name": "laminas/laminas-config-aggregator", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-config-aggregator.git", + "reference": "75331b17a9ba72c5fa8679988ef08a588ddd86af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-config-aggregator/zipball/75331b17a9ba72c5fa8679988ef08a588ddd86af", + "reference": "75331b17a9ba72c5fa8679988ef08a588ddd86af", + "shasum": "" + }, + "require": { + "brick/varexporter": "^0.5.0 || ^0.4.0 || ^0.6.0 || ^0.7.0", + "laminas/laminas-stdlib": "^3.18.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "webimpress/safe-writer": "^2.2.0" + }, + "conflict": { + "nikic/php-parser": "<4.12", + "zendframework/zend-config-aggregator": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~3.1.0", + "laminas/laminas-config": "^3.10.1", + "phpunit/phpunit": "^11.5.42", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13.1" + }, + "suggest": { + "laminas/laminas-config": "Allows loading configuration from XML, INI, YAML, and JSON files", + "laminas/laminas-config-aggregator-modulemanager": "Allows loading configuration from laminas-mvc Module classes", + "laminas/laminas-config-aggregator-parameters": "Allows usage of templated parameters within your configuration" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\ConfigAggregator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Lightweight library for collecting and merging configuration from different sources", + "homepage": "https://laminas.dev", + "keywords": [ + "config-aggregator", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-config-aggregator/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-config-aggregator/issues", + "rss": "https://github.com/laminas/laminas-config-aggregator/releases.atom", + "source": "https://github.com/laminas/laminas-config-aggregator" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2026-06-01T15:37:55+00:00" + }, + { + "name": "laminas/laminas-stdlib", + "version": "3.21.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/b1c81514cfe158aadf724c42b34d3d0a8164c096", + "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "conflict": { + "zendframework/zend-stdlib": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "^3.1.0", + "phpbench/phpbench": "^1.4.1", + "phpunit/phpunit": "^11.5.42", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "stdlib" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2025-10-11T18:13:12+00:00" + }, + { + "name": "marc-mabe/php-enum", + "version": "v4.7.2", + "source": { + "type": "git", + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef", + "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef", + "shasum": "" + }, + "require": { + "ext-reflection": "*", + "php": "^7.1 | ^8.0" + }, + "require-dev": { + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" + } + }, + "autoload": { + "psr-4": { + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" + } + ], + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", + "keywords": [ + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" + ], + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2" + }, + "time": "2025-09-14T11:18:39+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.9.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0" + }, + "time": "2026-09-13T18:51:52+00:00" + }, + { + "name": "php-parallel-lint/php-console-color", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-parallel-lint/PHP-Console-Color.git", + "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Color/zipball/7adfefd530aa2d7570ba87100a99e2483a543b88", + "reference": "7adfefd530aa2d7570ba87100a99e2483a543b88", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "replace": { + "jakub-onderka/php-console-color": "*" + }, + "require-dev": { + "php-parallel-lint/php-code-style": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.0", + "php-parallel-lint/php-var-dump-check": "0.*", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHP_Parallel_Lint\\PhpConsoleColor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "description": "Simple library for creating colored console ouput.", + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Console-Color/issues", + "source": "https://github.com/php-parallel-lint/PHP-Console-Color/tree/v1.0.1" + }, + "time": "2021-12-25T06:49:29+00:00" + }, + { + "name": "php-parallel-lint/php-console-highlighter", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-parallel-lint/PHP-Console-Highlighter.git", + "reference": "5b4803384d3303cf8e84141039ef56c8a123138d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Highlighter/zipball/5b4803384d3303cf8e84141039ef56c8a123138d", + "reference": "5b4803384d3303cf8e84141039ef56c8a123138d", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.2", + "php-parallel-lint/php-console-color": "^1.0.1" + }, + "replace": { + "jakub-onderka/php-console-highlighter": "*" + }, + "require-dev": { + "php-parallel-lint/php-code-style": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.0", + "php-parallel-lint/php-var-dump-check": "0.*", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHP_Parallel_Lint\\PhpConsoleHighlighter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "description": "Highlight PHP code in terminal", + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues", + "source": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/tree/v1.0.0" + }, + "time": "2022-02-18T08:23:19+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.51", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc3b523c45e714c70de2ac5113b958223b55dc59", + "reference": "dc3b523c45e714c70de2ac5113b958223b55dc59", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-04-21T18:22:01+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" + }, + { + "name": "povils/phpmnd", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/povils/phpmnd.git", + "reference": "2002af50f627a03d94d01529a3eb02754a23578d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/povils/phpmnd/zipball/2002af50f627a03d94d01529a3eb02754a23578d", + "reference": "2002af50f627a03d94d01529a3eb02754a23578d", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0", + "nikic/php-parser": "^4.18 || ^5.0", + "php": "^7.4 || ^8.0", + "php-parallel-lint/php-console-highlighter": "^1.0", + "phpunit/php-timer": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "symfony/console": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/finder": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^2.8.1 || ^3.5" + }, + "bin": [ + "bin/phpmnd" + ], + "type": "application", + "autoload": { + "psr-4": { + "Povils\\PHPMND\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Povilas Susinskas", + "email": "povilassusinskas@gmail.com" + } + ], + "description": "A tool to detect Magic numbers in codebase", + "support": { + "issues": "https://github.com/povils/phpmnd/issues", + "source": "https://github.com/povils/phpmnd/tree/v3.6.1" + }, + "time": "2026-02-21T20:13:10+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+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": "rector/rector", + "version": "2.4.2", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/e645b6463c6a88ea5b44b17d3387d35a912c7946", "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946", "shasum": "" }, "require": { - "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.48" + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.48" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.4.2" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2026-04-16T13:07:34+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9a90eb5d32d5a500296bf43f946d60246444d5f7", + "reference": "9a90eb5d32d5a500296bf43f946d60246444d5f7", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.12.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2026-06-12T11:32:29+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "65fb381613fd31745d39a740a112a233f978d2d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/65fb381613fd31745d39a740a112a233f978d2d9", + "reference": "65fb381613fd31745d39a740a112a233f978d2d9", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.3" + }, + "time": "2026-10-01T08:16:23+00:00" + }, + { + "name": "seld/signal-handler", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\Signal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "keywords": [ + "posix", + "sigint", + "signal", + "sigterm", + "unix" + ], + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "time": "2023-09-03T09:24:00+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/21f1bc2296f7ff6043241a8dcbc09559b3732820", + "reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^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/v7.4.21" + }, + "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-10-12T18:45:38+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "fd0e3462be6d32a0d130e8423e4940a7688162d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/fd0e3462be6d32a0d130e8423e4940a7688162d8", + "reference": "fd0e3462be6d32a0d130e8423e4940a7688162d8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.4.21" + }, + "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-10-06T20:05:19+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.4.20", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", + "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.4.20" + }, + "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-09-26T06:31:10+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" }, - "conflict": { - "rector/rector-doctrine": "*", - "rector/rector-downgrade-php": "*", - "rector/rector-phpunit": "*", - "rector/rector-symfony": "*" + "provide": { + "ext-ctype": "*" }, "suggest": { - "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + "ext-ctype": "For best performance" }, - "bin": [ - "bin/rector" - ], "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "homepage": "https://getrector.com/", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", "keywords": [ - "automation", - "dev", - "migration", - "refactoring" + "compatibility", + "ctype", + "polyfill", + "portable" ], "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.4.2" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { - "url": "https://github.com/tomasvotruba", + "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-04-16T13:07:34+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "symfony/console", - "version": "v7.4.21", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/21f1bc2296f7ff6043241a8dcbc09559b3732820", - "reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75", + "reference": "5cac8f4db97208823aa353f59a01faad2da52c75", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2|^8.0" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "php": ">=7.2" }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/lock": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/stopwatch": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "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\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -910,24 +2716,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ - "cli", - "command-line", - "console", - "terminal" + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.21" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0" }, "funding": [ { @@ -947,38 +2755,44 @@ "type": "tidelift" } ], - "time": "2026-10-12T18:45:38+00:00" + "time": "2026-09-25T15:46:47+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.7.1", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", - "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.7-dev" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { "files": [ - "function.php" + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -995,10 +2809,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "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/deprecation-contracts/tree/v3.7.1" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" }, "funding": [ { @@ -1018,36 +2840,46 @@ "type": "tidelift" } ], - "time": "2026-06-05T06:23:12+00:00" + "time": "2026-09-23T22:02:09+00:00" }, { - "name": "symfony/finder", - "version": "v7.4.20", + "name": "symfony/polyfill-mbstring", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", - "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", "shasum": "" }, "require": { - "php": ">=8.2" + "ext-iconv": "*", + "php": ">=7.2" }, - "require-dev": { - "symfony/filesystem": "^6.4|^7.0|^8.0" + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1055,18 +2887,25 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.20" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" }, "funding": [ { @@ -1086,31 +2925,25 @@ "type": "tidelift" } ], - "time": "2026-09-26T06:31:10+00:00" + "time": "2026-09-24T15:19:04+00:00" }, { - "name": "symfony/polyfill-ctype", + "name": "symfony/polyfill-php73", "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", - "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { "php": ">=7.2" }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -1123,8 +2956,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1132,24 +2968,24 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "ctype", "polyfill", - "portable" + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.0" }, "funding": [ { @@ -1169,28 +3005,25 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", + "name": "symfony/polyfill-php80", "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "5cac8f4db97208823aa353f59a01faad2da52c75" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75", - "reference": "5cac8f4db97208823aa353f59a01faad2da52c75", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", "shasum": "" }, "require": { "php": ">=7.2" }, - "suggest": { - "ext-intl": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -1203,14 +3036,21 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "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" @@ -1220,18 +3060,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "grapheme", - "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0" }, "funding": [ { @@ -1251,28 +3089,25 @@ "type": "tidelift" } ], - "time": "2026-09-25T15:46:47+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.43.0", + "name": "symfony/polyfill-php81", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", - "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "shasum": "" }, "require": { "php": ">=7.2" }, - "suggest": { - "ext-intl": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -1285,7 +3120,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, "classmap": [ "Resources/stubs" @@ -1305,18 +3140,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "intl", - "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" }, "funding": [ { @@ -1336,32 +3169,25 @@ "type": "tidelift" } ], - "time": "2026-09-23T22:02:09+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { - "name": "symfony/polyfill-mbstring", + "name": "symfony/polyfill-php84", "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", - "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", "shasum": "" }, "require": { - "ext-iconv": "*", "php": ">=7.2" }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -1374,8 +3200,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1391,17 +3220,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0" }, "funding": [ { @@ -1421,7 +3249,7 @@ "type": "tidelift" } ], - "time": "2026-09-24T15:19:04+00:00" + "time": "2026-09-23T22:01:09+00:00" }, { "name": "symfony/process", @@ -2478,56 +4306,6 @@ ], "time": "2026-10-16T05:55:07+00:00" }, - { - "name": "psr/log", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" - }, - "time": "2024-09-11T13:17:53+00:00" - }, { "name": "sebastian/cli-parser", "version": "4.2.1", @@ -3513,76 +5291,6 @@ ], "time": "2024-10-20T05:08:20+00:00" }, - { - "name": "symfony/filesystem", - "version": "v7.4.21", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "fd0e3462be6d32a0d130e8423e4940a7688162d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/fd0e3462be6d32a0d130e8423e4940a7688162d8", - "reference": "fd0e3462be6d32a0d130e8423e4940a7688162d8", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "require-dev": { - "symfony/process": "^6.4|^7.0|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.21" - }, - "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-10-06T20:05:19+00:00" - }, { "name": "symfony/http-client", "version": "v7.4.21", diff --git a/docs/architecture.rst b/docs/architecture.rst index b2d9988..98dbdd1 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -35,6 +35,27 @@ The supported configuration extension is the native ECS builder. A small PSR-4 p No configuration is needed for the default preset. A consumer selects an existing readable PHP file with ``--config``; relative paths resolve from the project root. That file must return the configuration supported by the installed ECS version. Native builder methods define rule imports, skips and overrides. Invalid paths, unsupported options and conflicting scan modes fail with an objective error; empty paths are rejected rather than coerced to the current directory. ECS configuration errors propagate as failures. Standard Symfony flag semantics apply, including idempotent repeated boolean flags and both ``--option=value`` and ``--option value``. Behavioral tests exercise the default preset, real rule overrides, help outside a checkout and invalid invocations. +Optional Composer metadata normalization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``normalize-composer`` is independent of PHP selection and style check/fix. +It reuses the library behind composer-normalize, ``ergebnis/json-normalizer``, +with the installed Composer schema and JSON printer. The adapter snapshots and +restores literal dependency constraints before printing: dependency policy and +constraint expression ordering are outside metadata formatting authority. +Native normalization retains priority-sensitive repositories, script lists, +autoload paths, wildcard rules, installer paths and patch ordering. + +``composer/composer`` is a runtime library dependency for its local schema and +public lock hash API. This adds 23 packages to the verified lock and promotes +the existing ``psr/log`` and ``symfony/filesystem`` dependencies to runtime; +existing versions remain unchanged. No Composer application/plugin manager is +constructed and no plugin is added to ``allow-plugins``. The integration does +not invoke the upstream plugin's lock-update path. Its explicit ``--apply`` +writes only the manifest and reports stale lock hashes before/after application. +See the `metadata command contract `__ for statuses, +formatting behavior and side effects. PHP source commands retain their scope. + Optional lossless process capture ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/composer-normalization.rst b/docs/composer-normalization.rst new file mode 100644 index 0000000..3470993 --- /dev/null +++ b/docs/composer-normalization.rst @@ -0,0 +1,139 @@ +Optional Composer metadata normalization +======================================== + +``normalize-composer`` checks the current project's ``composer.json`` in memory. +It is opt-in and independent of ``check``, ``fix``, ECS and changed-PHP selection. +No Git checkout is required. Neither the default Composer scripts nor Actions +enable it automatically. + +.. code-block:: sh + + vendor/bin/code-standards help normalize-composer + vendor/bin/code-standards normalize-composer + vendor/bin/code-standards normalize-composer --directory=/path/to/project + # With explicit authority to change Composer metadata: + vendor/bin/code-standards normalize-composer --apply + git diff -- composer.json composer.lock + composer validate --strict --no-plugins --no-scripts + +Options and effects +------------------- + +``--directory`` (``-C``) selects an existing project directory; the default is +the current directory. ``--apply`` defaults to false. Without it the command +reads metadata and reports whether normalization is needed, without writing +source, metadata, lock, vendor, caches or generated artifacts. Standard Symfony +global options appear in installed help. There are no file-selection, ECS +configuration, lock-update, dependency-update or plugin-activation options. +The operation uses the root ``composer.json`` and optional root ``composer.lock``; +missing/unreadable manifest files and symlink metadata files are rejected. + +Apply writes only ``composer.json``. Inspect its diff even on failure: a failed +write can be partial, and a successful write can make the existing lock stale. +An initially stale lock prevents writes. After writing, the command checks the +new manifest against the original lock and reports an incompatible hash with +status 1. It never edits the hash, resolves packages or runs a lock update. +Review any subsequent lock maintenance as a separate dependency operation. +Legacy locks with only ``hash`` use Composer's legacy exact-byte comparison. + +Exit codes +---------- + +* ``0``: the manifest is normalized (or was explicitly normalized), and an + existing lock hash is fresh. No lock is required. +* ``1``: check proposes normalization, the initial lock is stale, or explicit + application left a stale lock. Read the diagnostic to distinguish these cases. +* ``2``: invalid options/directory, missing/unreadable/symlink files, invalid + JSON/schema or controlled filesystem/library failures. + +Normalization and dependency boundary +------------------------------------- + +This integration uses ``ergebnis/json-normalizer`` 4.11.0, the library used by +the verified ``ergebnis/composer-normalize`` 2.54.0 release. ``composer/composer`` +2.10.3 supplies the local schema and public ``Locker::getContentHash()`` API. +The installed schema has the same property definitions/order as that release's +schema. Library dependencies are normal runtime requirements; no Composer plugin +is added or enabled, and ``config.allow-plugins`` is unchanged. + +This is a conservative metadata profile, rather than the plugin's complete CLI. +It retains every original dependency constraint string in ``require``, +``require-dev``, ``conflict``, ``provide`` and ``replace``; the upstream CLI also +rewrites those strings. Package keys and schema properties are normalized. +Sniffed indentation, newline style and presence of a final newline are retained. +The plugin's ``extra.composer-normalize`` indentation overrides and CLI flags +are not consumed. There is no automatic formatting policy migration. + +Deliberate differences from the upstream plugin CLI also include: + +* Check is the default; upstream requires ``--dry-run`` to avoid writes. +* Apply never maintains locks; upstream defaults to updating them unless + ``--no-update-lock`` is selected. This command offers no lock-update escape. +* Only root metadata filenames are accepted, with symlinks rejected; there is + no arbitrary manifest argument or ``--no-check-lock`` switch. +* Check emits a normalization diagnostic, without the plugin's unified diff. + Inspect explicit application with Git; neither output nor flags are CLI-compatible. +* Controlled JSON/schema/input failures use status 2; upstream normalization + validation generally uses status 1. The statuses in this guide are authoritative. + +Upstream normalization retains repository priority (arrays and named objects), +script command lists, autoload file/path lists, wildcard plugin/install rules, +installer paths and patch order. Regression fixtures assert these boundaries, +literal aliases/OR constraints, idempotence and complete file-byte preservation +in check mode. Review arbitrary extension metadata in the resulting diff. + +Only data APIs are used. The command never constructs a Composer application, +plugin manager or installer, starts Composer, loads consumer autoload/config, +executes scripts or fetches a remote schema. Ordinary installation still loads +the tool's Composer autoloader through its binary proxy, as documented in the +`architecture contract `__. This is not a filesystem sandbox +and does not support concurrent edits to the same metadata. + +Dependency cost and alternatives +------------------------------- + +The verified graph adds eight packages for ``ergebnis/json-normalizer`` and +its Composer constraint-parser support. This is the official standalone +normalization library: the plugin itself is not a runtime dependency. +Using it avoids copying its schema traversal, priority exclusions and native +formatting implementation into this project. + +Another fifteen new packages come from ``composer/composer``. They support the +official schema and ``Locker::getContentHash()`` API; they are not all needed +by the in-memory normalizer. This is an intentional cost of preserving native +lock-freshness validation without maintaining a second hash algorithm. Existing +``psr/log`` and ``symfony/filesystem`` also become runtime dependencies, retaining +their locked versions. No existing package version is updated in this change. + +The smaller alternative is the standalone normalizer plus ``composer/semver`` +and a copied official schema. It would still need a separate lock-hash +implementation or weaker lock validation: neither is provided by the reviewed +standalone normalizer API. The Composer plugin would require consumer plugin +policy changes; invoking it manually would defeat that boundary. A PHAR could +isolate its package graph but would introduce artifact verification, packaging +and a second runtime/distribution lifecycle. This increment selects the native +library/hash APIs rather than those additional contracts. + +Library autoloading is still executable code. The newly added graph includes +React Promise's conditional function include and Symfony's PHP polyfill +bootstraps. These were inspected in the resolved releases; they define helper +functions/constants, with version-gated polyfill setup, and do not install or +activate Composer plugins. No newly added package has ``composer-plugin`` type. +These files load with the tool autoloader even when normalization is not chosen; +this integration does not claim namespace or runtime isolation. Composer must +resolve compatibility with a normal consumer installation. + +The direct constraints select tested patch lines (``~4.11.0`` normalizer, +``~2.10.3`` Composer, ``~1.7.0`` JSON and ``~3.9.1`` printer). The lock records the +exact verified versions; consumers resolve their own graph within those lines. +Schema content/order therefore belongs to the installed Composer package. +There is no runtime schema updater, remote-schema fallback, download or global +installation. Dependency updates require reviewing the schema, autoload files, +significant-order fixtures, constraint preservation and lock-hash behavior again. + +References +---------- + +* `composer-normalize 2.54.0 `__ +* `json-normalizer 4.11.0 `__ +* `Composer 2.10.3 `__ diff --git a/docs/index.rst b/docs/index.rst index e8dca4c..69d1e22 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,6 +26,7 @@ Architecture, policy and evidence architecture automation + composer-normalization instruction-map optional-analysis legacy-fixer-comparison diff --git a/docs/instruction-map.rst b/docs/instruction-map.rst index 7497745..679bd51 100644 --- a/docs/instruction-map.rst +++ b/docs/instruction-map.rst @@ -7,4 +7,8 @@ The initial directory was empty and had no repository contracts. The shared host The opt-in refactoring guide remains under this same root authority. It records the native Rector invocation and executable-config boundary; it introduces no new instruction subtree, automatic semantic profile or consumer installer. +The opt-in Composer metadata guide also remains under root authority. Its root +index entry leads to the independent normalization/write/lock-hash contract; +ordinary source and test directories retain the same maintenance instructions. + No previous instruction tree is migrated. Rollback restores the root contract, its index and this map together through Git; it changes no host configuration. diff --git a/src/Composer/MetadataNormalizer.php b/src/Composer/MetadataNormalizer.php new file mode 100644 index 0000000..7db08ba --- /dev/null +++ b/src/Composer/MetadataNormalizer.php @@ -0,0 +1,43 @@ +decoded()) && isset($original->decoded()->{$section})) { + $constraints[$section] = (array) $original->decoded()->{$section}; + } + } + + $normalized = (new ComposerJsonNormalizer('file://' . $this->schema))->normalize($original)->decoded(); + + foreach ($constraints as $section => $packages) { + foreach ($packages as $package => $constraint) { + $normalized->{$section}->{$package} = $constraint; + } + } + + $encoded = json_encode($normalized, $format->jsonEncodeOptions()->toInt() | JSON_THROW_ON_ERROR); + $printed = (new Printer())->print($encoded, $format->indent()->toString(), $format->newLine()->toString()); + + return $printed . ($format->hasFinalNewLine() ? $format->newLine()->toString() : ''); + } +} diff --git a/src/Composer/NormalizationRunner.php b/src/Composer/NormalizationRunner.php new file mode 100644 index 0000000..48f815d --- /dev/null +++ b/src/Composer/NormalizationRunner.php @@ -0,0 +1,93 @@ +read($manifest); + $normalized = $this->normalizer->normalize($source); + $lock = $directory . '/composer.lock'; + $lockData = null; + if (file_exists($lock) || is_link($lock)) { + $decoded = Json::fromString($this->read($lock))->decoded(); + if (!is_object($decoded)) { + throw new RuntimeException('Expected a Composer lock object.'); + } + + $lockData = (array) $decoded; + if (!$this->fresh($source, $lockData)) { + $this->output->writeln('composer.lock is not fresh; review it separately before normalization. No files changed.'); + + return 1; + } + } + + if ($source === $normalized) { + $this->output->writeln('composer.json is normalized.'); + + return 0; + } + if (!$apply) { + $this->output->writeln('composer.json needs normalization. Inspect changes with normalize-composer --apply and git diff.'); + + return 1; + } + if (strlen($normalized) !== file_put_contents($manifest, $normalized)) { + throw new RuntimeException('Cannot write composer.json; inspect the local diff for partial changes.'); + } + + $this->output->writeln('Normalized composer.json. Inspect its diff. composer.lock was not changed.'); + if (null !== $lockData && !$this->fresh($normalized, $lockData)) { + $this->output->writeln('composer.lock content-hash is incompatible after normalization; review lock maintenance separately.'); + + return 1; + } + + return 0; + } + + /** Rejects symlinks rather than reading or overwriting another project's metadata. */ + private function read(string $path): string + { + if (is_link($path) || !is_file($path) || !is_readable($path)) { + throw new RuntimeException('Expected a readable regular metadata file: ' . $path); + } + + $bytes = file_get_contents($path); + if (false === $bytes) { + throw new RuntimeException('Cannot read metadata file: ' . $path); + } + + return $bytes; + } + + /** @param array $lock Uses Composer's public hash API, with no application/bootstrap execution. */ + private function fresh(string $source, array $lock): bool + { + if (!empty($lock['content-hash'])) { + return Locker::getContentHash($source) === $lock['content-hash']; + } + + return !empty($lock['hash']) && md5($source) === $lock['hash']; + } +} diff --git a/src/Console/Application.php b/src/Console/Application.php index fd8c7ca..c60b896 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -7,6 +7,7 @@ use FastForward\CodeStandards\Console\Command\CheckCommand; use FastForward\CodeStandards\Console\Command\FixCommand; use FastForward\CodeStandards\Console\Command\MagicNumbersCommand; +use FastForward\CodeStandards\Console\Command\NormalizeComposerCommand; use FastForward\CodeStandards\Console\Command\RefactorCommand; use Symfony\Component\Console\Application as SymfonyApplication; use Symfony\Component\Console\Command\Command; @@ -24,6 +25,7 @@ public function __construct( FixCommand $fix, RefactorCommand $refactor, MagicNumbersCommand $magicNumbers, + NormalizeComposerCommand $normalizeComposer, ) { parent::__construct('Fast Forward Code Standards'); @@ -31,6 +33,7 @@ public function __construct( $this->addCommand($fix); $this->addCommand($refactor); $this->addCommand($magicNumbers); + $this->addCommand($normalizeComposer); $this->setAutoExit(false); } diff --git a/src/Console/Command/NormalizeComposerCommand.php b/src/Console/Command/NormalizeComposerCommand.php new file mode 100644 index 0000000..ee6acbb --- /dev/null +++ b/src/Console/Command/NormalizeComposerCommand.php @@ -0,0 +1,46 @@ +directories->resolve(true, null, null, $directory); + + return $this->normalization->run($root, $apply); + } catch (InvalidArgumentException|RuntimeException $error) { + $io->getErrorStyle()->error($error->getMessage()); + + return Command::INVALID; + } + } +} diff --git a/src/ServiceProvider/StandardsServiceProvider.php b/src/ServiceProvider/StandardsServiceProvider.php index 8a1475a..04d020a 100644 --- a/src/ServiceProvider/StandardsServiceProvider.php +++ b/src/ServiceProvider/StandardsServiceProvider.php @@ -5,6 +5,7 @@ namespace FastForward\CodeStandards\ServiceProvider; use FastForward\CodeStandards\Analysis\MagicNumbersRunner; +use FastForward\CodeStandards\Composer\MetadataNormalizer; use FastForward\CodeStandards\Process\NativeProcessRunner; use FastForward\CodeStandards\Process\ProcessRunner; use FastForward\CodeStandards\Project\ProjectDirectoryResolver; @@ -57,6 +58,10 @@ public function getFactories(): array { return [ OutputInterface::class => new ServiceFactory($this->output), + MetadataNormalizer::class => new InvokableFactory( + MetadataNormalizer::class, + $this->vendorDirectory . '/composer/composer/res/composer-schema.json', + ), ProcessRunner::class => new CallableFactory($this->process(...)), ProjectScopeResolver::class => new InvokableFactory( ProjectScopeResolver::class, diff --git a/tests/Integration/NormalizeComposerTest.php b/tests/Integration/NormalizeComposerTest.php new file mode 100644 index 0000000..c721f13 --- /dev/null +++ b/tests/Integration/NormalizeComposerTest.php @@ -0,0 +1,201 @@ +directory = sys_get_temp_dir() . '/standards composer café-' . bin2hex(random_bytes(8)); + mkdir($this->directory); + } + + /** Removes only this test's uniquely owned fixture. */ + protected function tearDown(): void + { + (new Filesystem())->remove($this->directory); + } + + /** Check preserves every byte; explicit apply preserves meaningful ordering and never executes consumer code. */ + public function testReadOnlyCheckAndIdempotentExplicitApplyPreservePolicy(): void + { + $manifest = [ + 'scripts' => [ + 'test' => ['@second', '@first'], + 'auto-scripts' => ['z' => 'cmd', 'a' => 'cmd'], + 'post-update-cmd' => '@php -r "file_put_contents(\'executed\', \'unexpected\');"', + ], + 'extra' => [ + 'installer-paths' => ['z/{$name}' => ['type:library'], 'a/{$name}' => ['vendor/*']], + 'patches' => ['vendor/package' => ['Z patch' => 'z.patch', 'A patch' => 'a.patch']], + ], + 'config' => [ + 'allow-plugins' => ['vendor/*' => false, '*' => true], + 'preferred-install' => ['vendor/*' => 'source', '*' => 'dist'], + ], + 'autoload' => ['files' => ['z.php', 'a.php'], 'psr-4' => ['Example\\' => ['z/', 'a/']]], + 'repositories' => [ + 'priority' => ['type' => 'composer', 'url' => 'https://priority.example.test'], + 'fallback' => ['type' => 'composer', 'url' => 'https://fallback.example.test'], + ], + 'keywords' => ['zebra', 'alpha'], + 'description' => 'Synthetic metadata', + 'license' => 'MIT', + 'name' => 'example/fixture', + ]; + foreach (['require', 'require-dev', 'conflict', 'provide', 'replace'] as $section) { + $manifest[$section] = ['z/package' => '^2.0 || ^1.0', 'a/package' => 'dev-main as 1.0.x-dev']; + } + $source = json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; + file_put_contents($this->directory . '/composer.json', $source); + file_put_contents($this->directory . '/source.php', 'directory . '/vendor'); + file_put_contents($this->directory . '/vendor/autoload.php', 'snapshot(); + $check = $this->cli([]); + self::assertSame(1, $check->getExitCode(), $check->getErrorOutput()); + self::assertSame($before, $this->snapshot()); + $apply = $this->cli(['--apply']); + self::assertSame(0, $apply->getExitCode(), $apply->getErrorOutput()); + $normalized = json_decode(file_get_contents($this->directory . '/composer.json'), true, flags: JSON_THROW_ON_ERROR); + self::assertSame('name', array_key_first($normalized)); + self::assertSame($manifest['keywords'], $normalized['keywords']); + foreach (['require', 'require-dev', 'conflict', 'provide', 'replace'] as $section) { + self::assertSame(['a/package', 'z/package'], array_keys($normalized[$section])); + foreach ($manifest[$section] as $package => $constraint) { + self::assertSame($constraint, $normalized[$section][$package]); + } + } + self::assertSame($manifest['repositories'], $normalized['repositories']); + foreach (['scripts', 'autoload'] as $section) { + foreach ($manifest[$section] as $key => $value) { + self::assertSame($value, $normalized[$section][$key]); + } + } + self::assertSame($manifest['extra'], $normalized['extra']); + foreach (['allow-plugins', 'preferred-install'] as $section) { + self::assertSame($manifest['config'][$section], $normalized['config'][$section]); + } + $after = $this->snapshot(); + self::assertSame($before['source.php'], $after['source.php']); + self::assertSame($before['vendor/autoload.php'], $after['vendor/autoload.php']); + self::assertSame(0, $this->cli([])->getExitCode()); + self::assertSame(0, $this->cli(['--apply'])->getExitCode()); + self::assertSame($after, $this->snapshot()); + } + + /** Lock hashes use Composer's own API; apply reports incompatibility without maintaining the lock. */ + public function testApplyReportsChangedHashAndLeavesLockUntouched(): void + { + $source = '{"name":"example/fixture","require":{"z/package":"^2 || ^1","a/package":"^1"}}'; + file_put_contents($this->directory . '/composer.json', $source); + $lock = json_encode(['content-hash' => Locker::getContentHash($source), 'packages' => [], 'packages-dev' => []]); + file_put_contents($this->directory . '/composer.lock', $lock); + $before = $this->snapshot(); + self::assertSame(1, $this->cli([])->getExitCode()); + self::assertSame($before, $this->snapshot()); + + $apply = $this->cli(['--apply']); + self::assertSame(1, $apply->getExitCode(), $apply->getErrorOutput()); + self::assertStringContainsString('content-hash is incompatible after normalization', $apply->getOutput()); + self::assertSame($lock, file_get_contents($this->directory . '/composer.lock')); + $after = $this->snapshot(); + self::assertNotSame($before['composer.json'], $after['composer.json']); + self::assertSame(1, $this->cli(['--apply'])->getExitCode()); + self::assertSame($after, $this->snapshot()); + } + + /** An initially stale lock prevents both preview and apply writes. */ + public function testStaleLockPreventsApply(): void + { + file_put_contents($this->directory . '/composer.json', '{"name":"example/fixture"}'); + file_put_contents($this->directory . '/composer.lock', '{"content-hash":"stale","packages":[]}'); + $before = $this->snapshot(); + $result = $this->cli(['--apply']); + self::assertSame(1, $result->getExitCode()); + self::assertStringContainsString('No files changed', $result->getOutput()); + self::assertSame($before, $this->snapshot()); + } + + /** Formatting retains CRLF/tabs and repository priority arrays; a compatible lock remains byte-identical. */ + public function testFormattingAndFreshLockSurviveApply(): void + { + $source = "{\r\n\t\"repositories\": [\r\n\t\t{\"type\":\"composer\",\"url\":\"https://z.example.test\"},\r\n\t\t{\"type\":\"composer\",\"url\":\"https://a.example.test\"}\r\n\t],\r\n\t\"name\": \"example/fixture\"\r\n}\r\n"; + file_put_contents($this->directory . '/composer.json', $source); + // Sorted repository object properties avoid a meaningful hash change in this fixture. + $lock = json_encode(['content-hash' => Locker::getContentHash($source), 'packages' => []]); + file_put_contents($this->directory . '/composer.lock', $lock); + $result = $this->cli(['--apply', '--directory=' . $this->directory]); + self::assertSame(0, $result->getExitCode(), $result->getOutput() . $result->getErrorOutput()); + $normalized = file_get_contents($this->directory . '/composer.json'); + self::assertStringContainsString("\r\n\t\"name\"", $normalized); + self::assertStringEndsWith("\r\n", $normalized); + self::assertSame( + json_decode($source, true)['repositories'], + json_decode($normalized, true)['repositories'], + ); + self::assertSame($lock, file_get_contents($this->directory . '/composer.lock')); + self::assertSame(0, $this->cli([])->getExitCode()); + } + + /** Invalid JSON/schema and missing files fail without writing; symlinks never redirect metadata access. */ + public function testInvalidInputsAndSymlinksFailWithoutWriting(): void + { + self::assertSame(2, $this->cli([])->getExitCode()); + foreach (['not JSON', '{"require":42}', '[]'] as $source) { + file_put_contents($this->directory . '/composer.json', $source); + self::assertSame(2, $this->cli(['--apply'])->getExitCode()); + self::assertSame($source, file_get_contents($this->directory . '/composer.json')); + } + file_put_contents($this->directory . '/composer.json', '{"name":"example/fixture"}'); + foreach (['invalid lock', '42'] as $lock) { + file_put_contents($this->directory . '/composer.lock', $lock); + self::assertSame(2, $this->cli(['--apply'])->getExitCode()); + self::assertSame('{"name":"example/fixture"}', file_get_contents($this->directory . '/composer.json')); + self::assertSame($lock, file_get_contents($this->directory . '/composer.lock')); + } + unlink($this->directory . '/composer.lock'); + unlink($this->directory . '/composer.json'); + file_put_contents($this->directory . '/other.json', '{"name":"example/fixture"}'); + symlink($this->directory . '/other.json', $this->directory . '/composer.json'); + self::assertSame(2, $this->cli(['--apply'])->getExitCode()); + self::assertSame('{"name":"example/fixture"}', file_get_contents($this->directory . '/other.json')); + } + + /** @param list $options Runs without HTTP file streams/cURL execution to detect unexpected schema downloads. */ + private function cli(array $options): Process + { + $process = new Process([ + PHP_BINARY, '-d', 'allow_url_fopen=0', '-d', 'disable_functions=curl_exec,curl_multi_exec', + dirname(__DIR__, 2) . '/bin/code-standards', 'normalize-composer', ...$options, + ], $this->directory, ['XDEBUG_MODE' => 'off'], timeout: 30); + $process->run(); + + return $process; + } + + /** @return array Complete file-byte inventory catches unexpected writes or generated artifacts. */ + private function snapshot(): array + { + $files = []; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)); + foreach ($iterator as $file) { + $files[substr($file->getPathname(), strlen($this->directory) + 1)] = file_get_contents($file->getPathname()); + } + ksort($files); + + return $files; + } +} From 3dc82718a643f0a712e27c3b9400a69895c32e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 28 Oct 2026 13:40:19 -0300 Subject: [PATCH 2/6] fix: preserve Composer policy priority and extension aliases --- docs/architecture.rst | 4 +- docs/composer-normalization.rst | 12 +++-- src/Composer/MetadataNormalizer.php | 54 ++++++++++++++++----- tests/Integration/NormalizeComposerTest.php | 53 ++++++++++++++++++++ 4 files changed, 107 insertions(+), 16 deletions(-) diff --git a/docs/architecture.rst b/docs/architecture.rst index 98dbdd1..a99b70f 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -43,8 +43,10 @@ It reuses the library behind composer-normalize, ``ergebnis/json-normalizer``, with the installed Composer schema and JSON printer. The adapter snapshots and restores literal dependency constraints before printing: dependency policy and constraint expression ordering are outside metadata formatting authority. +The adapter also restores first-match plugin/install policies exactly and +retains original dependency maps whenever upstream would canonicalize their keys. Native normalization retains priority-sensitive repositories, script lists, -autoload paths, wildcard rules, installer paths and patch ordering. +autoload paths, installer paths and patch ordering. ``composer/composer`` is a runtime library dependency for its local schema and public lock hash API. This adds 23 packages to the verified lock and promotes diff --git a/docs/composer-normalization.rst b/docs/composer-normalization.rst index 3470993..cb446d9 100644 --- a/docs/composer-normalization.rst +++ b/docs/composer-normalization.rst @@ -60,6 +60,10 @@ This is a conservative metadata profile, rather than the plugin's complete CLI. It retains every original dependency constraint string in ``require``, ``require-dev``, ``conflict``, ``provide`` and ``replace``; the upstream CLI also rewrites those strings. Package keys and schema properties are normalized. +If upstream would canonicalize a dependency key, the adapter retains the entire +original section's keys, values and order instead; this preserves extension-name +aliases/collisions and their effective Composer constraints. Suggested package +keys/descriptions receive the same protection. Sniffed indentation, newline style and presence of a final newline are retained. The plugin's ``extra.composer-normalize`` indentation overrides and CLI flags are not consumed. There is no automatic formatting policy migration. @@ -76,9 +80,11 @@ Deliberate differences from the upstream plugin CLI also include: * Controlled JSON/schema/input failures use status 2; upstream normalization validation generally uses status 1. The statuses in this guide are authoritative. -Upstream normalization retains repository priority (arrays and named objects), -script command lists, autoload file/path lists, wildcard plugin/install rules, -installer paths and patch order. Regression fixtures assert these boundaries, +The adapter restores ``allow-plugins`` and ``preferred-install`` policies exactly, +including broad-first wildcard rules: upstream sorts some of these maps by +specificity, which can change Composer's first-match policy. Native normalization +retains repository priority (arrays and named objects), script command lists, +autoload file/path lists, installer paths and patch order. Regression fixtures assert these boundaries, literal aliases/OR constraints, idempotence and complete file-byte preservation in check mode. Review arbitrary extension metadata in the resulting diff. diff --git a/src/Composer/MetadataNormalizer.php b/src/Composer/MetadataNormalizer.php index 7db08ba..447e45b 100644 --- a/src/Composer/MetadataNormalizer.php +++ b/src/Composer/MetadataNormalizer.php @@ -20,24 +20,54 @@ public function normalize(string $source): string { $original = Json::fromString($source); $format = Format::fromJson($original); - $constraints = []; - foreach (['require', 'require-dev', 'conflict', 'provide', 'replace'] as $section) { - if (is_object($original->decoded()) && isset($original->decoded()->{$section})) { - $constraints[$section] = (array) $original->decoded()->{$section}; + // Upstream mutates decoded objects; isolate policy from its normalization input. + $policy = Json::fromString($source)->decoded(); + $normalized = (new ComposerJsonNormalizer('file://' . $this->schema))->normalize($original)->decoded(); + $this->restoreDependencies($policy, $normalized); + $this->restorePriorities($policy, $normalized); + + $encoded = json_encode($normalized, $format->jsonEncodeOptions()->toInt() | JSON_THROW_ON_ERROR); + $printed = (new Printer())->print($encoded, $format->indent()->toString(), $format->newLine()->toString()); + + return $printed . ($format->hasFinalNewLine() ? $format->newLine()->toString() : ''); + } + + /** Retains aliases/collisions in their original order whenever upstream would canonicalize package identities. */ + private function restoreDependencies(object $original, object $normalized): void + { + foreach (['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest'] as $section) { + if (!isset($original->{$section})) { + continue; } - } - $normalized = (new ComposerJsonNormalizer('file://' . $this->schema))->normalize($original)->decoded(); + $packages = (array) $original->{$section}; + $normalizedPackages = (array) ($normalized->{$section} ?? []); + if ([] !== array_diff_key($packages, $normalizedPackages) || [] !== array_diff_key($normalizedPackages, $packages)) { + $normalized->{$section} = (object) $packages; - foreach ($constraints as $section => $packages) { - foreach ($packages as $package => $constraint) { - $normalized->{$section}->{$package} = $constraint; + continue; + } + + foreach ($packages as $package => $value) { + $normalized->{$section}->{$package} = $value; } } + } - $encoded = json_encode($normalized, $format->jsonEncodeOptions()->toInt() | JSON_THROW_ON_ERROR); - $printed = (new Printer())->print($encoded, $format->indent()->toString(), $format->newLine()->toString()); + /** First-match rule maps must retain their original order, including intentionally broad-first deny policies. */ + private function restorePriorities(object $original, object $normalized): void + { + if (!isset($original->config)) { + return; + } - return $printed . ($format->hasFinalNewLine() ? $format->newLine()->toString() : ''); + foreach (['allow-plugins', 'preferred-install'] as $property) { + if (!property_exists($original->config, $property)) { + continue; + } + + $normalized->config ??= new \stdClass(); + $normalized->config->{$property} = $original->config->{$property}; + } } } diff --git a/tests/Integration/NormalizeComposerTest.php b/tests/Integration/NormalizeComposerTest.php index c721f13..2deabea 100644 --- a/tests/Integration/NormalizeComposerTest.php +++ b/tests/Integration/NormalizeComposerTest.php @@ -5,6 +5,8 @@ namespace FastForward\CodeStandards\Tests\Integration; use Composer\Package\Locker; +use Composer\Package\Link; +use Composer\Package\Loader\ArrayLoader; use PHPUnit\Framework\TestCase; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Process\Process; @@ -96,6 +98,57 @@ public function testReadOnlyCheckAndIdempotentExplicitApplyPreservePolicy(): voi self::assertSame($after, $this->snapshot()); } + /** Broad-first policies retain first-match authority; scalar policies remain literal data too. */ + public function testBroadFirstPluginAndInstallationPoliciesRemainUnchanged(): void + { + foreach ([ + ['allow-plugins' => ['*' => false, 'vendor/*' => true, 'vendor/plugin' => false], 'preferred-install' => ['*' => 'source', 'vendor/*' => 'dist']], + ['allow-plugins' => false, 'preferred-install' => 'dist'], + ] as $config) { + file_put_contents($this->directory . '/composer.json', json_encode(['config' => $config, 'name' => 'example/fixture'])); + $result = $this->cli(['--apply']); + self::assertSame(0, $result->getExitCode(), $result->getOutput() . $result->getErrorOutput()); + $normalized = json_decode(file_get_contents($this->directory . '/composer.json'), true, flags: JSON_THROW_ON_ERROR); + self::assertSame($config, $normalized['config']); + $after = $this->snapshot(); + self::assertSame(0, $this->cli([])->getExitCode()); + self::assertSame(0, $this->cli(['--apply'])->getExitCode()); + self::assertSame($after, $this->snapshot()); + } + } + + /** Canonicalizing extension aliases must never add requirements or reverse Composer's effective collision policy. */ + public function testExtensionAliasesPreserveKeysValuesAndEffectiveConstraints(): void + { + $sections = ['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest']; + foreach ([ + ['ext-Foo' => '^2 || ^1'], + ['ext-Foo' => '^2 || ^1', 'ext-foo' => '^3'], + ['ext-foo' => '^3', 'ext-Foo' => '^2 || ^1'], + ] as $requirements) { + $manifest = ['name' => 'example/fixture']; + foreach ($sections as $section) { + $manifest[$section] = $requirements; + } + file_put_contents($this->directory . '/composer.json', json_encode($manifest)); + $result = $this->cli(['--apply']); + self::assertSame(0, $result->getExitCode(), $result->getOutput() . $result->getErrorOutput()); + $normalized = json_decode(file_get_contents($this->directory . '/composer.json'), true, flags: JSON_THROW_ON_ERROR); + $loader = new ArrayLoader(); + $expected = $loader->parseLinks('example/fixture', '1.0.0', Link::TYPE_REQUIRE, $requirements); + foreach ($sections as $section) { + self::assertSame($requirements, $normalized[$section]); + if ('suggest' === $section) { + continue; + } + + $actual = $loader->parseLinks('example/fixture', '1.0.0', Link::TYPE_REQUIRE, $normalized[$section]); + self::assertSame($expected['ext-foo']->getPrettyConstraint(), $actual['ext-foo']->getPrettyConstraint()); + } + self::assertSame(0, $this->cli([])->getExitCode()); + } + } + /** Lock hashes use Composer's own API; apply reports incompatibility without maintaining the lock. */ public function testApplyReportsChangedHashAndLeavesLockUntouched(): void { From 661a7ea2930fd3df979d860c2f8c01fd35642f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 28 Oct 2026 13:43:02 -0300 Subject: [PATCH 3/6] fix: retain dependency order for package case collisions --- docs/architecture.rst | 2 +- docs/composer-normalization.rst | 7 ++++--- src/Composer/MetadataNormalizer.php | 4 +++- tests/Integration/NormalizeComposerTest.php | 17 ++++++++++------- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/architecture.rst b/docs/architecture.rst index a99b70f..ebee4cc 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -44,7 +44,7 @@ with the installed Composer schema and JSON printer. The adapter snapshots and restores literal dependency constraints before printing: dependency policy and constraint expression ordering are outside metadata formatting authority. The adapter also restores first-match plugin/install policies exactly and -retains original dependency maps whenever upstream would canonicalize their keys. +retains original dependency maps for case collisions or whenever upstream would canonicalize their keys. Native normalization retains priority-sensitive repositories, script lists, autoload paths, installer paths and patch ordering. diff --git a/docs/composer-normalization.rst b/docs/composer-normalization.rst index cb446d9..3ffb3b5 100644 --- a/docs/composer-normalization.rst +++ b/docs/composer-normalization.rst @@ -60,9 +60,10 @@ This is a conservative metadata profile, rather than the plugin's complete CLI. It retains every original dependency constraint string in ``require``, ``require-dev``, ``conflict``, ``provide`` and ``replace``; the upstream CLI also rewrites those strings. Package keys and schema properties are normalized. -If upstream would canonicalize a dependency key, the adapter retains the entire -original section's keys, values and order instead; this preserves extension-name -aliases/collisions and their effective Composer constraints. Suggested package +If upstream would canonicalize a dependency key, or the section contains +case-colliding names, the adapter retains the entire original section's keys, +values and order instead; this preserves package/extension-name aliases and +their effective Composer constraints. Suggested package keys/descriptions receive the same protection. Sniffed indentation, newline style and presence of a final newline are retained. The plugin's ``extra.composer-normalize`` indentation overrides and CLI flags diff --git a/src/Composer/MetadataNormalizer.php b/src/Composer/MetadataNormalizer.php index 447e45b..b82ad1a 100644 --- a/src/Composer/MetadataNormalizer.php +++ b/src/Composer/MetadataNormalizer.php @@ -42,7 +42,9 @@ private function restoreDependencies(object $original, object $normalized): void $packages = (array) $original->{$section}; $normalizedPackages = (array) ($normalized->{$section} ?? []); - if ([] !== array_diff_key($packages, $normalizedPackages) || [] !== array_diff_key($normalizedPackages, $packages)) { + $keysChanged = [] !== array_diff_key($packages, $normalizedPackages) || [] !== array_diff_key($normalizedPackages, $packages); + $caseCollision = count(array_change_key_case($packages, CASE_LOWER)) !== count($packages); + if ($keysChanged || $caseCollision) { $normalized->{$section} = (object) $packages; continue; diff --git a/tests/Integration/NormalizeComposerTest.php b/tests/Integration/NormalizeComposerTest.php index 2deabea..6fea3b9 100644 --- a/tests/Integration/NormalizeComposerTest.php +++ b/tests/Integration/NormalizeComposerTest.php @@ -117,15 +117,18 @@ public function testBroadFirstPluginAndInstallationPoliciesRemainUnchanged(): vo } } - /** Canonicalizing extension aliases must never add requirements or reverse Composer's effective collision policy. */ - public function testExtensionAliasesPreserveKeysValuesAndEffectiveConstraints(): void + /** Sorting/canonicalizing package aliases must never add requirements or reverse Composer's collision policy. */ + public function testPackageAndExtensionAliasesPreserveKeysValuesAndEffectiveConstraints(): void { $sections = ['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest']; foreach ([ - ['ext-Foo' => '^2 || ^1'], - ['ext-Foo' => '^2 || ^1', 'ext-foo' => '^3'], - ['ext-foo' => '^3', 'ext-Foo' => '^2 || ^1'], - ] as $requirements) { + ['target' => 'ext-foo', 'packages' => ['ext-Foo' => '^2 || ^1']], + ['target' => 'ext-foo', 'packages' => ['ext-Foo' => '^2 || ^1', 'ext-foo' => '^3']], + ['target' => 'ext-foo', 'packages' => ['ext-foo' => '^3', 'ext-Foo' => '^2 || ^1']], + ['target' => 'foo/package', 'packages' => ['Foo/package' => '^2 || ^1', 'foo/package' => '^3']], + ['target' => 'foo/package', 'packages' => ['foo/package' => '^3', 'Foo/package' => '^2 || ^1']], + ] as $fixture) { + $requirements = $fixture['packages']; $manifest = ['name' => 'example/fixture']; foreach ($sections as $section) { $manifest[$section] = $requirements; @@ -143,7 +146,7 @@ public function testExtensionAliasesPreserveKeysValuesAndEffectiveConstraints(): } $actual = $loader->parseLinks('example/fixture', '1.0.0', Link::TYPE_REQUIRE, $normalized[$section]); - self::assertSame($expected['ext-foo']->getPrettyConstraint(), $actual['ext-foo']->getPrettyConstraint()); + self::assertSame($expected[$fixture['target']]->getPrettyConstraint(), $actual[$fixture['target']]->getPrettyConstraint()); } self::assertSame(0, $this->cli([])->getExitCode()); } From beba5adee2bfbf1fc4591af8f3ab66b821dfa2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 28 Oct 2026 13:48:58 -0300 Subject: [PATCH 4/6] docs: correct normalization guide heading --- docs/composer-normalization.rst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/composer-normalization.rst b/docs/composer-normalization.rst index 3ffb3b5..04ca445 100644 --- a/docs/composer-normalization.rst +++ b/docs/composer-normalization.rst @@ -89,15 +89,19 @@ autoload file/path lists, installer paths and patch order. Regression fixtures a literal aliases/OR constraints, idempotence and complete file-byte preservation in check mode. Review arbitrary extension metadata in the resulting diff. -Only data APIs are used. The command never constructs a Composer application, -plugin manager or installer, starts Composer, loads consumer autoload/config, -executes scripts or fetches a remote schema. Ordinary installation still loads -the tool's Composer autoloader through its binary proxy, as documented in the +After CLI bootstrap, normalization uses only data APIs. It never constructs a +Composer application, plugin manager or installer, starts Composer, discovers an +additional target-project autoload/config, executes Composer script commands or +fetches a remote schema. In an ordinary consumer Composer installation, the +binary proxy intentionally loads the shared consumer/tool autoloader: registered +autoload files can execute during that existing bootstrap. A separate tool +installation does not import the target project's vendor/autoload.php; the +poison-autoload fixture verifies that separate-installation case. See the `architecture contract `__. This is not a filesystem sandbox and does not support concurrent edits to the same metadata. Dependency cost and alternatives -------------------------------- +-------------------------------- The verified graph adds eight packages for ``ergebnis/json-normalizer`` and its Composer constraint-parser support. This is the official standalone From 7d98ba5407eaa3ea8fac25e9fde54ef40de74819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 28 Oct 2026 14:26:17 -0300 Subject: [PATCH 5/6] fix: use caret ranges for normalization dependencies --- .changelog/composer-metadata-normalization.md | 2 ++ composer.json | 8 ++++---- composer.lock | 2 +- docs/composer-normalization.rst | 9 ++++++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.changelog/composer-metadata-normalization.md b/.changelog/composer-metadata-normalization.md index 520b2f5..c40a716 100644 --- a/.changelog/composer-metadata-normalization.md +++ b/.changelog/composer-metadata-normalization.md @@ -4,3 +4,5 @@ type: minor --- Add opt-in `normalize-composer` metadata checking and explicit `--apply`, using the composer-normalize library while retaining literal dependency constraints and significant ordering. Keep PHP check/fix unchanged; never execute Composer scripts/plugins or update locks, and report stale hashes before/after explicit manifest writes. + +Declare the added runtime libraries with compatible caret constraints while retaining the verified locked package versions. diff --git a/composer.json b/composer.json index 7d6ae27..a74ed21 100644 --- a/composer.json +++ b/composer.json @@ -39,11 +39,11 @@ "require": { "php": "^8.3", "composer-runtime-api": "^2.2", - "composer/composer": "~2.10.3", + "composer/composer": "^2.10.3", "container-interop/service-provider": "^0.4.1", - "ergebnis/json": "~1.7.0", - "ergebnis/json-normalizer": "~4.11.0", - "ergebnis/json-printer": "~3.9.1", + "ergebnis/json": "^1.7.0", + "ergebnis/json-normalizer": "^4.11.0", + "ergebnis/json-printer": "^3.9.1", "fast-forward/container": "~1.10.1", "phpstan/phpstan": "~2.1.51", "povils/phpmnd": "^3.6.1", diff --git a/composer.lock b/composer.lock index f15cd5d..1d82817 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": "f7c485cdfe131ac676da76d9646ec71c", + "content-hash": "3c41c08520b06ce626a7bbdc66bf7d07", "packages": [ { "name": "brick/varexporter", diff --git a/docs/composer-normalization.rst b/docs/composer-normalization.rst index 04ca445..888eb20 100644 --- a/docs/composer-normalization.rst +++ b/docs/composer-normalization.rst @@ -134,9 +134,12 @@ These files load with the tool autoloader even when normalization is not chosen; this integration does not claim namespace or runtime isolation. Composer must resolve compatibility with a normal consumer installation. -The direct constraints select tested patch lines (``~4.11.0`` normalizer, -``~2.10.3`` Composer, ``~1.7.0`` JSON and ``~3.9.1`` printer). The lock records the -exact verified versions; consumers resolve their own graph within those lines. +The direct constraints use caret ranges (``^4.11.0`` normalizer, +``^2.10.3`` Composer, ``^1.7.0`` JSON and ``^3.9.1`` printer), allowing compatible +minor/patch updates up to the next major. The lock records the exact verified +versions; consumers resolve their own graph within those ranges. The minimum +versions and current locked graph are tested; this does not claim execution of +unreleased versions admitted by those ranges. Schema content/order therefore belongs to the installed Composer package. There is no runtime schema updater, remote-schema fallback, download or global installation. Dependency updates require reviewing the schema, autoload files, From 6f44bea29455a4ba805ed383072f4dd122721bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Say=C3=A3o=20Lobato=20Abreu?= Date: Wed, 28 Oct 2026 15:03:51 -0300 Subject: [PATCH 6/6] fix: redact invalid Composer metadata diagnostics --- .changelog/composer-metadata-normalization.md | 2 ++ docs/composer-normalization.rst | 2 ++ .../Command/NormalizeComposerCommand.php | 5 +++++ tests/Integration/NormalizeComposerTest.php | 22 +++++++++++++++++++ 4 files changed, 31 insertions(+) diff --git a/.changelog/composer-metadata-normalization.md b/.changelog/composer-metadata-normalization.md index c40a716..8bab176 100644 --- a/.changelog/composer-metadata-normalization.md +++ b/.changelog/composer-metadata-normalization.md @@ -6,3 +6,5 @@ type: minor Add opt-in `normalize-composer` metadata checking and explicit `--apply`, using the composer-normalize library while retaining literal dependency constraints and significant ordering. Keep PHP check/fix unchanged; never execute Composer scripts/plugins or update locks, and report stale hashes before/after explicit manifest writes. Declare the added runtime libraries with compatible caret constraints while retaining the verified locked package versions. + +Keep rejected metadata contents out of invalid-JSON diagnostics, including verbose output. diff --git a/docs/composer-normalization.rst b/docs/composer-normalization.rst index 888eb20..ffcc428 100644 --- a/docs/composer-normalization.rst +++ b/docs/composer-normalization.rst @@ -27,6 +27,8 @@ global options appear in installed help. There are no file-selection, ECS configuration, lock-update, dependency-update or plugin-activation options. The operation uses the root ``composer.json`` and optional root ``composer.lock``; missing/unreadable manifest files and symlink metadata files are rejected. +Invalid JSON diagnostics omit the rejected bytes, including in verbose output, +so malformed metadata cannot disclose embedded URLs or tokens through errors. Apply writes only ``composer.json``. Inspect its diff even on failure: a failed write can be partial, and a successful write can make the existing lock stale. diff --git a/src/Console/Command/NormalizeComposerCommand.php b/src/Console/Command/NormalizeComposerCommand.php index ee6acbb..5d60941 100644 --- a/src/Console/Command/NormalizeComposerCommand.php +++ b/src/Console/Command/NormalizeComposerCommand.php @@ -4,6 +4,7 @@ namespace FastForward\CodeStandards\Console\Command; +use Ergebnis\Json\Exception\NotJson; use FastForward\CodeStandards\Composer\NormalizationRunner; use FastForward\CodeStandards\Project\ProjectDirectoryResolver; use InvalidArgumentException; @@ -37,6 +38,10 @@ public function __invoke( $root = $this->directories->resolve(true, null, null, $directory); return $this->normalization->run($root, $apply); + } catch (NotJson) { + $io->getErrorStyle()->error('Composer metadata is not valid JSON.'); + + return Command::INVALID; } catch (InvalidArgumentException|RuntimeException $error) { $io->getErrorStyle()->error($error->getMessage()); diff --git a/tests/Integration/NormalizeComposerTest.php b/tests/Integration/NormalizeComposerTest.php index 6fea3b9..73478d2 100644 --- a/tests/Integration/NormalizeComposerTest.php +++ b/tests/Integration/NormalizeComposerTest.php @@ -230,6 +230,28 @@ public function testInvalidInputsAndSymlinksFailWithoutWriting(): void self::assertSame('{"name":"example/fixture"}', file_get_contents($this->directory . '/other.json')); } + /** Rejected documents may contain private URLs/tokens; neither default nor verbose diagnostics may disclose them. */ + public function testMalformedMetadataDiagnosticsNeverExposeRejectedBytes(): void + { + $marker = 'fixture-sensitive-marker'; + $invalid = '{"synthetic":"' . $marker . '"'; + foreach (['composer.json', 'composer.lock'] as $file) { + file_put_contents($this->directory . '/composer.json', '{"name":"example/fixture"}'); + file_put_contents($this->directory . '/' . $file, $invalid); + $before = $this->snapshot(); + foreach ([[], ['--apply'], ['--apply', '-vvv']] as $options) { + $result = $this->cli($options); + $diagnostics = $result->getOutput() . $result->getErrorOutput(); + self::assertSame(2, $result->getExitCode()); + self::assertStringNotContainsString($marker, $diagnostics); + self::assertStringNotContainsString($invalid, $diagnostics); + self::assertStringContainsString('Composer metadata is not valid JSON.', $diagnostics); + self::assertSame('', $result->getOutput()); + self::assertSame($before, $this->snapshot()); + } + } + } + /** @param list $options Runs without HTTP file streams/cURL execution to detect unexpected schema downloads. */ private function cli(array $options): Process {