Skip to content

Instantly share code, notes, and snippets.

@edudobay
Created July 4, 2025 15:04
Show Gist options
  • Save edudobay/63e4acda24076184f7ff7e5ac248aaa0 to your computer and use it in GitHub Desktop.
Save edudobay/63e4acda24076184f7ff7e5ac248aaa0 to your computer and use it in GitHub Desktop.
Structural comparison for composer.lock files

Composer lock file diff

Structurally compare two versions of the composer.lock file and output a JSON document with the found differences.

Example usage

Compare the current version against the version two commits behind (@~2):

$ composer_lock_diff.php <(git show @~2:composer.lock) <(cat composer.lock)

# Note - this uses shell process substitution `<(command)`

Example output:

[
  {
    "result": "changed",
    "name": "doctrine/orm",
    "section": [ "", "" ],
    "version": [ "3.3.2", "3.5.0" ],
    "sourceReference": [ "c9557c588b3a70ed93caff069d0aa75737f25609", "6deec3655ba3e8f15280aac11e264225854d2369" ]
  },
  {
    "result": "changed",
    "name": "nikic/php-parser",
    "section": [ "dev", "dev" ],
    "version": [ "v5.4.0", "v5.5.0" ],
    "sourceReference": [ "447a020a1f875a434d62f2a401f53b82a396e494", "ae59794362fe85e051a58ad36b289443f57be7a9" ]
  }
]
#!/usr/bin/env php
<?php
/**
* @return array{name: string, section: string, version: string, sourceReference: null|string}
*/
function get_package_data(array $package, string $section): array {
$version = $package['version'];
$sourceRef = $package['source']['reference'] ?? null;
return [
'name' => $package['name'],
'section' => $section,
'version' => $version,
'sourceReference' => $sourceRef,
];
}
/**
* @return array<string, array{section: string, version: string, sourceReference: string}>
*/
function get_versions(array $doc): array {
$packages = [];
foreach ($doc['packages'] ?? [] as $package) {
$packageData = get_package_data($package, section: '');
$packages[$packageData['name']] = $packageData;
}
foreach ($doc['packages-dev'] ?? [] as $package) {
$packageData = get_package_data($package, section: 'dev');
$packages[$packageData['name']] = $packageData;
}
return $packages;
}
function compare(array $packages1, array $packages2): array {
$packages = array_fill_keys(array_keys(array_merge($packages1, $packages2)), []);
foreach ($packages as $name => &$data) {
if (! isset($packages2[$name])) {
$data = ['result' => 'removed', ...$packages1[$name]];
continue;
}
if (! isset($packages1[$name])) {
$data = ['result' => 'added', ...$packages2[$name]];
continue;
}
if (($p1 = $packages1[$name]) === ($p2 = $packages2[$name])) {
$data = ['result' => 'unchanged'];
continue;
}
$data = [
'result' => 'changed',
'name' => $name,
'section' => [$p1['section'], $p2['section']],
'version' => [$p1['version'], $p2['version']],
'sourceReference' => [$p1['sourceReference'], $p2['sourceReference']],
];
}
unset($data);
ksort($packages);
return $packages;
}
$file1 = str_replace('/proc/self/fd/', 'php://fd/', $argv[1]);
$file2 = str_replace('/proc/self/fd/', 'php://fd/', $argv[2]);
$file1 = file_get_contents($file1);
$file2 = file_get_contents($file2);
$doc1 = json_decode($file1, true, flags: JSON_THROW_ON_ERROR);
$doc2 = json_decode($file2, true, flags: JSON_THROW_ON_ERROR);
$v1 = get_versions($doc1);
$v2 = get_versions($doc2);
$diff = compare($v1, $v2);
$output = array_filter($diff, fn (array $r) => $r['result'] !== 'unchanged');
echo json_encode($output, flags: JSON_PRETTY_PRINT), "\n";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment