Available Rules

StructArmed ships rule classes you can register directly in structarmed.php, replace inside presets, or reuse from custom presets.

Contents

  1. Using Built-In Rules Directly
  2. Composer And PSR-4 Rules
  3. File Rules
  4. Class Rules
  5. Fixable Rules
  6. Function Rules
  7. Layer Rules
  8. Method Rules
  9. 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.
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) are skipped, since a static closure cannot access $this. Supports --fix by adding the static modifier.
MustHaveReturnTypeFunctionRule new MustHaveReturnTypeFunctionRule(layer: 'Helper') Named function declarations in a layer declare a return type.

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.
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.

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.