boundwize/structarmed
StructArmed is a dev-only PHP architecture guard: define layers and dependency rules, start from presets (PSR-4/1/12, MVC, DDD), then tune or skip checks in PHP. Run it in CI to catch boundary violations before they become conventions.
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.19...0.15.20
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.18...0.15.19
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.17...0.15.18
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.16...0.15.17
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.15...0.15.16
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.14...0.15.15
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.13...0.15.14
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.12...0.15.13
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.11...0.15.12
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.10...0.15.11
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.9...0.15.10
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.8...0.15.9
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.7...0.15.8
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.6...0.15.7
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.5...0.15.6
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.4...0.15.5
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.3...0.15.4
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.2...0.15.3
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.1...0.15.2
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.0...0.15.1
This release introduces Boundwize\StructArmed\Rule\ExtendedClassAwareRuleInterface.
A marker that lets a rule know whether the class it's evaluating is extended by another scanned class, and applies it to Boundwize\StructArmed\Rule\Rules\Class_\MustBeFinalRule.
Using interface in custom rule:
use Boundwize\StructArmed\Analyser\ClassNode;
use Boundwize\StructArmed\Rule\ExtendedClassAwareRuleInterface;
use Boundwize\StructArmed\Rule\RuleViolation;
final readonly class MyRule implements ExtendedClassAwareRuleInterface
{
public function appliesTo(ClassNode $classNode): bool
{
return $classNode->isClass();
}
public function evaluate(ClassNode $classNode): ?RuleViolation
{
// Skip classes that another scanned class extends.
if ($classNode->isExtended) {
return null;
}
// other check ...
return new RuleViolation(
...
);
}
}
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.20...0.15.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.19...0.14.20
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.18...0.14.19
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.17...0.14.18
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.16...0.14.17
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.15...0.14.16
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.14...0.14.15
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.13...0.14.14
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.12...0.14.13
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.11...0.14.12
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.10...0.14.11
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.9...0.14.10
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.8...0.14.9
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.7...0.14.8
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.6...0.14.7
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.5...0.14.6
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.4...0.14.5
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.3...0.14.4
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.2...0.14.3
Psr1Utf8WithoutBomRuleinto 2 rules: Psr1Utf8WithoutBomRule and Psr1ValidUtf8Rule and make Psr1Utf8WithoutBomRule implements FixableInterface by @samsonasik in https://github.com/boundwize/structarmed/pull/183Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.1...0.14.2
Full Changelog: https://github.com/boundwize/structarmed/compare/0.14.0...0.14.1
StructArmed 0.14.0 introduces new Boundwize\StructArmed\Rule\FixableInterface that when rule implements it, it can apply a fix with make use of fix method, eg:
use Boundwize\StructArmed\Rule\FixableInterface;
-final readonly class SomeRule implements RuleInterface
+final readonly class SomeRule implements RuleInterface, FixableInterface
{
+ public function fix(RuleViolation $ruleViolation): bool
+ {
+ // do custom fix implementation based on RuleViolation object information
+ }
}
This feature brings some example of it that utilize PhpParser:
that extends Boundwize\StructArmed\Rule\Fixer\PhpParser\AbstractPhpParserFixableRule that we only need to define the PhpParser visitor that do the fix:
protected function createFixerVisitor(RuleViolation $ruleViolation): AddPublicConstantVisibilityVisitor
{
/** [@var](https://github.com/var) string $constantName */
$constantName = $ruleViolation->constantName;
return new AddPublicConstantVisibilityVisitor(
$ruleViolation->className,
$constantName
);
}
The usage will be:
vendor/bin/structarmed analyze --fix
The example usage will be on https://boundwize.github.io/structarmed/custom-rules-and-presets/
Full Changelog: https://github.com/boundwize/structarmed/compare/0.13.8...0.14.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.13.7...0.13.8
Full Changelog: https://github.com/boundwize/structarmed/compare/0.13.6...0.13.7
Full Changelog: https://github.com/boundwize/structarmed/compare/0.13.5...0.13.6
Full Changelog: https://github.com/boundwize/structarmed/compare/0.13.4...0.13.5
Full Changelog: https://github.com/boundwize/structarmed/compare/0.13.3...0.13.4
Full Changelog: https://github.com/boundwize/structarmed/compare/0.13.2...0.13.3
--no-progress by @samsonasik in https://github.com/boundwize/structarmed/pull/162Full Changelog: https://github.com/boundwize/structarmed/compare/0.13.1...0.13.2
Full Changelog: https://github.com/boundwize/structarmed/compare/0.13.0...0.13.1
This release improves inheritance-aware architecture analysis. StructArmed can now resolve recursive class and interface ancestry.
For example, this now resolves correctly:
class A extends B {}
class B extends C {}
class C implements \Psr\Http\Server\MiddlewareInterface {}
A is now treated as implementing \Psr\Http\Server\MiddlewareInterface.
One example is ClassImplementingInterfaceMustHaveSuffixRule, which now applies to a class that extends another class, which eventually implements \Psr\Http\Server\MiddlewareInterface.
This keeps PSR-15 suffix checks compatible with custom middleware and request-handler abstractions before they reach the root PSR interfaces.
Full Changelog: https://github.com/boundwize/structarmed/compare/0.12.6...0.13.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.12.5...0.12.6
Full Changelog: https://github.com/boundwize/structarmed/compare/0.12.4...0.12.5
Full Changelog: https://github.com/boundwize/structarmed/compare/0.12.3...0.12.4
Full Changelog: https://github.com/boundwize/structarmed/compare/0.12.2...0.12.3
Full Changelog: https://github.com/boundwize/structarmed/compare/0.12.1...0.12.2
Full Changelog: https://github.com/boundwize/structarmed/compare/0.12.0...0.12.1
You can now pass an array of class-name regex patterns for a layer:
->layerPattern('Service', [
'/^App\\\\Service\\\\.*$/',
'/^App\\\\Application\\\\.*Service$/',
])
You can also pass an array of exclude patterns:
->layerPattern('HTTP', '/^App\\\\HTTP\\\\.*$/', [
'/Exception$/',
'/^App\\\\HTTP\\\\URI$/',
])
Full Changelog: https://github.com/boundwize/structarmed/compare/0.11.3...0.12.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.11.2...0.11.3
Full Changelog: https://github.com/boundwize/structarmed/compare/0.11.1...0.11.2
+LayerName by @samsonasik in https://github.com/boundwize/structarmed/pull/125Full Changelog: https://github.com/boundwize/structarmed/compare/0.11.0...0.11.1
Adds support for the +LayerName shorthand in ruleset definition.
For example, given the following config:
->ruleset([
'API' => ['Format'],
'Controller' => ['Validation'],
'RESTful' => ['+API', '+Controller'],
])
It nows merge 2 collection of layer and its allowed dependents, which replace the manual write:
-'RESTful' => ['API', 'Format', 'Controller', 'Validation'],
+'RESTful' => ['+API', '+Controller'],
removing the need to duplicate those entries by hand.
Full Changelog: https://github.com/boundwize/structarmed/compare/0.10.1...0.11.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.10.0...0.10.1
StructArmed 0.10.0 introduces two new Composer PSR-4 autoload rules:
Psr4RootPathRulePsr4EmptyNamespacePrefixRuleThese rules help detect broad or ambiguous PSR-4 autoload definitions that may cause unnecessary lookup across the project root or even the whole vendor/ directory.
Psr4RootPathRuleThis rule marks PSR-4 namespaces that point to the project root as violations.
For example:
{
"autoload": {
"psr-4": {
"App\\": "",
"App2\\": "./"
}
}
}
Root path definitions can make Composer search too broadly. In most applications and libraries, each namespace should point to a specific source directory, such as src/.
There are known cases where root path autoloading exists in real projects, for example in Symfony component repositories. In that case, it is usually tied to a split, read-only repository layout. The main/root project still uses directory-based PSR-4 paths.
Psr4EmptyNamespacePrefixRuleThis rule marks empty or invalid namespace prefixes as violations.
For example:
{
"autoload": {
"psr-4": {
"": "src",
"\\": "src/"
}
}
}
Empty namespace prefixes are too broad and can make Composer attempt to resolve classes without a clear namespace boundary. This can also overlap with third-party packages, which should already be resolved by their own autoload definitions.
Psr4RootPathRule and Psr4EmptyNamespacePrefixRule by @samsonasik in https://github.com/boundwize/structarmed/pull/122Full Changelog: https://github.com/boundwize/structarmed/compare/0.9.5...0.10.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.9.4...0.9.5
Full Changelog: https://github.com/boundwize/structarmed/compare/0.9.3...0.9.4
to target when possible, fallback to toPath by @samsonasik in https://github.com/boundwize/structarmed/pull/118Full Changelog: https://github.com/boundwize/structarmed/compare/0.9.2...0.9.3
Full Changelog: https://github.com/boundwize/structarmed/compare/0.9.1...0.9.2
Full Changelog: https://github.com/boundwize/structarmed/compare/0.9.0...0.9.1
ClassNode::dependsOn() no longer accepts namespace strings.
Use ClassNode::dependsOnNamespace() when checking namespace-level dependencies.
This release also adds MayNotUseNamespaceRule.
The rule checks that a layer does not depend on classes from a forbidden namespace.
It is wired into the presets as follows:
MvcPreset
Doctrine\ORM\Full Changelog: https://github.com/boundwize/structarmed/compare/0.8.7...0.9.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.8.6...0.8.7
Full Changelog: https://github.com/boundwize/structarmed/compare/0.8.5...0.8.6
Full Changelog: https://github.com/boundwize/structarmed/compare/0.8.4...0.8.5
Full Changelog: https://github.com/boundwize/structarmed/compare/0.8.3...0.8.4
Full Changelog: https://github.com/boundwize/structarmed/compare/0.8.2...0.8.3
Full Changelog: https://github.com/boundwize/structarmed/compare/0.8.1...0.8.2
StructArmed 0.8.0 introduces a new rule: Psr4DirectoryExistsRule.
This rule detects PSR-4 directory registrations in composer.json that do not exist on disk.
Given this composer.json configuration:
{
"autoload": {
"psr-4": {
"view\\": "directory/not/exists"
}
}
}
When using the PSR-4 preset, StructArmed will report the violation:
StructArmed 0.8.0 — Architecture Enforcement
=================================================
Found 1 violation(s):
─────────────────────────────────────────────────
✗ [psr4.source_paths.must_exist_on_disk]
PSR-4 source path(s) [directory/not/exists] declared in composer.json do not exist on disk
→ /Users/samsonasik/www/yourapp/composer.json:1
Layer: Source
─────────────────────────────────────────────────
1 violation(s) found • 0.34s
composer.json by @samsonasik in https://github.com/boundwize/structarmed/pull/100if block in ChainLayerResolver by @samsonasik in https://github.com/boundwize/structarmed/pull/98Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.12...0.8.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.11...0.7.12
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.10...0.7.11
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.9...0.7.10
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.8...0.7.9
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.7...0.7.8
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.6...0.7.7
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.5...0.7.6
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.4...0.7.5
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.3...0.7.4
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.2...0.7.3
Before
After
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.1...0.7.2
Full Changelog: https://github.com/boundwize/structarmed/compare/0.7.0...0.7.1
StructArmed 0.7.0 adds a new PSR-15 preset for projects that use HTTP middleware and request handlers.
This release helps keep PSR-15 naming and interface contracts consistent across your codebase, especially in Mezzio and other PSR-15 based applications.
use Boundwize\StructArmed\Architecture;
use Boundwize\StructArmed\Preset\Preset;
return Architecture::define()
->withPreset(Preset::PSR15(
sourcePaths: ['src/'],
));
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.16...0.6.17
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.15...0.6.16
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.14...0.6.15
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.13...0.6.14
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.11...0.6.12
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.10...0.6.11
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.9...0.6.10
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.8...0.6.9
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.7...0.6.8
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.6...0.6.7
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.5...0.6.6
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.3...0.6.4
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.2...0.6.3
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.1...0.6.2
Full Changelog: https://github.com/boundwize/structarmed/compare/0.6.0...0.6.1
Full Changelog: https://github.com/boundwize/structarmed/compare/0.5.5...0.6.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.5.4...0.5.5
Full Changelog: https://github.com/boundwize/structarmed/compare/0.5.3...0.5.4
Full Changelog: https://github.com/boundwize/structarmed/compare/0.5.2...0.5.3
Structarmed now runs analysis in parallel by default, making architecture checks faster on larger codebases.
The number of workers is automatically resolved from the available CPU cores, so most projects can benefit from parallel execution without extra configuration.
Parallel processing can still be disabled when needed, for example when debugging worker-related issues:
vendor/bin/structarmed analyse --disable-parallel
Full Changelog: https://github.com/boundwize/structarmed/compare/0.5.1...0.5.2
Full Changelog: https://github.com/boundwize/structarmed/compare/0.5.0...0.5.1
Until now, layers were resolved purely by path. In 0.5.0, you can define layers through namespace regex patterns using the new layerPattern() method. This is the natural choice for codebases where the architecture lives in the namespace tree rather than the directory structure.
return Architecture::define()
->layerPattern('API', '/^App\\\\API\\\\.*$/')
->layerPattern('HTTP', '/^App\\\\HTTP\\\\.*$/')
->layerPattern('Database', '/^App\\\\Database\\\\.*$/');
An optional third argument acts as an exclude filter — classes whose FQN matches it are skipped even when the primary pattern matches:
->layerPattern('HTTP', '/^App\\\\HTTP\\\\.*$/', '/^App\\\\HTTP\\\\URI$/')
->layerPattern('URI', '/^App\\\\HTTP\\\\URI$/')
Once layers are defined (via layer() or layerPattern()), declare the full allowed-dependency map in one place with ruleset(). Any dependency that resolves to a layer not listed is a violation:
->ruleset([
'API' => ['HTTP'], // API may only depend on HTTP
'HTTP' => ['Database'], // HTTP may only depend on Database
'Database' => [], // Database must not depend on any layer
])
Layers absent from the map are not checked. Same-layer dependencies and dependencies on external (non-registered) classes are always allowed.
When a specific class-to-class dependency is a known exception, suppress it without disabling the whole layer rule:
->skipClassViolation('App\\HTTP\\ResponseTrait', [
'App\\Pager\\PagerInterface',
])
->skipClassViolation('App\\Log\\ChromeLoggerHandler', 'App\\HTTP\\ResponseInterface')
Test files routinely cross layer boundaries by design. skipPathsForRuleset() excludes paths from ruleset evaluation while still scanning them for all other rules (e.g. PSR-4 namespace checks):
->skipPathsForRuleset(['*tests*', '*fixtures*'])
This is distinct from skipPaths() / skipPath(), which exclude files from all analysis.
The class collector now correctly detects and handles traits, so trait-defined dependencies are included in layer resolution and ruleset evaluation.
layerPattern() and ruleset() are additive — existing configs using layer() and individual rule() calls continue to work unchanged. Mix both styles freely within the same Architecture::define() chain.
Full Changelog: https://github.com/boundwize/structarmed/compare/0.4.5...0.5.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.4.4...0.4.5
How can I help you explore Laravel packages today?