open-solid/arch-viewer
Open Solid Arch Viewer is a Laravel package that helps you explore your application's architecture. It visualizes classes, dependencies, and relationships in an interactive diagram, making it easier to understand structure, spot coupling, and navigate large codebases.
Before touching Composer, answer one question: does this codebase actually use OpenSolid conventions? The generator inspects OpenSolid constructs (aggregates, commands, queries, events, handlers) and derives documentation from their shape. If your Laravel app only borrows a trait or two, the output will be thin at best and misleading at worst. Ten minutes in src/ will tell you more than any README will, because the package is single-author, has zero dependents, and almost certainly has no documentation site.
composer require --dev open-solid/arch-viewer
Keep it in require-dev. This is a documentation tool — it should never appear in the deployable artifact, never sit in the request path, and never be a runtime dependency of anything your app actually executes.
php artisan vendor:publish --tag=arch-viewer # if a config is shipped
php artisan arch:viewer # name may differ — check src/Console
If the artisan command name isn't obvious, grep -r "signature" src/Console is your fastest path. The command typically walks the OpenSolid constructs it understands and emits static output — Markdown, HTML, or both — into a docs/ or build/ directory.
src/ — the whole thing is small enough to read in an afternoon. You want to know: how does it discover constructs? Reflection, AST, or runtime container boot? Static is far safer for CI; a runtime scan can trip on unbound services.Pick one bounded context — ideally the messiest, most convention-divergent one you have. Run the generator against it. Then pull up the hand-written architecture summary you'd normally give a new hire and compare. That diff, not the package's star count, is the thing that tells you whether this is worth adopting. Expect roughly half a day for a developer to get through this evaluation.
The workflow that makes generated docs useful is the same one you'd use for any other build artifact you want reviewed: generate on demand, commit the result, diff it in PRs. A generated docs/architecture/ directory with a visible timestamp and commit SHA in its header stays honest. A wiki page that nobody regenerates rots within a quarter.
Keep the generator in require-dev and the output in version control. That inverts the usual dependency relationship in a healthy way: if the tool disappears tomorrow, the docs still exist.
Run the generator per context or per module rather than for the whole monolith, and land each output in its own file. This gives you three things at once:
This package documents; it does not police. If your actual goal is failing builds on layering violations, that's Deptrac, PHPat, or Arkitect — different tool, different conversation. The pattern that works is:
The cheap, useful version: render the model and enforce the rules from the same conceptual source, so the doc review and the rule check are describing the same thing.
Wire it as a non-blocking step before you ever consider gating on it:
- name: Generate architecture docs
run: php artisan arch:viewer
continue-on-error: true
Run it twice back-to-back and diff the two outputs. If they're byte-identical, you have determinism and can move toward a soft "docs changed but weren't committed" warning. If they aren't, stop there — a non-deterministic generator in a blocking check will fail spuriously and be disabled within a fortnight.
The clearest day-to-day win for a Laravel team is onboarding and "where does this live?" questions. Keep the generated map in docs/ and link it from the README's architecture section. When a new hire asks "which module owns this?", the answer is a file, not a Slack thread. That's the measurable line item — time-to-first-PR, ramp-up cost per hire — not "better documentation" as an aspiration.
Post-incident, "which module actually owns this responsibility?" is a real question and the generated map is a fast answer. Same for design review: put the map at the top of the PR description when introducing a new dependency between contexts, so reviewers see the coupling rather than inferring it from a diff.
A doc generator that misses constructs is worse than no doc generator, because the output looks complete. If handlers are registered through a custom service provider, or your team uses attributes where the generator expects annotations, those constructs simply won't appear — and nothing will warn you. Validate the generator's coverage on a real context against a manual inventory, not against a sample repo. The most dangerous output is a map with a confidently blank region.
Before you commit a single generated file, run the generator twice and diff. Stable output means stable diffs, which means CI-checkable, which means the docs can be reviewed like code. Unstable output — timestamps in the body, non-sorted keys, reflection order leaking through — means every regeneration produces a noisy diff and the check gets disabled. The mitigation is cheap: commit generated docs with a header line containing the timestamp and commit SHA, so the only thing that ever varies is what you deliberately put there.
Zero stars, zero dependents, no score, single-vendor origin. Pin to a commit SHA, not a version range — release cadence is unknown and a silent upstream bump can change output under you. Mirror the repository internally and record the decision in your dependency register. If it's abandoned, you fork a small tool you already understand; that's a priceable risk, but only if you priced it. The realistic maintainer cost is half a day to evaluate plus one to two days of contingent fork ownership if you adopt.
The generator's model of the codebase is built on OpenSolid's constructs, so the constraint on OpenSolid core is likely narrow. That means an OpenSolid or PHP bump can break generation mid-upgrade, and the failure can land in the middle of an unrelated maintenance task. Run the generator as part of your upgrade checklist before the OpenSolid bump, so you know what shape the breakage takes while you still have context.
If the generator discovers constructs via reflection or AST, it's safe in CI. If it boots the container to scan at runtime, it can trip on unbound services, need a database connection, or pull in environment-specific providers. Check src/ early. If it's runtime-based, either isolate it in a separate CI step with a minimal env, or accept that it's a local-only tool and skip CI integration entirely.
Laravel teams register things in ways static analyzers can't see: deferred providers, container bindings behind conditionals, event subscribers constructed dynamically. If your OpenSolid wiring leans on any of these, the generator will under-report. The mitigation is a manual checklist per bounded context: list what you know exists, compare to what appears in the output, and only then trust the map. Do this once per context, at evaluation time — not every run.
The require-dev-only stance isn't just about install size. It's an operational guarantee: the generator can be broken, outdated, abandoned, or uninstalled and it will not affect your deployable artifact. Resist the temptation to make it a hard gate until you've seen stable output across two consecutive runs and have an exit path documented. The exit path is the short in-house script — reflection plus a static docs page, roughly 1–2 engineer-days given the narrow scope. Write that down before the spike, so "no" is a cheap, informed answer rather than a sunk cost.
src/If you need the generator to understand a construct it doesn't ship with — your own base command class, a custom attribute, a project-specific handler pattern — the extension point is whatever the author happened to leave open in the source. There is no documented plugin API and no contributor community to ask. Read the discovery layer, find where constructs are matched, and extend there. Vendoring the fork from day one is the honest posture.
The generator can tell you what depends on what. It cannot tell you why the boundary exists, what trade-off was made, or what you rejected. Those belong in hand-written ADRs next to the generated map, not in it. The healthy pattern is: generated structure lives in one directory and is never hand-edited; narrative decisions live alongside and cite the map. Mixing the two means the next regeneration wipes someone's careful prose.
How can I help you explore Laravel packages today?