Download the PHP package happenv-com/laravel-true-modular without Composer
On this page you can find all versions of the php package happenv-com/laravel-true-modular. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package laravel-true-modular
True Modular for Laravel
Make your Laravel architecture explicit, deterministic, and analyzable.
Modules are first-class Composer packages with deterministic dependency ordering, an extended lifecycle, and a built-in architecture runtime. Instead of an architecture that lives only in your team's heads, you get one you can query, graph, and reason about.
Ask the codebase what a change touches before you make it.
Contents
- Why
- Where modules live
- The module graph
- Enforcing boundaries (static analysis)
- Built for agentic coding
- Defining a module
- Extending existing modules
- Extending the framework
- Switching modules off
- Production notes
- Install
- Documentation
- Development
- License
Why
Large Laravel applications get harder to evolve over time. Modules end up depending on each other silently, boot order becomes implicit, cross-module initialization is fragile, and the real shape of the architecture survives only in the heads of the people who wrote it.
True Modular for Laravel makes that shape explicit, and builds three guarantees on top of it:
- Topological provider ordering - module service providers are sorted by their
composer.jsondependencies, so a module always boots after the modules it depends on. Cycles are detected and reported, not silently mis-ordered. - Enhanced lifecycle -
register() → initialize() → boot(). Theinitialize()phase runs after every provider is registered but before anything boots - including third-party package providers. So your cross-module wiring (morph maps, permissions, drivers, Livewire/Filament hooks) is in place before any package'sboot()reads it. - Architecture runtime -
module:graph,module:impact,module:why,module:list, with--format=jsonso you can wire blast-radius checks into CI, and--format=mermaid/dotto render the graph.
Where modules live
A module is just a Composer package whose composer.json declares type: "true-module". That means
a module can live in either place:
- Local to your app - under the
app-modules/directory, versioned alongside the rest of your code. This is where most modules start. - An external Composer package - pulled in via
composer requireand resolved fromvendor/like any dependency, so a module can be shared across applications or published privately.
Both are discovered the same way and take part in the same dependency ordering and tooling - there's
no difference in how they behave at runtime. The modules directory (default app-modules) and the
module type (default true-module) are configurable in bootstrap/app.php via
Application::modulesDirectory() and Application::moduleComposerType().
The module graph
Modules declare their dependencies in composer.json like any other Composer package. The package
reads those edges and derives both the shape of your system and the exact order things run.
Real graphs aren't a straight line - modules fan out and share dependencies. The number on each node
is its position in the deterministic boot order:
module:graph renders that as a tree - each module sits under the one it depends on. A module with
two dependencies (here sale) appears under each path that reaches it:
module:list flattens it into the deterministic execution order - the exact, numbered sequence
in which providers register(), initialize(), and boot(), dependencies first:
No module ever boots before the modules it depends on - and a cycle is a hard error, not a race condition. Other views of the same graph:
Local modules are referenced by their short name (amazon) — the package qualifies them with your
default vendor (derived from modulesNamespace, e.g. happenv/amazon). External packages keep their
full vendor/name (acme/catalog), which signals they aren't part of the local system. Resolution is
case-insensitive.
Output mirrors this: local modules print by short name, external packages by full vendor/name. Pass
--with-vendor to any of these commands to print every module with its full vendor/name; --format=json
always uses full names.
Enforcing boundaries (static analysis)
The runtime discovers and explains the architecture; a companion package
happenv-com/laravel-true-modular-phpstan
enforces it. It ships two zero-config PHPStan extensions:
- Module Boundary Enforcer - fails analysis when a module references a class from another module
that isn't declared in its
composer.jsonrequire, and detects circular dependencies between modules. It reads the samecomposer.jsonedges the framework uses to order providers, so there's nothing to configure. - Dynamic Relation Resolver - types Eloquent relations registered at runtime (e.g. relations one module adds to another module's model via model extensions), which are otherwise invisible to static analysis.
With phpstan/extension-installer both
extensions register automatically. See the
package README for details.
Built for agentic coding
Explicit boundaries aren't only good for humans — they're what makes a codebase legible to an AI coding agent, and one of the biggest reasons to adopt this architecture today.
- Smaller context, faster iterations. A module is a self-contained package with an explicit dependency list, so an agent loads just that module and the few it depends on — not the whole app.
- The agent knows where it's allowed to work.
module:impact,module:why, andmodule:graph --root=let it ask "what does this affect, and what does it depend on?" — so it moves inside well-defined boundaries instead of grepping and guessing across the whole system. - Guardrails it gets feedback from. The PHPStan
Module Boundary Enforcer fails the moment generated code
crosses a boundary not declared in
composer.json— immediate, machine-readable feedback, not a reviewer catching it later. - It already knows the conventions. The package ships
Laravel Boost resources auto-installed by
boost:install, so an agent picks up the lifecycle, theModulebuilder, and the rules without docs in the prompt.
See docs/agentic-coding.md for the full agent workflow.
Defining a module
A module is a Composer package (type: "true-module") whose service provider extends
ModuleProvider and declares its features fluently:
The
ModuleProvider/Modulefluent API is heavily inspired by spatie/laravel-package-tools. Many thanks to Spatie and its contributors for their hard work — this package builds on the patterns they pioneered.
Extending existing modules
Modules don't only talk to each other through services - they can extend the domain model itself. A downstream module adds attributes and relations to an upstream module's Eloquent model without touching that model's class, so the dependency arrow stays pointed the right way.
The billing module declares the extension:
And Product gains the relation as if it were defined on it:
A sibling, hasModelBuilderExtensions(), does the same for an Eloquent query builder - the key
is the builder class to mix new query methods into, so a downstream module can teach an upstream
model's builder new scopes:
The catalog module that owns Product is never modified - billing contributes new attributes,
relations, and query methods to it. Each module composes the shared domain model instead of forking
or patching it. (Static analysis still sees these runtime additions, thanks to the
PHPStan extension above.)
Extending the framework
The extensions above aren't a custom trick — they ride on Macroable, the trait Eloquent already
uses everywhere (Builder, Collection, Str, Request, …) to add methods to a class from the
outside, without editing it. That's a textbook open/closed, and the natural tool for
cross-module extension: hasModelBuilderExtensions() registers a builder mixin
(Builder::mixin(...)), hasModelExtensions() adds relations via resolveRelationUsing().
Macros are normally invisible to static analysis — but the companion PHPStan extension (with Larastan) types the relations and mixins this package registers, so you extend modules without patching them and keep a green analysis. See docs/model-extensions.md.
Switching modules off
A module you are not ready to ship should stay in the repository and stop booting — not disappear from
composer.json. Removing it there leaves the directory on disk while its PSR-4 mapping vanishes, so
the first symptom is a bare class not found from somewhere unrelated.
Two channels decide what boots, and they are never merged — the environment key wins whenever it is set, even to an empty string:
Filtering happens in the package manifest, so a disabled module loses its service providers and its facade aliases together: no config merge, no routes, no migrations, no scheduled tasks, no panel plugins. With an empty list the manifest is returned untouched and the module registry is never read, so an application that configures neither channel behaves exactly as before.
A module can declare two things about itself in its own composer.json:
disablable: false marks a module that must never be switched off (set it on your core module).
owns lists vendor packages this module is the sole owner of — they leave discovery with it, so
disabling a wrapper doesn't leave the wrapped engine booting its own provider and migrations.
Disabling never cascades: switching off a module an enabled module depends on is an error that lists exactly what to add. Computing a cascade belongs to a user interface that can ask for confirmation; what reaches the runtime is always an explicit, complete list.
module:check also catches the orphaned-directory failure above — a module directory that no
composer.json requires — turning it into one sentence instead of a missing class.
See Switching modules off for the full reference.
Install
The one non-obvious step: the package ships a custom Application that performs the topological sort
and the extra lifecycle phase, so bootstrap/app.php must boot through it. The
php artisan true-modular:setup command rewrites bootstrap/app.php for you - or wire it by hand as
shown in Getting started.
Requires PHP 8.3+ and Laravel 12/13.
Documentation
Full documentation lives in docs/:
| Getting started | Install and create your first module. |
| CLI commands | Graph / impact / why / list, and the --format options. |
| Agentic coding | Working on the codebase with AI agents; the workflow. |
| Architecture & runtime | The lifecycle and the analysis layer. |
| Module dependencies | Discovery, ordering, cycles. |
| Model extensions | Add attributes/relations to another module's model. |
| Module builders | The fluent Module API and every feature. |
| Lifecycle hooks & schemas | Overridable provider hooks; report JSON schema. |
| Config merging | The four config strategies and merge semantics. |
| Switching modules off | Keep a module in the repo without booting it. |
| Anti-patterns | Do's and don'ts. |
| Extending the package | Add features, renderers, sources. |
| Testing | Fixtures, helpers, patterns. |
| Laravel Boost | AI guidelines & skills for coding agents. |
Development
Alternatives
This isn't the only way to build a modular Laravel app. If this package doesn't fit your needs, take a look at:
- nWidart/laravel-modules — organizes a large Laravel app into self-contained modules, each with its own views, controllers, and models, managed through a dedicated directory structure and generator commands.
- InterNACHI/modular — splits an app into separate modules using Composer path repositories and Laravel's native package discovery, instead of a custom directory structure.
License
MIT
All versions of laravel-true-modular with dependencies
laravel/framework Version ^12.0 || ^13.0
thecodingmachine/safe Version ^3.0