onurb/doctrine-metadata-grapher
Generates a visual graph of Doctrine ORM metadata, turning entity associations, inheritance, and mapped fields into a readable diagram so you can understand and document complex domain models.
This is a dev-only Doctrine ORM tool, not an Eloquent tool. It walks a Doctrine ClassMetadataFactory and emits a Graphviz representation of your entity graph. There is no Laravel service provider — you wire it manually, usually behind an Artisan command you keep out of production.
Minimal path:
composer require --dev onurb/doctrine-metadata-grapher
# Graphviz itself is an OS dependency, not a Composer one
sudo apt-get install graphviz # or: brew install graphviz
Then, assuming you already have Doctrine wired into Laravel (typically via laravel-doctrine/orm), pull the factory off the entity manager and hand it to the grapher:
use Onurb\Doctrine\MetadataGrapher\MetadataGrapher;
$grapher = new MetadataGrapher();
$grapher->addClassMetadataFactory(
app('em')->getMetadataFactory()
);
// Graphviz DOT text — feed to `dot` yourself, or let the
// package write the file depending on the version's API
$dot = $grapher->getGraph();
Where to look first: src/MetadataGrapher.php in the repo — the public surface is one class, and the exact method names are your only unknown. The README is thinner than the API, so read the source before trusting a snippet from any blog post (including this one).
First useful use case: before a schema refactor, dump the graph and eyeball it. Doctrine's mapping annotations/attributes are scattered across dozens of entity files; a single rendered SVG shows you every orphaned association, every bidirectional pair missing an inversedBy, and every accidental cascade={"remove"} chain in one glance. That beats grepping #[ManyToOne] forty times.
Wrap it in a guarded Artisan command. This is the single pattern worth committing.
class DoctrineGraphCommand extends Command
{
protected $signature = 'doctrine:graph
{--format=svg : png|svg|dot}
{--output=storage/app/doctrine-graph}';
public function handle()
{
abort_unless(app()->environment(['local', 'development']), 403);
$grapher = new MetadataGrapher();
$grapher->addClassMetadataFactory(app('em')->getMetadataFactory());
$dot = $grapher->getGraph();
file_put_contents($path = $this->option('output') . '.dot', $dot);
exec("dot -T{$this->option('format')} {$path} -o {$path}.{$this->option('format')}");
$this->info("Wrote {$path}.{$this->option('format')}");
}
}
Shelling out to dot yourself is usually more reliable than trusting whatever the package does internally, and it removes any ambiguity about output paths.
Snapshot the DOT output in tests. Because the package produces deterministic text, you can assert on it. Generate the DOT in a test and compare against a checked-in fixture. Any PR that silently adds a ManyToOne or drops an association fails CI with a readable diff. This is the highest-value use of a low-maintenance package — you depend on the output string, not on internal behaviour, so a stale upstream is less of a liability.
Slice the graph by namespace or module. A full graph of a 60-entity domain is a hairball. Either filter $metadataFactory->getAllMetadata() before handing it over, or post-process the DOT lines by entity label. Doing the filtering yourself keeps you independent of whether the package ever adds a filter option.
Pair it with migration review. Run doctrine:graph before and after generating a migration set, then diff the two DOT files. It catches relationship-level mistakes (orphan removal, cascade changes) that doctrine:migrations:diff renders as opaque SQL.
Treat it as documentation, not architecture. It reads Doctrine mappings. If half your app is Eloquent — which is the normal case in Laravel — the graph is at best a partial picture. Scope it to the Doctrine portion explicitly in the command's help text so nobody mistakes it for a full domain map.
Doctrine is not a Laravel default. If you're an Eloquent shop, this package is dead weight — it will find zero entities and produce an empty graph. Confirm app('em') resolves and getMetadataFactory()->getAllMetadata() is non-empty before debugging the grapher itself.
Graphviz is a system binary, not a dependency. composer install succeeds and the command then fails at runtime on machines without dot. Add it to your Dockerfile and your README's prerequisites section; a which dot guard clause at the top of the command saves a support ticket.
Adoption is effectively zero (8 stars, 0 dependents, last meaningful activity is unclear). Pin the version in composer.json ("onurb/doctrine-metadata-grapher": "1.0.*" or an exact tag), and be prepared to fork. The codebase is one class, so forking is genuinely cheap — keep that option in mind rather than filing upstream issues and waiting.
Attribute vs. annotation era. Doctrine 2.x annotations and Doctrine 3.x PHP attributes both produce ClassMetadata, so mapping style shouldn't matter — but embedded objects, inheritance hierarchies (SINGLE_TABLE, JOINED), and association overrides often render poorly or not at all in graphers of this vintage. Spot-check a known-complex entity before believing the output.
Self-referencing and circular associations. A Category.parent → Category relation or an A↔B bidirectional pair can produce visually confusing edges. If the graph is unreadable, that's usually a modelling smell surfacing honestly, not a bug in the tool.
Escape labels if you post-process. Entity FQCNs contain backslashes; if you're rewriting DOT labels for readability, escape them (\\) or Graphviz silently mangles the output.
Extension points are thin. There's no event system or formatter registry — the realistic extension route is subclassing MetadataGrapher and overriding the graph-building method, or wrapping it entirely and generating DOT yourself from getAllMetadata(). If you find yourself fighting it for layout control, writing ~80 lines of DOT emission against Doctrine metadata is often faster than bending this package.
How can I help you explore Laravel packages today?