byrcsc/laravel-cartographer · 1.x
Model discovery.
How configured paths become a list of model classes, which classes qualify, how globs handle modular layouts, and why a path that matches nothing only warns.
Discovery answers one question: which classes should become entities. It reads files, not the database, and it runs before anything else.
// config/cartographer.php
'paths' => [
app_path('Models'),
],Each entry is expanded as a glob against directories. Every matched
directory is scanned recursively for .php files.
What qualifies as a model
A class becomes a candidate entity when all four hold:
- It is declared in a scanned
.phpfile. - The class exists once the autoloader has been asked for it.
- It subclasses
Illuminate\Database\Eloquent\Model. - It is not abstract.
Everything else is skipped in silence: traits, interfaces, enums, plain classes, anonymous classes, and files with no class in them at all. A file that declares two classes contributes both, if both qualify.
Class names are read from the file's PHP tokens, so a class is found by its
declaration rather than by its filename. app/Models/Concerns/HasSlug.php
containing a trait contributes nothing; app/Models/Catalog.php containing
Product and Variant contributes two.
Discovery is where exclude_models is applied:
'exclude_models' => [
App\Models\Telemetry::class,
App\Models\PasswordReset::class,
],An excluded model produces no entity and no edges. Relations that other models declare towards it are dropped too, because the edge has nowhere to land.
Modular and domain-driven layouts
Globs are the reason paths is a list of patterns rather than a list of
directories:
'paths' => [
app_path('Models'),
base_path('src/Domain/*/Models'),
base_path('modules/*/src/Models'),
],src/Domain/*/Models matches src/Domain/Billing/Models,
src/Domain/Catalog/Models, and any domain added later. Nothing needs updating
when a new module appears.
Only *-style directory matching is available — the pattern is passed to PHP's
glob() with directories only. There is no filtering by class name, namespace,
or file name; a directory is either scanned entirely or not at all. Use
exclude_models to remove individual classes.
Overlapping paths are safe
Files are keyed by their resolved real path, so a file reached through two
patterns is read once. Listing app_path('Models') twice, or listing both a
parent and a child directory, changes nothing about the result.
Ordering
The final list is sorted by fully qualified class name. Directory order,
filesystem order, and the order of the paths array have no effect on the
output. This is one half of why the diagram is stable between runs.
A path that matches nothing
A pattern matching no directory produces a warning and is skipped:
WARN Model discovery path matched no directories: /srv/app/src/Domain/*/ModelsThe run continues. This is deliberate: a glob for modules that do not exist yet in a given deployment should not break the command.
Warnings are suppressed under --stdout, so piped output stays clean. If you
are debugging paths, run without --stdout.
When discovery finds nothing at all, the command fails instead:
ERROR No Eloquent models were discovered. Check the paths configured in [cartographer.paths].Models are still filtered later
Discovery produces candidates, not entities. A discovered model is dropped from the graph at assembly time when either is true:
-
It cannot be instantiated.
new Modelis called to read the table name; if the constructor throws, the model is skipped with a warning naming the exception. -
Its table does not exist on the target connection:
WARN Skipping model [App\Models\Ghost]: table [missing_ghosts] does not exist.
The second is the common one, and it usually means the database is behind the code. Run your migrations and generate again.
Validation
paths and exclude_models are validated when the command runs, and a bad
value fails it:
| Problem | Message |
|---|---|
paths is not an array | [cartographer.paths] must be an array of strings. |
paths contains a non-string or an empty string | [cartographer.paths] must contain only non-empty strings. |
exclude_models contains a class that is not a model | [cartographer.exclude_models] must contain only Eloquent model class names. |
exclude_models entries are checked with is_a(..., Model::class, true), so
the class must be loadable. A typo in the class name fails the command rather
than silently excluding nothing.
What to read next
- Relationship detection for what happens to each discovered model next.
- Configuration for the full key reference.
- Troubleshooting if a model you expected is missing.