Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Structarmed Laravel Package

boundwize/structarmed

StructArmed turns architecture decisions into executable checks for Laravel and PHP projects. Start from PSR, MVC, or DDD presets, define your layers and dependency rules in native PHP, then run structarmed analyse to catch violations before they spread.

View on GitHub
Deep Wiki
Context7

Getting Started

StructArmed is a dev-only static analyser for your app/ tree — the same category as PHPStan or Deptrac, not a runtime Laravel package. You get value on day one by turning your team's unwritten "controllers don't touch models directly" convention into a failing command.

composer require --dev boundwize/structarmed
vendor/bin/structarmed init --preset=psr4   # scaffold structarmed.php at the repo root
vendor/bin/structarmed analyse             # baseline: expect noise on a mature app

Where to look first:

  1. structarmed.php at the project root — the whole tool is this one PHP file returning Architecture::define(). It is committed to the repo and reviewed like code.
  2. The docs site (https://boundwize.github.io/structarmed/) — the README is thin; the layer/rule DSL and rule catalogue only exist in docs/. Read docs/custom-rules-and-presets.md before writing anything custom.
  3. The presets — Preset::PSR4(), PSR15(), YAGNI(), plus MVC/DDD presets. Start with PSR4() only, because it is the least opinionated and produces the smallest diff of complaints.

First use case that pays for itself on a Laravel app: layer enforcement on App\Http. Define your layers (Http, Jobs, Models, Services, Actions, Support, Providers), declare that Http may depend on Actions/Services/Support but not on Models, run analyse, and you will immediately find the three controllers that query Eloquent inline. That is a genuine architectural regression caught in CI rather than in a code review comment nobody enforces.

Second quick win: stack Preset::YAGNI() to find unused interfaces, traits, and abstract classes left behind by refactors — it supports --fix, which is the closest thing this tool has to a "wow" moment.

Implementation Patterns

Baseline first, then ratchet. Do not add layer rules on day one of an existing codebase. Add the preset, capture the current violation count, and add skip paths for legacy directories:

vendor/bin/structarmed analyse > baseline.txt; wc -l baseline.txt

Then fix one layer at a time and never let the count grow. This is the same adoption model as PHPStan baselines and is the only way a 60-star, zero-dependent tool survives contact with a team.

Stack presets instead of hand-rolling rules. Most of what a Laravel team wants is already covered:

return Architecture::define()
    ->withPresets(Preset::PSR4(), Preset::PSR15(), Preset::YAGNI());

Add YAGNI() last and scope it — see the gotcha about external extension points below.

Write custom rules for Laravel-specific conventions. The custom-rule contract is small and stable: implement RuleInterface (or a marker sub-interface), implement appliesTo(ClassNode) to narrow the blast radius, and return ?RuleViolation. A good first custom rule is "every contract in App\Contracts is actually implemented or referenced":

final readonly class ContractMustBeImplementedRule implements UsedInterfaceAwareRuleInterface
{
    public function appliesTo(ClassNode $classNode): bool
    {
        return $classNode->isInterface && $classNode->isInLayer('Contracts');
    }

    public function evaluate(ClassNode $classNode): ?RuleViolation
    {
        if ($classNode->isImplemented || $classNode->isReferenced) {
            return null;
        }

        return new RuleViolation(
            message:   sprintf('Contract [%s] must be implemented or referenced', $classNode->className),
            file:      $classNode->file,
            line:      $classNode->line,
            className: $classNode->className,
            layer:     $classNode->layer,
        );
    }
}

Always populate file and line from the node — that is what makes violations clickable in an IDE terminal and unambiguous in CI logs. appliesTo() is a performance contract: check isInLayer() or the isInterface/isAbstract flags before doing string work.

Gate expensive usage analysis behind marker interfaces. The analyser only populates isExtended, isInstantiated, isImplemented, isReferenced when at least one active rule declares it needs them:

Marker interface Flags populated on ClassNode
ExtendedClassAwareRuleInterface isExtended, isReferenced, isInstantiated
UsedInterfaceAwareRuleInterface isImplemented, isReferenced
UsedTraitAwareRuleInterface isReferenced

This is a deliberate opt-in cost, so treat markers as part of your rule's API, not boilerplate.

Enforce facades as dependencies. Laravel's DB::, Cache::, Http:: calls are invisible to any class-dependency analyser, but the use Illuminate\Support\Facades\DB; import is a real class reference. Treat Illuminate\Support\Facades as a layer and forbid Http from depending on it if "controllers must not touch infrastructure directly" is your rule. This is the single most useful Laravel-specific trick with this class of tool.

CI wiring. Fail the build on violations, don't auto-fix:

- run: composer install --no-interaction --prefer-dist
- run: vendor/bin/structarmed analyse

Run it as its own step (not chained with Pint/PHPStan) so a violation is distinguishable from a style failure. Give CI a cold cache and key it on composer.lock + structarmed.php — the analyser caches results and caches are how you get a green build that contradicts a red local one.

Local --fix workflow. YAGNI rules support --fix: unused interfaces and traits are deleted, classes that are only ever extended are made abstract. Run it on a clean working tree, then git diff before commit. Never run --fix inside CI or a pre-commit hook — deleting declarations automatically is exactly the fix you want to eyeball.

Pre-review sweep. Make analyse the last command before opening a PR. Architecture drift is introduced by feature PRs, and one command that names the layer violation is far cheaper than a review thread about it.

Gotchas and Tips

Pre-1.0 with a fast release cadence. The current line is 0.19.x with dozens of patch releases, many of them performance and correctness fixes to instantiation tracking. Pin "boundwize/structarmed": "^0.19" and read the release note before bumping — 0.16.0 added the marker interfaces and YAGNI, so anything reading usage flags silently misbehaves on older versions.

Silent false negatives from missing marker interfaces. If your custom rule reads $classNode->isImplemented but the class implements plain RuleInterface, the flag stays false forever and the rule simply never fires. There is no warning. When a rule "doesn't detect anything", check the marker first.

Instantiation tracking needed specific patches. new self()/new static() in traits and new parent() in traits were fixed in 0.16.4/0.16.5. If you rely on isInstantiated semantics, require >=0.16.5.

YAGNI lies about libraries and extension points. The preset only knows about usage inside the scanned paths. Interfaces exposed for third-party consumers, Laravel package contracts, and anything resolved only by a dynamic container string will look unused and will be deleted by --fix. Skip those paths, and never run --fix on a package or a src/ that ships publicly.

Facades and helpers are invisible; string bindings are partly visible. app(), resolve(), DB:: hide dependencies from the analyser entirely. Usage detection does cover type hints, instanceof, ::class, static calls, and class-name strings — so Foo::class in a service provider counts, but a concatenated or config-driven class name does not. Design rules around what is visible, or you will chase phantom violations.

Cache staleness after editing the config. Cache paths are centralised in CachePathFactory, and the cache is designed to only clear files it created — meaning a config edit can leave you staring at yesterday's result. Clear the cache directory whenever you touch structarmed.php or change presets, and always run cold in CI.

Parallel worker failures used to be opaque. Before 0.16.6, a worker dying mid-run produced no exit code or stderr, just a confusing empty result. If the analysis "passes" suspiciously fast on a large app, upgrade and re-run serially once to compare.

Layer-prefix overlap is resolved by configuration order and specificity. Laravel namespaces nest (App\Http\Controllers\Api vs App\Http), so define the more specific layer first and validate with a deliberate throwaway violation in app/Http/Controllers/Api before trusting the map. isInLayer() returning false for a class you can see in that directory is almost always an ordering or namespace-prefix mistake, not a bug.

Config is executed PHP. structarmed.php is required, so no side effects, no env-dependent conditionals, no config() calls. If CI and local disagree about which layers exist, you have put runtime logic in the config.

Use the Explorer for onboarding. The community StructArmed Explorer renders structarmed.php as an interactive layer-dependency graph with no install. It is the fastest way to show a new hire that Http → Models is currently the shortest path in the app, before you make it fail the build.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel