Skip to content

Released: StructArmed 0.18.0

Choose a tag to compare

@samsonasik samsonasik released this 24 Sep 03:53
· 123 commits to main since this release
0.18.0
f2728fe

ci build PHPStan

StructArmed 0.18.0 improves CI integration and expands YAGNI analysis to named functions.

This release introduces:

  • GitHub Actions annotations with the new --report=github;
  • path exclusions for Architecture::layer();
  • better control over the PHPUnit extension in CI;
  • Boundwize\StructArmed\Rule\UsedFunctionAwareRuleInterface and the new fixable MustBeUsedFunctionRule.

GitHub Actions Report

StructArmed can now report violations directly as GitHub Actions annotations:

vendor/bin/structarmed analyse --report=github

Each violation is emitted as a GitHub workflow error annotation, so violations appear directly on affected files and lines in pull requests.

StructArmed GitHub report annotations

The regular console report is appended after the annotations, so View details still opens a readable StructArmed report in the job log.

For example:

- name: Run StructArmed
  run: vendor/bin/structarmed analyse --report=github

The GitHub report intentionally exits with 0, allowing violations to be surfaced as annotations without failing the job itself.

Use the regular console or json report when violations should fail the job.

The github and json reports also disable the progress bar automatically.

Exclude Paths From Layers

Architecture::layer() now supports excluding one or more nested paths from a layer.

For example:

return Architecture::define()
    ->layer(
        'Logger',
        'src/Logger/',
        excludePath: 'src/Logger/Factory/',
    )
    ->layer('Factory', 'src/Logger/Factory/');

Files under src/Logger/Factory/ now belong to Factory without also belonging to Logger.

Multiple paths can be excluded:

->layer(
    'Logger',
    'src/Logger/',
    excludePath: [
        'src/Logger/Factory/',
        'src/Logger/Formatter/',
    ],
)

The exclusion only applies to that particular layer. A file may still resolve to another matching path layer, namespace-pattern layer, or the automatically registered Source layer.

This mirrors the existing exclusion support in layerPattern() and makes nested architecture boundaries easier to express without changing the directory structure.

YAGNI For Named Functions

The YAGNI preset can now detect unused named functions.

A new rule:

MustBeUsedFunctionRule

checks whether a function is called or otherwise referenced within the scanned code.

For example:

-function legacy_helper(): string
-{
-    return 'unused';
-}

The rule understands usages including:

  • regular function calls;
  • first-class callable references;
  • function-name strings such as 'App\helper';
  • calls from top-level code.

A function that only recursively calls itself still counts as unused.

MustBeUsedFunctionRule supports --fix, removing the unused function automatically. It can also clean up an empty surrounding if (! function_exists(...)) guard and remove the file when only boilerplate remains.

The rule is enabled automatically by:

Preset::YAGNI()

As with the other YAGNI rules, only usages inside the scanned paths are known. Functions intended as public extension points for external consumers can be excluded with skipRule() or skip paths.

UsedFunctionAwareRuleInterface

Custom function rules can opt into function-usage analysis through the new:

Boundwize\StructArmed\Rule\UsedFunctionAwareRuleInterface

It extends FunctionRuleInterface and makes the usage state available through:

$functionNode->isReferenced

For example:

final readonly class CustomRule implements UsedFunctionAwareRuleInterface
{
    public function appliesTo(FunctionNode $functionNode): bool
    {
        return $functionNode->isInLayer('Source');
    }

    public function evaluate(FunctionNode $functionNode): ?RuleViolation
    {
        if ($functionNode->isReferenced) {
            return null;
        }

        // ...
    }
}

Usage analysis is opt-in: StructArmed performs the additional work only when at least one active rule implements UsedFunctionAwareRuleInterface.

PHPUnit Extension Controls

The PHPUnit extension is now easier to control in CI.

Disable StructArmed For One PHPUnit Run

Set:

STRUCTARMED_DISABLED=1 vendor/bin/phpunit

StructArmed remains configured in phpunit.xml, but the extension skips architecture analysis for that run.

This is useful when StructArmed already runs as a dedicated CI step.

Disable Progress Output

PHPUnit's own --no-progress now also disables the StructArmed progress bar:

vendor/bin/phpunit --no-progress

To disable only StructArmed's progress output while preserving PHPUnit's progress display:

<extensions>
    <bootstrap class="Boundwize\StructArmed\PHPUnit\StructArmedExtension">
        <parameter name="progress" value="false"/>
    </bootstrap>
</extensions>

Dependency Update

StructArmed now requires:

"nikic/php-parser": "^5.9"

What's Changed

  • feat: Add GithubReport for Github output with --report=github in #445
  • Bump nikic/php-parser version to ^5.9 in #448
  • feat: Support excludePath in Architecture::layer() in #450
  • feat: Append console report to github report output and exit 0 in #458
  • feat: Add STRUCTARMED_DISABLED env to disable the PHPUnit extension in #459
  • feat: Allow disabling progress bar in PHPUnit extension in #460
  • feat: Add UsedFunctionAwareRuleInterface and MustBeUsedFunctionRule for YAGNI preset in #462
  • Handle top-level function usage in #463

Full Changelog: 0.17.12...0.18.0

What's Changed

  • feat: Add GithubReport for Github output with --report=github by @samsonasik in #445
  • Bump nikic/php-parser version to ^5.9 by @samsonasik in #448
  • feat: Support excludePath in Architecture::layer() by @samsonasik in #450
  • feat: Append console report to github report output and exit 0 by @samsonasik in #458
  • feat: Add STRUCTARMED_DISABLED env to disable the PHPUnit extension by @samsonasik in #459
  • feat: Allow disabling progress bar in PHPUnit extension: StructArmedExtension via progress config or PHPUnit --no-progress by @samsonasik in #460
  • feat: Add UsedFunctionAwareRuleInterface and MustBeUsedFunctionRule for YAGNI preset by @samsonasik in #462
  • handle top level function usage by @samsonasik in #463
  • Prepare for 0.18.0 by @samsonasik in #447

Full Changelog: 0.17.12...0.18.0