›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-cartographer
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • How a diagram is built
  • Model discovery
  • Relationship detection
  • Schema introspection
  • Scoping a diagram

Operations

  • Diagrams per subsystem
  • Exporting images
  • Themes and fonts
  • Renderer limits
  • Keeping the diagram current
  • Continuous integration

Reference

  • Configuration
  • Console commands
  • Diagram syntax
  • PHP API
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • How a diagram is built
  • Model discovery
  • Relationship detection
  • Schema introspection
  • Scoping a diagram

Operations

  • Diagrams per subsystem
  • Exporting images
  • Themes and fonts
  • Renderer limits
  • Keeping the diagram current
  • Continuous integration

Reference

  • Configuration
  • Console commands
  • Diagram syntax
  • PHP API
  • Testing
  • Troubleshooting

byrcsc/laravel-cartographer · 1.x

Continuous integration.

Fail CI when a committed diagram no longer matches the application.

The check is two commands: migrate a throwaway database, then ask Cartographer whether the committed diagrams still match.

php artisan migrate --force
php artisan cartographer:check
   INFO  3 diagram(s) up to date.

cartographer:check regenerates everything your config declares, the full diagram and every group, compares each against the file on disk, and exits 1 listing what drifted:

   ERROR  Stale: [/srv/app/docs/erd.md].
--- committed
+++ generated
@@ -14,1 +14,2 @@
-        bigint author_id FK
+        bigint author_id FK
+        bigint editor_id FK

   ERROR  Missing: [/srv/app/docs/erd/catalog.md].
   ERROR  2 of 3 diagram(s) out of date. Regenerate with: php artisan cartographer:erd

Every declared output is reported, so one run tells you everything that needs regenerating rather than only the first thing it hit.

It writes nothing

The check never touches the filesystem, so it is safe on a read-only checkout and cannot leave a job's working tree dirty. It also never runs mermaid-cli, even for a diagram exported as an image, so a job that verifies an SVG needs no node and no browser. See exporting images.

Why not git diff

The older recipe was cartographer:erd followed by git diff --exit-code, and it still works. cartographer:check is better in four ways:

  • It reports every stale file in one run, with a diff per file, instead of whatever the path argument happened to cover.
  • It knows what your config declares, so a group added to config and never generated is caught as missing. git diff cannot see a file that does not exist.
  • It writes nothing, so the job needs no writable checkout and no cleanup step.
  • It compares an exported image through the Mermaid source beside it, which git diff would report as stale on every mermaid-cli upgrade.

Because it runs the same pipeline as cartographer:erd, the two cannot disagree. A schema change, a model change, and a config change all show up the same way.

Why CI needs a database

Cartographer reads structure from a live connection, so the job needs one, and it needs the migrations run against it. SQLite is enough when your migrations run on SQLite; use the same engine as production when they do not.

Type normalization is what makes this safe across engines. The same application produces the same bigint, varchar, and timestamp on MySQL, PostgreSQL, and SQLite, so a developer generating against SQLite and a build generating against MySQL still produce the same file. See schema introspection.

GitHub Actions with SQLite

The smallest version, with no service container:

name: ERD

on: pull_request

jobs:
  erd:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: shivammathur/setup-php@v2
        with:
          php-version: "8.3"

      - run: composer install --prefer-dist --no-interaction

      - run: touch database/database.sqlite

      - run: php artisan migrate --force
        env:
          DB_CONNECTION: sqlite
          DB_DATABASE: database/database.sqlite

      - name: Check the ERD is current
        run: php artisan cartographer:check
        env:
          DB_CONNECTION: sqlite
          DB_DATABASE: database/database.sqlite

Install dev dependencies. Cartographer is one, and --no-dev leaves the commands unregistered.

GitHub Actions with MySQL

When your migrations use engine-specific SQL, run the check against the real engine:

jobs:
  erd:
    runs-on: ubuntu-latest

    services:
      mysql:
        image: mysql:8.4
        env:
          MYSQL_DATABASE: app
          MYSQL_ROOT_PASSWORD: password
        ports:
          - 3306:3306
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=5

    env:
      DB_CONNECTION: mysql
      DB_HOST: 127.0.0.1
      DB_PORT: 3306
      DB_DATABASE: app
      DB_USERNAME: root
      DB_PASSWORD: password

    steps:
      - uses: actions/checkout@v4

      - uses: shivammathur/setup-php@v2
        with:
          php-version: "8.3"

      - run: composer install --prefer-dist --no-interaction
      - run: php artisan migrate --force
      - run: php artisan cartographer:check

Splitting the check across jobs

--group narrows the check to one configured group:

strategy:
  matrix:
    group: [billing, catalog]

steps:
  - run: php artisan cartographer:check --group=${{ matrix.group }}

Use this to parallelize, never as the only check. A matrix over the groups never looks at the full diagram, and it cannot notice a group you deleted from config but left on disk. Keep one bare cartographer:check in the pipeline; that is what proves every committed file is current.

Reporting the drift instead of failing

On a repository where a stale diagram should be visible but not blocking, capture the output and keep the step green:

- name: Report a stale ERD
  run: |
    if ! php artisan cartographer:check > check.txt 2>&1; then
      {
        echo '### The committed ERD is out of date'
        echo
        echo '```'
        cat check.txt
        echo '```'
      } >> "$GITHUB_STEP_SUMMARY"
    fi

Exit codes

cartographer:check returns 0 when every declared file matches, and 1 when any is stale or missing. It also returns 1 on the failures that stop it from producing a comparison at all: an unreachable connection, an invalid option or config value, an unknown group, and no discovered models.

It does not fail on a skipped model. A table missing on the CI database produces a warning and a smaller diagram, which then shows up as drift:

   WARN  Skipping model [App\Models\Ghost]: table [missing_ghosts] does not exist.

If a build fails on a diff full of removed entities, the migrations did not all run.

A size warning never fails the check. A diagram too large for GitHub to draw is still a correct diagram, and the file on disk still matches.

Regenerating in CI

Do not. cartographer:check is the CI half; cartographer:erd belongs on a developer machine or in a pre-commit hook, where the diff lands in the pull request that caused it. See keeping the diagram current.

A job that regenerates and commits will eventually push a diagram generated against a database that is not quite yours, and it removes the review step that makes the committed file worth having.

What to read next

  • Console commands for every option and exit code.
  • Keeping the diagram current for the local half of the same workflow.
  • Troubleshooting for what a specific failure means.
PreviousKeeping the diagram currentNextConfiguration
View source

On this page

  1. It writes nothing
  2. Why not git diff
  3. Why CI needs a database
  4. GitHub Actions with SQLite
  5. GitHub Actions with MySQL
  6. Splitting the check across jobs
  7. Reporting the drift instead of failing
  8. Exit codes
  9. Regenerating in CI
  10. What to read next