Read composer.lock directly and emit a dependency graph as an ASCII tree, GraphViz DOT, or Mermaid.
acme/app-core 1.2.0
├── acme/http 1.0.5
│ └── psr/log 1.1.4
└── psr/log 1.1.4
- Zero Composer runtime dependency — we parse the lockfile JSON directly.
- PHP 8.2 stdlib only (no framework, no SDK).
- Three output formats — tree for humans, DOT for SVGs, Mermaid for GitHub PRs.
- Cycle-safe — handles
replace/provideedges that can form cycles. - CI-ready — 0/1/2 exit codes, no terminal assumed.
- Dockerized — one-line run on any project.
docker build -t composer-graph .
docker run --rm -v "$PWD:/project" composer-graph /projectgit clone https://github.com/sen-ltd/composer-graph.git
cd composer-graph
./bin/composer-graph /path/to/your/projectPHP 8.2+ is required. No Composer install needed on the target — bin/composer-graph ships with a tiny manual autoloader so it runs on broken checkouts.
composer-graph [path] [options]
Options:
--format FORMAT tree (default) | dot | mermaid
--depth N limit tree/graph depth
--dev include require-dev
--no-dev exclude require-dev (default)
--only PACKAGE show only the subtree rooted at PACKAGE
--reverse PACKAGE reverse lookup: what depends on PACKAGE
--platform include platform reqs (php, ext-*, lib-*) as nodes
--stats print total / dev / direct / transitive / depth
-h, --help print help
-V, --version print version
Tree view — the default:
composer-graph .Mermaid for a PR comment — paste it directly, GitHub renders it:
composer-graph . --format mermaid > deps.mmd```mermaid
graph LR
n0["acme/app-core<br/>1.2.0"]
n1["acme/http<br/>1.0.5"]
n2["psr/log<br/>1.1.4"]
n0 --> n1
n0 --> n2
n1 --> n2
```
Render to SVG via GraphViz:
composer-graph . --format dot | dot -Tsvg > deps.svgAudit transitive deps of a single package:
composer-graph . --only symfony/consoleReverse lookup — who depends on psr/log?
composer-graph . --reverse psr/logStats — one-line overview:
composer-graph . --dev --statscomposer.lock is a documented JSON format. Reading it directly means:
- No Composer install on target. Works on half-broken checkouts with no
vendor/dir. - No plugin coupling.
composer show --treeis great interactively but hard to pipe. - Deterministic. The lockfile is the resolved source of truth, not the semver ranges in
composer.json. - Small. One JSON decode, one graph walk, three formatters. ~1000 LoC total.
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | composer.lock missing or unreadable |
| 2 | bad args / package not found |
docker run --rm --entrypoint /app/vendor/bin/phpunit composer-graph --no-coverage -c /app/phpunit.xml23 PHPUnit cases covering the parser, the graph (including cycle + replace resolution), all three formatters, the CLI arg parser, and exit-code paths.
MIT © 2026 SEN LLC