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.
Full Changelog: https://github.com/boundwize/structarmed/compare/0.19.4...0.19.5
Full Changelog: https://github.com/boundwize/structarmed/compare/0.18.9...0.18.10
Full Changelog: https://github.com/boundwize/structarmed/compare/0.16.5...0.16.6
Full Changelog: https://github.com/boundwize/structarmed/compare/0.16.4...0.16.5
Full Changelog: https://github.com/boundwize/structarmed/compare/0.16.3...0.16.4
Full Changelog: https://github.com/boundwize/structarmed/compare/0.16.2...0.16.3
Full Changelog: https://github.com/boundwize/structarmed/compare/0.16.1...0.16.2
Full Changelog: https://github.com/boundwize/structarmed/compare/0.16.0...0.16.1
This release introduces 2 new interfaces:
Boundwize\StructArmed\Rule\UsedInterfaceAwareRuleInterfaceBoundwize\StructArmed\Rule\UsedTraitAwareRuleInterfaceThey complete the existing Boundwize\StructArmed\Rule\ExtendedClassAwareRuleInterface, which was introduced in 0.15.0.
These interfaces are used by the new YAGNI preset to detect abstractions that are not actually used by the scanned codebase.
The preset checks for:
Boundwize\StructArmed\Rule\Rules\Class_\MustBeUsedInterfaceRuleBoundwize\StructArmed\Rule\Rules\Class_\MustBeInterfaceRuleBoundwize\StructArmed\Rule\Rules\Class_\MustBeUsedTraitRuleBoundwize\StructArmed\Rule\Rules\Class_\ExtendedClassMustBeAbstractOrInstantiatedRuleFor the last case, the class can usually be made abstract.
-class SomeClassOnlyExtended
+abstract class SomeClassOnlyExtended
Usage detection also takes references into account, including type hints, instanceof, ::class, static calls, and class-name strings.
All YAGNI rules support --fix. Unused declarations can be removed, while classes that are only extended can be changed to abstract.
You can define YAGNI preset:
<?php
declare(strict_types=1);
use Boundwize\StructArmed\Architecture;
use Boundwize\StructArmed\Preset\Preset;
use Boundwize\StructArmed\Preset\Presets\Psr1Preset;
use Boundwize\StructArmed\Rule\Rules\Class_\MustBeFinalRule;
return Architecture::define()
// ... other config ...
- ->withPresets(Preset::PSR4(), Preset::PSR15());
+ ->withPresets(Preset::PSR4(), Preset::PSR15(), Preset::YAGNI());
The new interfaces are marker interfaces that custom rules can implement instead of the plain Boundwize\StructArmed\Rule\RuleInterface (each marker extends it, so no other change is needed). Collecting usage information costs extra analysis time, so the analyser only computes it when an active rule declares that it needs it via one of these markers:
| Marker interface | Flags populated on ClassNode |
|---|---|
Boundwize\StructArmed\Rule\ExtendedClassAwareRuleInterface |
$isExtended, $isReferenced, $isInstantiated |
Boundwize\StructArmed\Rule\UsedInterfaceAwareRuleInterface |
$isImplemented, $isReferenced |
Boundwize\StructArmed\Rule\UsedTraitAwareRuleInterface |
$isReferenced |
Without a matching marker on at least one active rule, the corresponding flags keep their default false.
For example, a custom rule that requires every interface in a Contracts layer to actually be implemented or referenced:
<?php
namespace App\Architecture\Rules;
use Boundwize\StructArmed\Analyser\ClassNode;
use Boundwize\StructArmed\Rule\RuleViolation;
use Boundwize\StructArmed\Rule\UsedInterfaceAwareRuleInterface;
use function sprintf;
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,
);
}
}
The built-in YAGNI preset rules follow this same pattern: MustBeUsedInterfaceRule implements UsedInterfaceAwareRuleInterface, MustBeUsedTraitRule implements UsedTraitAwareRuleInterface, and MustBeUsedAbstractClassRule and ExtendedClassMustBeAbstractOrInstantiatedRule implement ExtendedClassAwareRuleInterface.
See the Reading Usage Flags In A Custom Rule documentation for more details.
YAGNI only knows about usage inside the scanned paths. If a library intentionally exposes interfaces or extension points for external consumers, the relevant rules or paths should be skipped.
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.44...0.16.0
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.43...0.15.44
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.42...0.15.43
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.41...0.15.42
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.39...0.15.40
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.38...0.15.39
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.37...0.15.38
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.36...0.15.37
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.35...0.15.36
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.34...0.15.35
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.33...0.15.34
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.32...0.15.33
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.31...0.15.32
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.30...0.15.31
array_keys() when iterating worker file sizes by @samsonasik in https://github.com/boundwize/structarmed/pull/291Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.29...0.15.30
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.28...0.15.29
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.27...0.15.28
defined()||define() short-circuits as symbol declarations, not side effects by @samsonasik in https://github.com/boundwize/structarmed/pull/285Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.26...0.15.27
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.25...0.15.26
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.24...0.15.25
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.23...0.15.24
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.22...0.15.23
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.21...0.15.22
Full Changelog: https://github.com/boundwize/structarmed/compare/0.15.20...0.15.21
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?