Configurable PHP architecture guards — define your layers and rules, then keep them enforced.
composer require --dev boundwize/structarmedvendor/bin/structarmed initGenerates a structarmed.php in your project root. Edit it to match your structure, then run:
vendor/bin/structarmed analyseIf violations are found, the output reports each one:
If everything passes, you get a clean summary:
// structarmed.php
use Boundwize\StructArmed\Architecture;
use Boundwize\StructArmed\Preset\Preset;
return Architecture::define()
->withPreset(Preset::PSR4());->withPresets(Preset::PSR4(), Preset::DDD(), Preset::MVC())use Boundwize\StructArmed\Architecture;
use Boundwize\StructArmed\Rule\Rules\Layer\MayNotDependOnRule;
use Boundwize\StructArmed\Rule\Rules\Method\MustHaveReturnTypeRule;
return Architecture::define()
->layer('Domain', 'src/Domain/')
->layer('Application', 'src/Application/')
->layer('Infrastructure', 'src/Infrastructure/')
->skip([
'tests/Fixtures/',
'var/cache/*',
DddPreset::ENTITY_MUST_BE_FINAL => ['src/Legacy/'],
])
->rule(
'domain.must_not_depend_on_infrastructure',
new MayNotDependOnRule(from: 'Domain', to: 'Infrastructure', toPath: 'Infrastructure')
)
->rule(
'domain.public_methods_must_have_return_types',
new MustHaveReturnTypeRule(layer: 'Domain')
);Use rule key constants — never raw strings:
use Boundwize\StructArmed\Preset\Presets\DddPreset;
return Architecture::define()
->layer('Domain', 'src/Domain/')
->layer('Application', 'src/Application/')
->layer('Infrastructure', 'src/Infrastructure/')
->withPreset(Preset::DDD())
// Remove a rule entirely
->withoutRule(DddPreset::DOMAIN_NO_BASE_EXCEPTION)
// Replace a rule with a different configuration
->replaceRule(
DddPreset::ENTITY_MUST_BE_FINAL,
new MustBeFinalRule(layer: 'Domain', classNamePattern: '/Entity$|Aggregate$/')
)
// Add your own custom rule
->rule(
'our.handlers.must_be_in_application',
new NamingConventionRule(
classNamePattern: '/Handler$/',
mustBeInLayer: 'Application'
)
);->withPreset(Preset::DDD(
maxComplexity: 3, // default: 5
maxMethodLength: 15, // default: 20
enforceFinalEntities: false, // default: true
))
->withPreset(Preset::MVC(
controllerMaxComplexity: 3, // default: 5
controllerMaxDependencies: 4, // default: 5
viewMaxComplexity: 2, // default: 3
))
->withPreset(Preset::PSR4(
sourcePaths: ['src/', 'tests/'], // default: read composer.json PSR-4 paths
))| Preset | Rules |
|---|---|
Preset::PSR4() |
Verifies configured source paths exist in composer.json autoload or autoload-dev PSR-4 mappings |
Preset::DDD() |
Layer isolation, entity/VO/repository/event/service conventions |
Preset::MVC() |
Layer isolation, thin controllers, model/view/service rules |
Run architecture checks as part of your test suite:
<!-- phpunit.xml -->
<extensions>
<bootstrap class="Boundwize\StructArmed\PHPUnit\StructArmedExtension"/>
</extensions>Violations cause the test run to fail before any tests execute.
# Analyse with default config discovery
vendor/bin/structarmed analyse
vendor/bin/structarmed analyze
# Analyse only specific paths
vendor/bin/structarmed analyse src
vendor/bin/structarmed analyze src tests
# Custom config path
vendor/bin/structarmed analyse --config=path/to/structarmed.php
vendor/bin/structarmed analyze --config=path/to/structarmed.php
# JSON output (for CI tools)
vendor/bin/structarmed analyse --report=json
vendor/bin/structarmed analyze --report=json
# Generate initial config
vendor/bin/structarmed initLayers are resolved by file path — no attributes needed on classes:
src/Domain/ → 'Domain'
src/Application/ → 'Application'
src/Infrastructure/ → 'Infrastructure'
Every preset rule has a public constant. Use constants, never raw strings:
// ✅ correct — caught by IDE and static analysis
DddPreset::ENTITY_MUST_BE_FINAL
// ❌ wrong — typo silently does nothing
'ddd.entity.must_be_fnal'- PHP 8.2 or higher
nikic/php-parser^5.0