adachsoft/composer-json-fixer
A small PHP library and CLI tool for validating and automatically fixing composer.json files, with a focus on library packages.
The tool runs a set of opinionated rules against your composer.json, reports issues, and can generate patches or write fixes back to disk while preserving indentation style.
Requirements
- PHP >= 8.2
composer.json size up to 5 MB (hard limit enforced by file reader)
Runtime dependencies are installed automatically via Composer (notably adachsoft/console-io and adachsoft/collection).
Installation
Install via Composer:
composer require --dev adachsoft/composer-json-fixer
The package is intended primarily as a dev-tool, hence --dev in the example. You can also install it as a regular dependency if you need to ship it with your application.
After installation, Composer exposes the binary as:
./vendor/bin/composer-json-fixer
CLI Usage
Basic commands
Validate a composer.json file:
./vendor/bin/composer-json-fixer validate --path=path/to/composer.json
Fix a composer.json file and show a patch (dry-run):
./vendor/bin/composer-json-fixer fix --path=path/to/composer.json --dry-run
Fix a composer.json file in place:
./vendor/bin/composer-json-fixer fix --path=path/to/composer.json
You can optionally provide a JSON configuration file with rule settings:
./vendor/bin/composer-json-fixer validate --path=path/to/composer.json --config=path/to/config.json
./vendor/bin/composer-json-fixer fix --path=path/to/composer.json --config=path/to/config.json
Exit codes
0 – success, no issues (or all issues fixed and file is valid)
1 – validation failed, at least one issue (error or warning) remains before/after fix, or invalid/broken file
Note: the CLI does not distinguish severity for exit codes – any reported issue (even a warning) results in a non-zero exit code.
CLI output
The CLI uses adachsoft/console-io for colored and icon-enhanced output. Example flows:
validate with no issues:
- prints a success message and exits with code
0
validate with issues:
- prints all issues (severity, message, JSON pointer) and exits with code
1
fix --dry-run:
- prints a unified diff with proposed changes but does not modify the file
fix (write mode):
- prints a diff if there are changes
- writes the updated
composer.json
- validates the result and prints final status
Indentation is preserved (2 spaces, 4 spaces or tabs) based on the original file.
Library Usage (Public API)
The main entry point is AdachSoft\ComposerJsonFixer\PublicApi\ComposerJsonFixerInterface.
In most cases you should use the ComposerJsonFixerBuilder from the Public API, which wires Core dependencies and rules for you.
Construction via builder
<?php
use AdachSoft\ComposerJsonFixer\PublicApi\ComposerJsonFixerBuilder;
use AdachSoft\ComposerJsonFixer\PublicApi\ComposerJsonFixerInterface;
$builder = new ComposerJsonFixerBuilder();
$fixer = $builder->build();
$fixerFromConfig = $builder->buildFromConfigFile(__DIR__ . '/composer-json-fixer.config.json');
assert($fixer instanceof ComposerJsonFixerInterface);
You normally do not need to instantiate Core classes (ComposerJsonFileReader, ComposerJsonFileWriter, DefaultRuleProvider, UnifiedDiffGenerator) by hand – this is handled inside the builder.
Configuration file (rules)
The builder supports a JSON configuration file passed on the CLI (--config) or directly to buildFromConfigFile().
The configuration controls:
- which rules are enabled/disabled,
- rule-specific parameters (e.g. packages for
RequireVersionNormalizationRule or optional fields for MissingRequiredFieldsRule).
Example configuration file (snake_case JSON):
{
"rules": [
{
"rule_id": "require_version_normalization",
"enabled": true,
"params": {
"packages": [
"phpunit/phpunit",
"phpstan/phpstan"
]
}
},
{
"rule_id": "missing_required_fields",
"enabled": true,
"params": {
"optional": ["license", "authors"]
}
}
]
}
Validation API
use AdachSoft\ComposerJsonFixer\PublicApi\Dto\ValidateComposerJsonRequestDto;
$request = new ValidateComposerJsonRequestDto('/path/to/composer.json');
$response = $fixer->validate($request);
if ($response->isValid) {
} else {
foreach ($response->issues as $issue) {
}
}
Fix API
use AdachSoft\ComposerJsonFixer\PublicApi\Dto\FixComposerJsonRequestDto;
use AdachSoft\ComposerJsonFixer\PublicApi\Enum\FixModeEnum;
$request = new FixComposerJsonRequestDto(
'/path/to/composer.json',
FixModeEnum::DryRun,
);
$response = $fixer->fix($request);
if ($response->unifiedDiff !== null) {
echo $response->unifiedDiff;
}
if ($response->wasWritten) {
}
if (!$response->isValidAfterFix) {
}
Exceptions
The following public exceptions can be thrown by the reader/fixer facade:
AdachSoft\ComposerJsonFixer\PublicApi\Exception\ComposerJsonNotFoundException – file does not exist
AdachSoft\ComposerJsonFixer\PublicApi\Exception\ComposerJsonInvalidException – file is too large, not readable, or contains invalid JSON
Rules
The library ships with a set of built-in rules, all implementing:
AdachSoft\ComposerJsonFixer\Core\Rule\ComposerJsonRuleInterface
Each rule provides:
getRuleId(): string
validate(ComposerJsonDocument $document): ComposerJsonIssueCollection
fix(array $data): ?array – returns modified decoded JSON or null when no change is applied
MissingRequiredFieldsRule
Checks that the following fields are present at the root level:
name
description
type
license
authors
keywords
Severity: error for each missing field (unless marked as optional via configuration).
Configuration support (new in Unreleased):
The rule accepts an optional parameter (JSON array of field names). When license or authors are listed:
validate() skips reporting them as missing or empty.
fix() automatically adds default values ("MIT" for license, [] for authors).
Example in config file:
{
"rule_id": "missing_required_fields",
"enabled": true,
"params": {
"optional": ["license", "authors"]
}
}
Psr4NamespaceConsistencyRule
For packages with:
type: "library"
- valid non-empty
name (vendor/package-name)
The rule computes an expected PSR-4 namespace from the package name using ExpectedPsr4Namespace, e.g.:
vendor/package-name → Vendor\PackageName\\
Behavior:
validate() compares the first autoload.psr-4 key with the expected namespace and reports a warning if they differ.
- Internally, the rule compares the normalized namespace (multiple backslashes collapsed) so that
DoubleBackslashInNamespaceRule and this rule work together.
fix() rewrites only the first autoload.psr-4 key to the expected namespace, preserving:
- the original path for that entry
- all other
psr-4 entries
- all other
autoload sections (classmap, files, etc.).
DoubleBackslashInNamespaceRule
Normalizes namespace keys in autoload.psr-4 by collapsing multiple backslashes:
AdachSoft\\\\Foo\\ → AdachSoft\Foo\\
Reports warnings for affected entries and updates keys during fix().
ConfigMustBeObjectRule
Ensures that the root config section is a JSON object, not a JSON array.
Examples:
"config": [] (JSON array) → issue + fix to "config": {} (empty object, represented as stdClass in PHP)
"config": {} (already an object) → no issue
The rule uses both the decoded data and the raw JSON to distinguish between [] and {} at the root level.
Severity: warning.
RequireVersionNormalizationRule
Normalizes overly specific versions for selected packages in require, e.g.:
"phpunit/phpunit": "9.6.5" → "9.6"
"adachsoft/command-executor-lib": "2.0.1" → "2.0"
Behavior:
validate() emits warnings for versions matching x.y.z (full major.minor.patch) for a configured list of packages.
fix() rewrites them to x.y (i.e. drops the patch segment) for configured packages, such as:
phpunit/phpunit
phpstan/phpstan
rector/rector
adachsoft/command-executor-lib
After a successful fix, a subsequent validate should no longer report normalization warnings for these packages.
Indentation & Formatting
The tool tries hard not to destroy your formatting:
- The reader inspects the original file and detects the indentation style (2 spaces, 4 spaces, or tab).
- The fixer uses
json_encode(..., JSON_PRETTY_PRINT) and then rewrites the leading spaces on each line to match the detected indent.
- If the file is fully minified (single line), the fixer will pretty-print it using a default indent (4 spaces).
Note: key ordering may still change according to json_encode behavior; the primary focus is on logical structure and indent style, not on preserving exact byte-for-byte formatting.
Development
Running tests
composer install
./vendor/bin/phpunit tests
Static analysis
./vendor/bin/phpstan analyse src tests
Code style
This project follows coding standards provided by adachsoft/php-code-style.
./vendor/bin/php-cs-fixer fix
Limitations
- Only JSON files up to 5 MB are supported.
- Automatic fixes are intentionally conservative – some rules only validate and do not attempt to guess missing metadata.
- The ruleset is opinionated and biased towards typical library packages; custom requirements may need additional rules.
Contributions in the form of new rules or improvements to existing ones are welcome, as long as they keep the behavior predictable and safe for CI usage.