Available Rules
StructArmed ships rule classes you can register directly in structarmed.php, replace inside presets, or reuse from custom presets.
Contents
- Using Built-In Rules Directly
- Composer And PSR-4 Rules
- File Rules
- Class Rules
- Fixable Rules
- Function Rules
- Layer Rules
- Method Rules
- Usage Rules
Using Built-In Rules Directly
Register built-in rules with rule() and give each rule a stable project-specific key.
<?php
use Boundwize\StructArmed\Architecture;
use Boundwize\StructArmed\Rule\Rules\Class_\MustBeFinalRule;
use Boundwize\StructArmed\Rule\Rules\Usage\MayNotUseClassRule;
return Architecture::define()
->layer('Domain', 'src/Domain/')
->rule(
'domain.entities_must_be_final',
new MustBeFinalRule(layer: 'Domain', classNamePattern: '/Entity$/')
)
->rule(
'domain.must_not_use_datetime',
new MayNotUseClassRule(layer: 'Domain', forbiddenClass: DateTime::class)
);
Class, method, layer, and usage rules evaluate classes already assigned to layers. Composer and file rules evaluate project-level structure, such as composer.json PSR-4 mappings or source file contents.
Composer And PSR-4 Rules
Namespace: Boundwize\StructArmed\Rule\Rules\Composer.
| Rule | Constructor | Checks |
|---|---|---|
Psr4DirectoryExistsRule |
new Psr4DirectoryExistsRule() |
composer.json exists, is valid JSON, and every PSR-4 source path exists on disk. Supports --fix by removing mappings for missing directories. |
Psr4EmptyNamespacePrefixRule |
new Psr4EmptyNamespacePrefixRule() |
autoload and autoload-dev PSR-4 mappings do not use an empty namespace prefix. |
Psr4NamespaceRule |
new Psr4NamespaceRule(layer: 'Source') |
A class name matches the namespace expected from its PSR-4 path. |
Psr4RootPathRule |
new Psr4RootPathRule() |
PSR-4 mappings do not point directly to the project root. |
Psr4SourcePathsRule |
new Psr4SourcePathsRule(sourcePaths: ['src/', 'tests/']) |
Configured source paths are present in composer.json PSR-4 mappings. |
File Rules
Namespace: Boundwize\StructArmed\Rule\Rules\File.
| Rule | Constructor | Checks |
|---|---|---|
Psr1PhpTagsRule |
new Psr1PhpTagsRule(sourcePaths: ['src/']) |
PHP files use only <?php and <?= tags. Supports --fix. |
Psr1SymbolsOrSideEffectsRule |
new Psr1SymbolsOrSideEffectsRule(sourcePaths: ['src/']) |
A file declares symbols or causes side effects, but does not do both. |
Psr1ValidUtf8Rule |
new Psr1ValidUtf8Rule(sourcePaths: ['src/']) |
PHP files use valid UTF-8 encoding. |
Psr1Utf8WithoutBomRule |
new Psr1Utf8WithoutBomRule(sourcePaths: ['src/']) |
PHP files do not start with a byte order mark. Supports --fix. |
MustUseLowercaseKeywordConstantRule |
new MustUseLowercaseKeywordConstantRule(sourcePaths: ['src/']) |
PHP’s special keyword constants true, false, and null use their canonical lowercase spelling. Fully qualified forms such as \TRUE are preserved as \true. Supports --fix. |
LargeNumericLiteralMustUseSeparatorRule |
new LargeNumericLiteralMustUseSeparatorRule(minimum: 1_000_000, sourcePaths: ['src/']) |
Plain decimal integer and float literals whose magnitude is at least minimum (default 1_000_000) group their integer digits in threes with _ separators, so 1000500.001 becomes 1_000_500.001. Hexadecimal, octal, binary, exponent, and already separated literals are ignored. Supports --fix. |
Pass sourcePaths: null or omit it to let the rule read PSR-4 paths from composer.json.
Class Rules
Namespace: Boundwize\StructArmed\Rule\Rules\Class_.
| Rule | Constructor | Checks |
|---|---|---|
AnonymousClassMayNotHaveEmptyParenthesesRule |
new AnonymousClassMayNotHaveEmptyParenthesesRule(layer: 'Source') |
Anonymous classes that pass no constructor argument omit the parentheses after class (new class {}, not new class () {}), per PER Coding Style. Supports --fix by removing the empty parentheses. |
ClassConstantNameMustBeUpperCaseRule |
new ClassConstantNameMustBeUpperCaseRule(layer: 'Domain') |
Class, interface, and trait constants use upper case with underscore separators. Enums are skipped (PER Coding Style recommends PascalCase enum constants). |
ClassImplementingInterfaceMustHaveSuffixRule |
new ClassImplementingInterfaceMustHaveSuffixRule(layer: 'HTTP', interface: MiddlewareInterface::class, suffix: 'Middleware') |
Classes implementing a specific interface use the required suffix. |
ClassNameMustBeStudlyCapsRule |
new ClassNameMustBeStudlyCapsRule(layer: 'Source') |
Class names use StudlyCaps. |
ClassNameMustHaveSuffixRule |
new ClassNameMustHaveSuffixRule(layer: 'Controller', suffix: 'Controller') |
Classes in a layer have the required suffix. |
ClassNameMustNotHavePrefixRule |
new ClassNameMustNotHavePrefixRule(layer: 'Model', prefix: 'Model') |
Classes in a layer do not use a forbidden prefix. |
EnumCaseNameMustBePascalCaseRule |
new EnumCaseNameMustBePascalCaseRule(layer: 'Source') |
Enum case names use PascalCase, per PER Coding Style. |
EnumConstantMayNotBeProtectedRule |
new EnumConstantMayNotBeProtectedRule(layer: 'Source') |
Enum constants are not declared protected — enums cannot be extended, so private is used instead, per PER Coding Style. Supports --fix by changing protected to private. |
EnumMethodMayNotBeProtectedRule |
new EnumMethodMayNotBeProtectedRule(layer: 'Source') |
Enum methods are not declared protected — enums cannot be extended, so private is used instead, per PER Coding Style. Supports --fix by changing protected to private. |
ExtendedClassMustBeAbstractOrInstantiatedRule |
new ExtendedClassMustBeAbstractOrInstantiatedRule(layer: 'Source') |
Classes another scanned class extends are declared abstract unless they are also instantiated (new X, a new self/new static/new parent resolving to them, a constant class expression such as new (X::class) or new ('App\X'), or a chained (new ReflectionClass(X::class))->newInstance*()). Type hints, instanceof, and ::class keep working on an abstract class, so they do not count. Runtime-fed construction (new $class from a parameter, unserialize(), container factories) is outside the scanned-code boundary — exclude such factories’ targets with rule-scoped skip() or skipRule(). *Test classes extending PHPUnit\Framework\TestCase are skipped: the PHPUnit runner instantiates them, so a test another test extends must stay concrete (*TestCase base classes are still checked). Supports --fix by adding the abstract modifier. |
MaxDependencyCountRule |
new MaxDependencyCountRule(layer: 'Controller', maxCount: 5) |
Constructor dependency count stays below the configured limit. |
MayNotExtendClassRule |
new MayNotExtendClassRule(layer: 'Domain', class: 'Illuminate\\Database\\Eloquent\\Model') |
Classes in a layer do not extend a forbidden class, directly or through any parent class. |
MayNotImplementInterfaceRule |
new MayNotImplementInterfaceRule(layer: 'Domain', interface: JsonSerializable::class) |
Classes in a layer do not implement a forbidden interface. |
MustBeFinalRule |
new MustBeFinalRule(layer: 'Domain', classNamePattern: '/Entity$/') |
Matching classes in a layer are declared final. Classes extended by another scanned class are skipped (making them final would break the child). Supports --fix. |
MustBeUsedInterfaceRule |
new MustBeUsedInterfaceRule(layer: 'Source') |
Interfaces are implemented by a scanned class (directly or through inheritance), extended by another scanned interface, or referenced as a dependency (type hint, instanceof, ::class, a class-name string, …). Supports --fix by removing the unused interface (and deleting its file when only boilerplate remains). |
MustBeInterfaceRule |
new MustBeInterfaceRule(layer: 'Contract', classNamePattern: '/Interface$/') |
Matching declarations in a layer are interfaces. |
MustBeUsedAbstractClassRule |
new MustBeUsedAbstractClassRule(layer: 'Source') |
Abstract classes are extended by a scanned class or referenced as a dependency (type hint, instanceof, ::class, static call, a class-name string, …). Supports --fix by removing the unused abstract class (and deleting its file when only boilerplate remains). |
MustBeUsedTraitRule |
new MustBeUsedTraitRule(layer: 'Source') |
Traits are used by a scanned class, trait, or enum, or referenced as a dependency (::class, static call, a class-name string, …). Supports --fix by removing the unused trait (and deleting its file when only boilerplate remains). |
MustDeclareConstantVisibilityRule |
new MustDeclareConstantVisibilityRule(layer: 'Source') |
Class constants declare public, protected, or private. Supports --fix. |
MustDeclareMethodVisibilityRule |
new MustDeclareMethodVisibilityRule(layer: 'Source') |
Methods declare public, protected, or private. Supports --fix. |
MustDeclarePropertyVisibilityRule |
new MustDeclarePropertyVisibilityRule(layer: 'Source') |
Properties declare public, protected, or private. Supports --fix. |
MustImplementInterfaceRule |
new MustImplementInterfaceRule(layer: 'HTTP', interface: RequestHandlerInterface::class, classNamePattern: '/Handler$/') |
Matching classes implement a required interface. |
NamingConventionRule |
new NamingConventionRule(classNamePattern: '/Repository$/', mustBeInLayer: 'Infrastructure') |
Classes matching a name pattern live in the expected layer. |
classNamePattern and excludePattern are regular expressions matched against the fully-qualified class name.
Fixable Rules
The following rules implement Boundwize\StructArmed\Rule\FixableInterface and can apply their changes when you run vendor/bin/structarmed analyse --fix.
| Rule | Automatic fix |
|---|---|
Psr4DirectoryExistsRule |
Removes PSR-4 mappings for missing directories. |
Psr1PhpTagsRule |
Normalizes invalid PHP opening tags. |
Psr1Utf8WithoutBomRule |
Removes the UTF-8 byte order mark. |
MustUseLowercaseKeywordConstantRule |
Lowercases TRUE, FALSE, and NULL keyword constants. |
LargeNumericLiteralMustUseSeparatorRule |
Adds _ separators to large numeric literals. |
AnonymousClassMayNotHaveEmptyParenthesesRule |
Removes empty parentheses from anonymous classes that pass no constructor arguments. |
ExtendedClassMustBeAbstractOrInstantiatedRule |
Adds the abstract modifier to an extended class that is not instantiated. |
MustBeFinalRule |
Adds the final modifier. |
MustBeUsedInterfaceRule |
Removes an unused interface, deleting its file when only boilerplate remains. |
MustBeUsedAbstractClassRule |
Removes an unused abstract class, deleting its file when only boilerplate remains. |
MustBeUsedTraitRule |
Removes an unused trait, deleting its file when only boilerplate remains. |
MustBeUsedFunctionRule |
Removes an unused function and the if (! function_exists(...)) guard it leaves empty, deleting its file when only boilerplate remains. |
MustDeclareConstantVisibilityRule |
Adds a missing constant visibility modifier. |
MustDeclareMethodVisibilityRule |
Adds a missing method visibility modifier. |
MustDeclarePropertyVisibilityRule |
Adds a missing property visibility modifier. |
Function Rules
Namespace: Boundwize\StructArmed\Rule\Rules\Function_.
| Rule | Constructor | Checks |
|---|---|---|
MustBeStaticAnonymousFunctionRule |
new MustBeStaticAnonymousFunctionRule(layer: 'Domain') |
Closures and arrow functions in a layer are declared static. Anonymous functions that read $this (directly or through a nested closure), or are directly object-bound with Closure::bind(), bindTo(), or call(), are skipped. Supports --fix by adding the static modifier. |
MustBeUsedFunctionRule |
new MustBeUsedFunctionRule(layer: 'Source', functionNamePattern: '/_helper$/') |
Named functions (optionally only those matching functionNamePattern) are called by a scanned scope (including as a first-class callable helper(...)) or referenced by a function-name string such as 'App\helper'. A function calling only itself counts as unused. Supports --fix by removing the unused function, along with an if (! function_exists(...)) guard left with an empty block and no else/elseif (and deleting its file when only boilerplate remains). |
MustHaveReturnTypeFunctionRule |
new MustHaveReturnTypeFunctionRule(layer: 'Helper') |
Named function declarations in a layer declare a return type. |
functionNamePattern is an optional regular expression matched against the fully-qualified function name.
Layer Rules
Namespace: Boundwize\StructArmed\Rule\Rules\Layer.
| Rule | Constructor | Checks |
|---|---|---|
MayNotDependOnRule |
new MayNotDependOnRule(from: 'Domain', to: 'Infrastructure', toPath: 'Infrastructure') |
Classes in one layer do not depend on classes from a forbidden layer. |
toPath is optional. It is only needed when the forbidden layer name differs from the namespace or path segment that should be matched.
Method Rules
Namespace: Boundwize\StructArmed\Rule\Rules\Method.
| Rule | Constructor | Checks |
|---|---|---|
MaxCyclomaticComplexityRule |
new MaxCyclomaticComplexityRule(layer: 'Controller', maxComplexity: 5) |
Method cyclomatic complexity stays below the configured limit. |
MaxMethodLengthRule |
new MaxMethodLengthRule(layer: 'Controller', maxLines: 30) |
Method line count stays below the configured limit. |
MethodNameMustBeCamelCaseRule |
new MethodNameMustBeCamelCaseRule(layer: 'Source') |
Method names use camelCase. Magic methods are ignored. |
MustHaveReturnTypeRule |
new MustHaveReturnTypeRule(layer: 'Domain', classNamePattern: '/Service$/') |
Public non-constructor methods declare a return type. |
Usage Rules
Namespace: Boundwize\StructArmed\Rule\Rules\Usage.
| Rule | Constructor | Checks |
|---|---|---|
MayNotCallFunctionRule |
new MayNotCallFunctionRule(layer: 'Domain', function: 'header') |
Classes in a layer do not call a forbidden function. |
MayNotUseClassRule |
new MayNotUseClassRule(layer: 'Domain', forbiddenClass: DateTime::class) |
Classes in a layer do not depend on a forbidden class. |
MayNotUseConstantRule |
new MayNotUseConstantRule(layer: 'Domain', constant: 'PHP_EOL') |
Classes in a layer do not use a forbidden constant. |
MayNotUseLanguageConstructRule |
new MayNotUseLanguageConstructRule(layer: 'Domain', construct: 'echo') |
Classes in a layer do not use a forbidden language construct. |
MayNotUseNamespaceRule |
new MayNotUseNamespaceRule(layer: 'Domain', forbiddenNamespace: 'Doctrine\\ORM\\') |
Classes in a layer do not depend on a forbidden namespace. |
MayNotUseSuperglobalsRule |
new MayNotUseSuperglobalsRule(layer: 'Controller') |
Classes in a layer do not access superglobals directly. |
MayNotUseClassRule and MayNotUseNamespaceRule also accept classNamePattern when only matching classes should be checked.
MayNotUseConstantRule takes a global or namespaced constant name, such as 'PHP_EOL' or 'Vendor\\Config\\DEBUG'. An unqualified constant inside a namespace counts as both the constant of that namespace and the global constant of that name, as PHP only resolves it at runtime.
MayNotUseLanguageConstructRule accepts one of the following construct names: echo, print, eval, isset, empty, unset, list, exit, die, include, include_once, require, require_once. die is a pure alias of exit, so banning either spelling catches both. The include / include_once / require / require_once constructs are distinct and are matched exactly.