Skip to content

Releases: boundwize/structarmed

Released: StructArmed 0.17.2

Choose a tag to compare

@samsonasik samsonasik released this 07 Sep 14:35
0.17.2
4c9b8d1

ci build PHPStan

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

Choose a tag to compare

@samsonasik samsonasik released this 06 Sep 06:13
0.17.1
b5bea9d

ci build PHPStan

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

Choose a tag to compare

@samsonasik samsonasik released this 04 Sep 02:28
0.17.0
5536c6d

ci build PHPStan

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\FunctionRuleInterface
  • Boundwize\StructArmed\Rule\AnonymousFunctionRuleInterface
  • Boundwize\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(),
    );
...
Read more

Released: StructArmed 0.16.31

Choose a tag to compare

@samsonasik samsonasik released this 31 Aug 15:53
0.16.31
a23c7cb

ci build PHPStan

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

Choose a tag to compare

@samsonasik samsonasik released this 31 Aug 03:07
0.16.30
4539690

ci build PHPStan

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

Choose a tag to compare

@samsonasik samsonasik released this 31 Aug 02:29
0.16.29
320f2da

ci build PHPStan

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

Choose a tag to compare

@samsonasik samsonasik released this 28 Aug 12:07
0.16.28
e21583d

ci build PHPStan

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

Choose a tag to compare

@samsonasik samsonasik released this 28 Aug 02:19
0.16.27
6ab6e7c

ci build PHPStan

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

Choose a tag to compare

@samsonasik samsonasik released this 27 Aug 10:31
0.16.26
53ec54a

ci build PHPStan

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

Choose a tag to compare

@samsonasik samsonasik released this 27 Aug 01:41
0.16.25
7fad2f4

ci build PHPStan

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