Browse documentationOpen

byrcsc/laravel-cartographer · 1.x

How a diagram is built.

The stages between the Artisan command and the written file, what each one contributes, and why the output is byte-identical between runs.

cartographer:erd runs six stages in order. Each one has a class behind it, and each one either produces the input for the next or fails the command.

StageClassProduces
Find the modelsModelDiscoveryA sorted list of model class names
Read the schemaSchemaReaderTables, columns, indexes, foreign keys
Resolve the relationsRelationshipDetectorRelation metadata per model
Assemble the graphGraphAssemblerEntities and edges, scoped
Render itMermaidRendererThe diagram text
Write itAtomicFileWriterThe file on disk

Each class is usable on its own. See PHP API if you want to drive the pipeline yourself.

1. Find the models

Every configured path is expanded as a glob and scanned recursively for .php files. Class declarations are read out of the file's tokens, then each candidate is checked: it has to exist, subclass Illuminate\Database\Eloquent\Model, and not be abstract.

Nothing here touches the database. Details in model discovery.

2. Read the schema

Laravel's schema builder is asked for tables, columns, indexes, and foreign keys on the target connection. Column types are normalized so that int8, bigint, and INTEGER from three different drivers all arrive as bigint.

Failures at this stage — a wrong connection name, bad credentials, a database that is not running — end the command with a message naming the connection. Details in schema introspection.

3. Resolve the relations

For each discovered model that has a table, Cartographer instantiates the model and invokes its public zero-argument methods. Anything that returns a Relation is kept, and its keys are read from the relation object.

This is the only stage that runs your code. It is also the reason relation metadata is exact rather than inferred: belongsTo(User::class, 'author_id') reports author_id because the relation object says so. Details in relationship detection.

4. Assemble the graph

The graph is built in four steps:

  1. Entities. Each model is instantiated to read its table name, and matched against the schema. A model whose table does not exist is skipped with a warning.
  2. Edges. Each relation becomes an edge from the model's table to the related model's table. Relations pointing at models that were not discovered are dropped, as are excluded relation types.
  3. Selection. With no seeds, every model table is selected. With seeds, a breadth-first walk follows edges outward until the hop limit is reached.
  4. Pivot tables. Any pivot table named by a selected many-to-many edge is added as an entity, if that table exists in the schema.

Two consequences follow from step 2. A relation to a model outside your configured paths produces no edge, because the target has no entity. And a morphTo produces an edge only where another discovered model declares the matching morphOne or morphMany back to it — the inverse side is what names the possible targets.

Details in scoping a diagram.

5. Render and write

The renderer emits edges first, a blank line, then entity blocks. Table and column names that are not valid Mermaid identifiers are rewritten, and collisions are resolved with a numeric suffix. Details in diagram syntax.

The file is then written through a temporary file in the destination directory and moved into place with rename(). A run that dies partway through leaves the previous diagram intact rather than a half-written one. --stdout skips this stage entirely.

Why the output is stable

Every collection is sorted before it reaches the renderer:

  • Models are sorted by class name; discovered files are sorted by real path, so two paths matching the same directory produce one entry.
  • Tables, columns, indexes, and foreign keys are sorted by name.
  • Relations are sorted by method name.
  • Entities are sorted by table name, and edges by source, target, type, and name.

Filesystem order, hash order, and the order the database happens to return rows in are all removed. Regenerating without changing the application produces a byte-identical file, which is what makes the diagram worth committing and worth checking in CI.

What never happens

  • No rows are read. The package issues schema queries only. The test suite asserts that no query in a full run selects from a fixture table.
  • No relation query runs. Building a relation object does not execute it, and Cartographer never calls get() or first() on one.
  • Nothing is written to the database.
  • No file outside the output path is touched, apart from the temporary file next to it during the atomic write.