Releases: boundwize/structarmed
Release list
Released: StructArmed 0.17.2
What's Changed
- refactor: Pair each class-like with its analysis instead of keying by spl_object_id() by @samsonasik in #421
- [yagni] fix: Skip PHPUnit with suffix Test classes in ExtendedClassMustBeAbstractOrInstantiatedRule by @samsonasik in #422
- Fix stray 0 in test print by @samsonasik in #423
Full Changelog: 0.17.1...0.17.2
Released: StructArmed 0.17.1
What's Changed
- perf: Compact FileAnalysis cache scalars into a positional list by @samsonasik in #414
- refactor: simplify worker payload merge in ParallelAnalysisNodeExtractor with array_push() by @samsonasik in #417
- perf: Split cache hydration across parallel workers by @samsonasik in #419
Full Changelog: 0.17.0...0.17.1
Released: StructArmed 0.17.0
StructArmed 0.17.0 expands architecture analysis beyond named classes.
This release introduces dedicated analysis nodes and rule interfaces for:
- named functions;
- closures and arrow functions;
- anonymous classes.
It also introduces the new PER Coding Style and Code Quality presets, expands the MVC and DDD presets, adds several fixable coding-style rules.
New Rule Interfaces
Three new interfaces allow custom rules to target a specific kind of PHP declaration:
Boundwize\StructArmed\Rule\FunctionRuleInterfaceBoundwize\StructArmed\Rule\AnonymousFunctionRuleInterfaceBoundwize\StructArmed\Rule\AnonymousClassRuleInterface
Each interface uses the same appliesTo() and evaluate() method names as the existing RuleInterface, but receives a node containing information specific to that declaration type.
| Interface | Node | Analyses |
|---|---|---|
FunctionRuleInterface |
FunctionNode |
Named functions |
AnonymousFunctionRuleInterface |
AnonymousFunctionNode |
Closures and arrow functions |
AnonymousClassRuleInterface |
AnonymousClassNode |
Anonymous classes |
These nodes expose information such as their source file, line, layer, dependencies, function calls, superglobal access, language constructs, parameters, return types, complexity, and line count.
Anonymous-function nodes additionally report whether the declaration:
- is a closure or arrow function;
- is already
static; - accesses
$this; - belongs to a named class or function.
Anonymous-class nodes include:
- their extended class and implemented interfaces;
- traits and members;
- transitive parent classes and interfaces;
- constructor parameter count;
- readonly status;
- whether empty constructor parentheses were written.
Named Function Rules
The new MustHaveReturnTypeFunctionRule requires named functions in a configured layer to declare a return type.
It is enabled for the MVC preset's Helper layer.
-function format_price(int $amount)
+function format_price(int $amount): string
{
return number_format($amount);
}This complements the existing method return-type rules: standalone helper functions can now be checked independently from class methods.
Closures And Arrow Functions
The new MustBeStaticAnonymousFunctionRule detects closures and arrow functions that do not access $this but have not been declared static.
-$activeUsers = array_filter($users, function (User $user): bool {
+$activeUsers = array_filter($users, static function (User $user): bool {
return $user->isActive();
});Arrow functions are supported as well:
-$ids = array_map(fn (User $user): int => $user->id, $users);
+$ids = array_map(static fn (User $user): int => $user->id, $users);Closures that read $this, directly or through a nested closure, are skipped because PHP does not allow $this inside a static closure.
This rule supports --fix.
Anonymous Class Analysis
Anonymous classes now have their own AnonymousClassNode representation and rule interface.
Their class members, dependencies, traits, readonly status, and parent hierarchy are collected just like those of named classes. Consequently, methods such as extendsClass() and implementsInterface() work across direct and transitive parents.
The new fixable AnonymousClassMayNotHaveEmptyParenthesesRule implements the PER convention that an anonymous class passing no constructor arguments should omit empty parentheses:
-$handler = new class () implements Handler {
+$handler = new class implements Handler {
public function handle(): void
{
}
};Parentheses containing actual constructor arguments are unaffected.
New PER Coding Style Preset
The new Preset::PER() implements additional rules from the PER Coding Style and includes the existing PSR-12 rules.
Enable it in structarmed.php:
return Architecture::define()
- ->withPresets(Preset::PSR4(), Preset::PSR12());
+ ->withPreset(Preset::PER());In addition to PSR-12, the PER preset checks the following conventions.
Enum Cases Must Use PascalCase
enum OrderStatus
{
- case pending_payment;
+ case PendingPayment;
}Enum Methods May Not Be Protected
Enums cannot be extended, so protected methods should be private:
enum OrderStatus
{
- protected function label(): string
+ private function label(): string
{
return $this->name;
}
}Enum Constants May Not Be Protected
enum OrderStatus
{
- protected const DEFAULT_LABEL = 'Unknown';
+ private const DEFAULT_LABEL = 'Unknown';
}Anonymous Classes May Not Have Empty Parentheses
-$object = new class () {};
+$object = new class {};The enum visibility and anonymous-class-parentheses rules support --fix.
Lowercase PHP Keyword Constants
The PSR-12 preset now includes the fixable MustUseLowercaseKeywordConstantRule.
It requires the PHP keyword constants true, false, and null to use their canonical lowercase spelling:
-$enabled = TRUE;
-$disabled = FALSE;
-$value = NULL;
+$enabled = true;
+$disabled = false;
+$value = null;Only the spelling is changed. For example, a fully qualified \TRUE becomes \true.
New Code Quality Preset
The new Preset::CODEQUALITY() provides readability rules that are independent of a particular architecture style or coding standard.
Enable it alongside other presets:
return Architecture::define()
->withPresets(
Preset::DDD(),
+ Preset::CODEQUALITY(),
);Anonymous Functions Must Be Static
Closures and arrow functions that do not use $this must be declared static.
-$names = array_map(fn (User $user) => $user->name, $users);
+$names = array_map(static fn (User $user) => $user->name, $users);Declaring these functions static makes it explicit that they do not capture the enclosing object.
Large Numeric Literals Must Use Separators
Plain decimal numeric literals of at least 1_000_000 must group their digits using _ separators:
-$maximumUploadSize = 10000000;
+$maximumUploadSize = 10_000_000;Decimal fractions retain their fractional portion:
-$amount = 1000500.75;
+$amount = 1_000_500.75;The default threshold can be customized by replacing the preset rule:
<?php
use Boundwize\StructArmed\Architecture;
use Boundwize\StructArmed\Preset\Preset;
use Boundwize\StructArmed\Preset\Presets\CodeQualityPreset;
use Boundwize\StructArmed\Rule\Rules\File\LargeNumericLiteralMustUseSeparatorRule;
return Architecture::define()
->withPreset(Preset::CODEQUALITY())
->replaceRule(
CodeQualityPreset::LARGE_NUMERIC_LITERALS_MUST_USE_SEPARATOR,
new LargeNumericLiteralMustUseSeparatorRule(minimum: 1_000),
);Both Code Quality rules support --fix.
DDD Preset: Prevent Infrastructure Inheritance
The new MayNotExtendClassRule prevents classes in a layer from extending a configured class, either directly or through a parent class.
The DDD preset uses it to prevent Domain classes from extending Doctrine's infrastructure-oriented EntityRepository:
namespace App\Domain\Repository;
-use Doctrine\ORM\EntityRepository;
-
-final class OrderRepository extends EntityRepository
+interface OrderRepository
{
}A custom rule can enforce the same boundary for another framework base class:
use Boundwize\StructArmed\Rule\Rules\Class_\MayNotExtendClassRule;
return Architecture::define()
->layer('Domain', 'src/Domain/')
->rule(
'domain.must_not_extend_eloquent_model',
new MayNotExtendClassRule(
layer: 'Domain',
class: 'Illuminate\Database\Eloquent\Model',
),
);Writing A Custom Function Rule
For example, the following rule prevents named functions in the Domain layer from reading PHP superglobals:
<?php
namespace App\Architecture\Rules;
use Boundwize\StructArmed\Analyser\FunctionNode;
use Boundwize\StructArmed\Rule\FunctionRuleInterface;
use Boundwize\StructArmed\Rule\RuleViolation;
use function sprintf;
final readonly class FunctionsMustNotAccessSuperglobalsRule implements FunctionRuleInterface
{
public function appliesTo(FunctionNode $functionNode): bool
{
return $functionNode->isInLayer('Domain');
}
public function evaluate(FunctionNode $functionNode): ?RuleViolation
{
if (! $functionNode->accessesSuperglobals()) {
return null;
}
return new RuleViolation(
message: sprintf(
'Function [%s()] must not access superglobals',
$functionNode->functionName,
),
file: $functionNode->file,
line: $functionNode->line,
className: $functionNode->functionName,
layer: $functionNode->layer,
functionName: $functionNode->functionName,
);
}
}Register it like any other rule:
return Architecture::define()
->layer('Domain', 'src/Domain/')
->rule(
'domain.functions_must_not_access_superglobals',
new FunctionsMustNotAccessSuperglobalsRule(),
);
...Released: StructArmed 0.16.31
What's Changed
- Fix: normalise reported composer.json path in PSR-4 rules for consistent violation display on Windows by @samsonasik in #381
Full Changelog: 0.16.30...0.16.31
Released: StructArmed 0.16.30
What's Changed
- dx: use 💡 icon in Hint for --fix usage on ConsoleReport by @samsonasik in #377
Full Changelog: 0.16.29...0.16.30
Released: StructArmed 0.16.29
What's Changed
- perf: Remove fflush call from WorkerProgressHandler::advance() method by @samsonasik in #372
- perf: Avoid redundant deduplication of paths already deduplicated by Psr4PathResolver::normalisePaths(). by @samsonasik in #373
- perf: directly append violations on RuleViolationCollection::merge() by @samsonasik in #374
Full Changelog: 0.16.28...0.16.29
Released: StructArmed 0.16.28
What's Changed
- perf: Reduce redundant key-scan passes when decoding cached nodes in AnalysisResultCache by @samsonasik in #362
Full Changelog: 0.16.27...0.16.28
Released: StructArmed 0.16.27
What's Changed
- perf: Single-pass source path matching in PhpFileFinder::filesFromScope() by @samsonasik in #361
Full Changelog: 0.16.26...0.16.27
Released: StructArmed 0.16.26
What's Changed
- chore: Clean up no longer used method sourcePathsFor() on Psr4SourcePathsRule by @samsonasik in #357
- chore: Enable pcov coverage only on the PHP 8.2 ubuntu job via matrix include by @samsonasik in #358
- fix: Make implementsInterface() cover interfaceExtends and drop unused extendsInterface() by @samsonasik in #359
- comment by @samsonasik in #360
Full Changelog: 0.16.25...0.16.26
Released: StructArmed 0.16.25
What's Changed
- chore: clean up rector skip config by @samsonasik in #354
- perf: reduce FileAnalysisProvider overhead per file by @samsonasik in #355
- fix: Psr4NamespaceRule misses PSR-4 mappings pointing outside the project directory by @samsonasik in #356
Full Changelog: 0.16.24...0.16.25