Skip to content

feat: invalidate caches when Architecture changes - #521

Closed
NickSdot wants to merge 6 commits into
boundwize:0.20.xfrom
NickSdot:feat/cache-fingerprinting
Closed

NickSdot wants to merge 6 commits into
boundwize:0.20.xfrom
NickSdot:feat/cache-fingerprinting

Conversation

@NickSdot

Copy link
Copy Markdown

Hey!

What

Cache invalidation currently relies on the config file being edited. That is missing changes to config from other inputs. This PR instead fingerprints the evaluated Architecture.

structarmed.php can require layer definitions from another PHP file. Changes in required files leave structarmed.php and hence cache identity unchanged. This means StructArmed can reuse caches in situations where the config had external changes.

B/C: since this changes cache identity, existing caches will miss on first run after updating.

Why

You might recall from our Twitter interaction that I am working in a modular monolith -- think of it as many Composer packages in one repo; each with its own rules. The config API doesn't really scale with that, so I've built a wrapper on top of StructArmed. Although I was able to work around the issue I think it's good to fix it in StructArmed itself.


* implementation by Codex

@samsonasik

Copy link
Copy Markdown
Member

@NickSdot could you target to 0.20.x branch? Also fix phpstan and re-run rector and cs, eg:

composer phpstan
composer rector
composer cs-fix

Comment thread src/Cache/ConfigurationFingerprint.php Resolved
@NickSdot
NickSdot force-pushed the feat/cache-fingerprinting branch from 35c7e1f to 2850343 Compare October 29, 2026 13:08
@NickSdot
NickSdot changed the base branch from main to 0.20.x October 29, 2026 13:09
@codecov

codecov Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.65625% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/Cache/ConfigurationFingerprint.php 97.29% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@NickSdot
NickSdot force-pushed the feat/cache-fingerprinting branch from 2850343 to 725bb3d Compare October 29, 2026 13:13
@samsonasik

Copy link
Copy Markdown
Member

@NickSdot could you add more test to make 100% test coverage? Use e2e test on tests/Analyser/AnalyserTest.php may help to demonstrate the use case.

@samsonasik

samsonasik commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

@NickSdot needs another 3% coverage test: 97.56% of diff hit (target 100.00%

@samsonasik
samsonasik requested a max review from Copilot October 29, 2026 13:33
@samsonasik

Copy link
Copy Markdown
Member

@NickSdot tried locally, needs another 1% to cover 100%

➜  structarmed git:(feat/cache-fingerprinting) vendor/bin/phpunit --coverage-text
Analyzing [============================] 100% 293/293

StructArmed dev-main — Architecture Enforcement
=================================================

✅  No violations found. (0.51s)

PHPUnit 11.5.57 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.4.25 with Xdebug 3.5.0
Configuration: /Users/samsonasik/www/boundwize/structarmed/phpunit.xml

.............................................................   61 / 1778 (  3%)
.............................................................  122 / 1778 (  6%)
.............................................................  183 / 1778 ( 10%)
.............................................................  244 / 1778 ( 13%)
.............................................................  305 / 1778 ( 17%)
.............................................................  366 / 1778 ( 20%)
.............................................................  427 / 1778 ( 24%)
.............................................................  488 / 1778 ( 27%)
.............................................................  549 / 1778 ( 30%)
.............................................................  610 / 1778 ( 34%)
.............................................................  671 / 1778 ( 37%)
.............................................................  732 / 1778 ( 41%)
.............................................................  793 / 1778 ( 44%)
.............................................................  854 / 1778 ( 48%)
.............................................................  915 / 1778 ( 51%)
.............................................................  976 / 1778 ( 54%)
............................................................. 1037 / 1778 ( 58%)
............................................................. 1098 / 1778 ( 61%)
............................................................. 1159 / 1778 ( 65%)
............................................................. 1220 / 1778 ( 68%)
............................................................. 1281 / 1778 ( 72%)
............................................................. 1342 / 1778 ( 75%)
............................................................. 1403 / 1778 ( 78%)
............................................................. 1464 / 1778 ( 82%)
............................................................. 1525 / 1778 ( 85%)
............................................................. 1586 / 1778 ( 89%)
............................................................. 1647 / 1778 ( 92%)
............................................................. 1708 / 1778 ( 96%)
............................................................. 1769 / 1778 ( 99%)
.........                                                     1778 / 1778 (100%)
=========
Pyrameter
=========

Shape:  Healthy Pyramid
Result: Passed ✓

               ▲  E2E              -
             ▄▄▄▄▄  Functional     -
           ▄▄▄▄▄▄▄▄▄  Integration  ✓
         ▄▄▄▄▄▄▄▄▄▄▄▄▄  Unit       ✓

+=============+=======+========+============+
|    KIND     | TESTS | ACTUAL |   TARGET   |
+=============+=======+========+============+
| Unit        |   949 |  53.4% | >= 50.0% ✓ |
+-------------+-------+--------+------------+
| Integration |   829 |  46.6% | <= 50.0% ✓ |
+-------------+-------+--------+------------+
| Functional  |     0 |   0.0% |  No target |
+-------------+-------+--------+------------+
| E2E         |     0 |   0.0% |  No target |
+-------------+-------+--------+------------+

Total: 1778 tests

Your test pyramid target passed.


Time: 00:46.113, Memory: 70.00 MB

OK (1778 tests, 4595 assertions)


Code Coverage Report:        
  2026-10-29 13:36:33        
                             
 Summary:                    
  Classes: 99.30% (141/142)  
  Methods: 99.86% (693/694)  
  Lines:   99.95% (6344/6347)

Boundwize\StructArmed\Analyser\Analyser
  Methods: 100.00% (33/33)   Lines: 100.00% (738/738)
Boundwize\StructArmed\Analyser\AnalyserOptions
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  8/  8)
Boundwize\StructArmed\Analyser\AnalysisNodeCollector
  Methods: 100.00% (56/56)   Lines: 100.00% (676/676)
Boundwize\StructArmed\Analyser\AnalysisNodeExtractor
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (104/104)
Boundwize\StructArmed\Analyser\AnonymousClassNode
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  2/  2)
Boundwize\StructArmed\Analyser\AnonymousFunctionNode
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  3/  3)
Boundwize\StructArmed\Analyser\ClassLikeAnalysis
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
Boundwize\StructArmed\Analyser\ClassNode
  Methods: 100.00% (13/13)   Lines: 100.00% ( 38/ 38)
Boundwize\StructArmed\Analyser\ConstantNode
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
Boundwize\StructArmed\Analyser\EnumCaseNode
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  2/  2)
Boundwize\StructArmed\Analyser\ExtractionResult
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 10/ 10)
Boundwize\StructArmed\Analyser\FileAnalysis
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
Boundwize\StructArmed\Analyser\FileAnalysisProvider
  Methods: 100.00% (27/27)   Lines: 100.00% (243/243)
Boundwize\StructArmed\Analyser\FunctionLikeAnalysis
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
Boundwize\StructArmed\Analyser\FunctionNode
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  7/  7)
Boundwize\StructArmed\Analyser\MemberQueryTrait
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  4/  4)
Boundwize\StructArmed\Analyser\MethodNode
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  4/  4)
Boundwize\StructArmed\Analyser\NameQueryTrait
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  2/  2)
Boundwize\StructArmed\Analyser\NodeQueryTrait
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 26/ 26)
Boundwize\StructArmed\Analyser\Parallel\AnalysisNodeWorker
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 48/ 48)
Boundwize\StructArmed\Analyser\Parallel\ParallelAnalysisNodeExtractor
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% (185/185)
Boundwize\StructArmed\Analyser\Parallel\WorkerProgressHandler
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  4/  4)
Boundwize\StructArmed\Analyser\PropertyNode
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
Boundwize\StructArmed\Analyser\PropertyUnserializeTrait
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  2/  2)
Boundwize\StructArmed\Analyser\RecursiveParentsTrait
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 11/ 11)
Boundwize\StructArmed\Architecture
  Methods: 100.00% (33/33)   Lines: 100.00% ( 99/ 99)
Boundwize\StructArmed\Baseline\Baseline
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% (111/111)
Boundwize\StructArmed\Baseline\BaselineFilter
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  7/  7)
Boundwize\StructArmed\Cache\AnalysisCacheMetadataFactory
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 36/ 36)
Boundwize\StructArmed\Cache\AnalysisResultCache
  Methods: 100.00% (45/45)   Lines: 100.00% (785/785)
Boundwize\StructArmed\Cache\CachePathFactory
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  4/  4)
Boundwize\StructArmed\Cache\ConfigurationFingerprint
  Methods:  83.33% ( 5/ 6)   Lines:  97.00% ( 97/100)
Boundwize\StructArmed\Cache\FileHashProvider
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 10/ 10)
Boundwize\StructArmed\Cli\AnalyseCommand
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% (215/215)
Boundwize\StructArmed\Cli\ClearCacheCommand
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 28/ 28)
Boundwize\StructArmed\Cli\ColorSupport
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 21/ 21)
Boundwize\StructArmed\Cli\InitCommand
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 50/ 50)
Boundwize\StructArmed\Cli\StructArmedApplication
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 26/ 26)
Boundwize\StructArmed\Composer\ComposerJsonProvider
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 14/ 14)
Boundwize\StructArmed\Composer\Psr4PathResolver
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 49/ 49)
Boundwize\StructArmed\Config\ConfigLoader
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 26/ 26)
Boundwize\StructArmed\File\PhpFileCollector
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 17/ 17)
Boundwize\StructArmed\File\SkipPathMatcher
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 43/ 43)
Boundwize\StructArmed\LayerResolver\ChainLayerResolver
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 26/ 26)
Boundwize\StructArmed\LayerResolver\Resolvers\ClassNameRegexLayerResolver
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 22/ 22)
Boundwize\StructArmed\LayerResolver\Resolvers\NamespaceLayerResolver
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 40/ 40)
Boundwize\StructArmed\PHPUnit\StructArmedExtension
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 60/ 60)
Boundwize\StructArmed\Preset\Preset
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 34/ 34)
Boundwize\StructArmed\Preset\Presets\CodeQualityPreset
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 11/ 11)
Boundwize\StructArmed\Preset\Presets\DddPreset
  Methods: 100.00% (10/10)   Lines: 100.00% (152/152)
Boundwize\StructArmed\Preset\Presets\MvcPreset
  Methods: 100.00% (11/11)   Lines: 100.00% (206/206)
Boundwize\StructArmed\Preset\Presets\PerPreset
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 23/ 23)
Boundwize\StructArmed\Preset\Presets\Psr12Preset
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 23/ 23)
Boundwize\StructArmed\Preset\Presets\Psr15Preset
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 35/ 35)
Boundwize\StructArmed\Preset\Presets\Psr1Preset
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 20/ 20)
Boundwize\StructArmed\Preset\Presets\Psr4Preset
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 15/ 15)
Boundwize\StructArmed\Preset\Presets\ResolvesSourceLayerNameTrait
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 14/ 14)
Boundwize\StructArmed\Preset\Presets\YagniPreset
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 23/ 23)
Boundwize\StructArmed\Progress\ConsoleProgressBar
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 54/ 54)
Boundwize\StructArmed\Report\Reports\ConsoleReport
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 33/ 33)
Boundwize\StructArmed\Report\Reports\GithubReport
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 14/ 14)
Boundwize\StructArmed\Report\Reports\JsonReport
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  6/  6)
Boundwize\StructArmed\Rule\AbstractLayerAwareRule
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  2/  2)
Boundwize\StructArmed\Rule\Fixer\JsonRecast\AbstractJsonRecastFixableRule
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 15/ 15)
Boundwize\StructArmed\Rule\Fixer\JsonRecast\JsonRecastFixerProcessor
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 17/ 17)
Boundwize\StructArmed\Rule\Fixer\JsonRecast\ObjectItemNode\RemoveMissingPsr4PathVisitor
  Methods: 100.00% (14/14)   Lines: 100.00% ( 46/ 46)
Boundwize\StructArmed\Rule\Fixer\PhpParser\AbstractPhpParserFixableRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 17/ 17)
Boundwize\StructArmed\Rule\Fixer\PhpParser\AbstractTokenAwareVisitor
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
Boundwize\StructArmed\Rule\Fixer\PhpParser\ClassConst\AddPublicConstantVisibilityVisitor
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 17/ 17)
Boundwize\StructArmed\Rule\Fixer\PhpParser\ClassConst\ChangeProtectedConstantToPrivateVisitor
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 18/ 18)
Boundwize\StructArmed\Rule\Fixer\PhpParser\ClassLike\RemoveClassLikeVisitor
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  8/  8)
Boundwize\StructArmed\Rule\Fixer\PhpParser\ClassMethod\AddPublicMethodVisibilityVisitor
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 12/ 12)
Boundwize\StructArmed\Rule\Fixer\PhpParser\ClassMethod\ChangeProtectedMethodToPrivateVisitor
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 13/ 13)
Boundwize\StructArmed\Rule\Fixer\PhpParser\Class_\AddAbstractClassVisitor
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  9/  9)
Boundwize\StructArmed\Rule\Fixer\PhpParser\Class_\AddFinalClassVisitor
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  9/  9)
Boundwize\StructArmed\Rule\Fixer\PhpParser\Class_\RemoveAnonymousClassParenthesesVisitor
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 12/ 12)
Boundwize\StructArmed\Rule\Fixer\PhpParser\ConstFetch\LowercaseKeywordConstantVisitor
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 15/ 15)
Boundwize\StructArmed\Rule\Fixer\PhpParser\FunctionLike\AddStaticAnonymousFunctionVisitor
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 31/ 31)
Boundwize\StructArmed\Rule\Fixer\PhpParser\Function_\RemoveFunctionVisitor
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 29/ 29)
Boundwize\StructArmed\Rule\Fixer\PhpParser\PhpParserFixerProcessor
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 44/ 44)
Boundwize\StructArmed\Rule\Fixer\PhpParser\Property\AddPublicPropertyVisibilityVisitor
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 25/ 25)
Boundwize\StructArmed\Rule\Fixer\PhpParser\Scalar\AddNumericLiteralSeparatorsVisitor
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 17/ 17)
Boundwize\StructArmed\Rule\RuleViolation
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 29/ 29)
Boundwize\StructArmed\Rule\RuleViolationCollection
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 21/ 21)
Boundwize\StructArmed\Rule\Rules\Class_\AnonymousClassMayNotHaveEmptyParenthesesRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 15/ 15)
Boundwize\StructArmed\Rule\Rules\Class_\ClassConstantNameMustBeUpperCaseRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 21/ 21)
Boundwize\StructArmed\Rule\Rules\Class_\ClassImplementingInterfaceMustHaveSuffixRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 18/ 18)
Boundwize\StructArmed\Rule\Rules\Class_\ClassNameMustBeStudlyCapsRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 11/ 11)
Boundwize\StructArmed\Rule\Rules\Class_\ClassNameMustHaveSuffixRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 16/ 16)
Boundwize\StructArmed\Rule\Rules\Class_\ClassNameMustNotHavePrefixRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 16/ 16)
Boundwize\StructArmed\Rule\Rules\Class_\EnumCaseNameMustBePascalCaseRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 20/ 20)
Boundwize\StructArmed\Rule\Rules\Class_\EnumConstantMayNotBeProtectedRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 26/ 26)
Boundwize\StructArmed\Rule\Rules\Class_\EnumMethodMayNotBeProtectedRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 26/ 26)
Boundwize\StructArmed\Rule\Rules\Class_\ExtendedClassMustBeAbstractOrInstantiatedRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 27/ 27)
Boundwize\StructArmed\Rule\Rules\Class_\MaxDependencyCountRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 18/ 18)
Boundwize\StructArmed\Rule\Rules\Class_\MayNotExtendClassRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 11/ 11)
Boundwize\StructArmed\Rule\Rules\Class_\MayNotImplementInterfaceRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 18/ 18)
Boundwize\StructArmed\Rule\Rules\Class_\MustBeFinalRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 23/ 23)
Boundwize\StructArmed\Rule\Rules\Class_\MustBeInterfaceRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 19/ 19)
Boundwize\StructArmed\Rule\Rules\Class_\MustBeUsedAbstractClassRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 24/ 24)
Boundwize\StructArmed\Rule\Rules\Class_\MustBeUsedInterfaceRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 25/ 25)
Boundwize\StructArmed\Rule\Rules\Class_\MustBeUsedTraitRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 22/ 22)
Boundwize\StructArmed\Rule\Rules\Class_\MustDeclareConstantVisibilityRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 25/ 25)
Boundwize\StructArmed\Rule\Rules\Class_\MustDeclareMethodVisibilityRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 25/ 25)
Boundwize\StructArmed\Rule\Rules\Class_\MustDeclarePropertyVisibilityRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 25/ 25)
Boundwize\StructArmed\Rule\Rules\Class_\MustImplementInterfaceRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 19/ 19)
Boundwize\StructArmed\Rule\Rules\Class_\NamingConventionRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 23/ 23)
Boundwize\StructArmed\Rule\Rules\Composer\Psr4DirectoryExistsRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 26/ 26)
Boundwize\StructArmed\Rule\Rules\Composer\Psr4EmptyNamespacePrefixRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 32/ 32)
Boundwize\StructArmed\Rule\Rules\Composer\Psr4NamespaceRule
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 60/ 60)
Boundwize\StructArmed\Rule\Rules\Composer\Psr4RootPathRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 41/ 41)
Boundwize\StructArmed\Rule\Rules\Composer\Psr4SourcePathsRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 41/ 41)
Boundwize\StructArmed\Rule\Rules\File\LargeNumericLiteralMustUseSeparatorRule
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 50/ 50)
Boundwize\StructArmed\Rule\Rules\File\MustUseLowercaseKeywordConstantRule
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 32/ 32)
Boundwize\StructArmed\Rule\Rules\File\PhpFileFinder
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 37/ 37)
Boundwize\StructArmed\Rule\Rules\File\Psr1PhpTagsRule
  Methods: 100.00% (10/10)   Lines: 100.00% ( 74/ 74)
Boundwize\StructArmed\Rule\Rules\File\Psr1SymbolsOrSideEffectsRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 32/ 32)
Boundwize\StructArmed\Rule\Rules\File\Psr1Utf8WithoutBomRule
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 31/ 31)
Boundwize\StructArmed\Rule\Rules\File\Psr1ValidUtf8Rule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 25/ 25)
Boundwize\StructArmed\Rule\Rules\Function_\MustBeStaticAnonymousFunctionRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 18/ 18)
Boundwize\StructArmed\Rule\Rules\Function_\MustBeUsedFunctionRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 21/ 21)
Boundwize\StructArmed\Rule\Rules\Function_\MustHaveReturnTypeFunctionRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 15/ 15)
Boundwize\StructArmed\Rule\Rules\Layer\MayNotDependOnRule
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 29/ 29)
Boundwize\StructArmed\Rule\Rules\Method\MaxCyclomaticComplexityRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 20/ 20)
Boundwize\StructArmed\Rule\Rules\Method\MaxMethodLengthRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 20/ 20)
Boundwize\StructArmed\Rule\Rules\Method\MethodNameMustBeCamelCaseRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 22/ 22)
Boundwize\StructArmed\Rule\Rules\Method\MustHaveReturnTypeRule
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 26/ 26)
Boundwize\StructArmed\Rule\Rules\Usage\MayNotCallFunctionRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 16/ 16)
Boundwize\StructArmed\Rule\Rules\Usage\MayNotUseClassRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 20/ 20)
Boundwize\StructArmed\Rule\Rules\Usage\MayNotUseConstantRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 16/ 16)
Boundwize\StructArmed\Rule\Rules\Usage\MayNotUseLanguageConstructRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 16/ 16)
Boundwize\StructArmed\Rule\Rules\Usage\MayNotUseNamespaceRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 20/ 20)
Boundwize\StructArmed\Rule\Rules\Usage\MayNotUseSuperglobalsRule
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 16/ 16)
Boundwize\StructArmed\Util\InlineHtmlOpeningTagMatcher
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  8/  8)
Boundwize\StructArmed\Util\Path
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 49/ 49)
Boundwize\StructArmed\Util\PhpParser\AnonymousClassParentheses
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 29/ 29)
Boundwize\StructArmed\Util\PhpParser\ObjectBoundAnonymousFunction
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 32/ 32)
Boundwize\StructArmed\Util\PhpParser\ProtectedToPrivateFlags
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  3/  3)
Boundwize\StructArmed\Util\PhpParser\UnconditionallyDeclaredFunctions
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  8/  8)
Boundwize\StructArmed\Util\PhpParser\VisibilityFlagChecker
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
Boundwize\StructArmed\Util\SourceLayerName
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  2/  2)
Boundwize\StructArmed\Version
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  5/  5)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Configuration fingerprints omit stored object options, nested rule configuration, and closure or anonymous-rule source changes, allowing stale cached results to be reused after configuration changes.

2 open issues
What changed in this PR

Extends cache identity with the evaluated Architecture, allowing configuration changes from required PHP files to invalidate StructArmed’s analysis caches.

Changes:

  • Combines architecture fingerprints with the root configuration file’s content hash.
  • Uses the combined identity in CLI and PHPUnit caches and bumps cache versions.
  • Adds unit and cross-process tests for fingerprint stability and configuration-driven invalidation.
File Description
tests/​Fixtures/​Cache/​RuleSettingsGroup.php Provides nested readonly settings for graph tests.
tests/​Fixtures/​Cache/​RuleSettings.php Provides readonly options, bound callbacks, and custom serialization.
tests/​Fixtures/​Cache/​RulePolicy.php Provides enum settings for identity tests.
tests/​Fixtures/​Cache/​CyclicRuleSettings.php Provides self-referencing settings for cycle tests.
tests/​Cache/​ConfigurationFingerprintTest.php Tests deterministic identities, configuration values, and runtime-state exclusion.
tests/​Cache/​ConfigurationCacheTest.php Tests cache invalidation across CLI modes and the PHPUnit extension.
tests/​Cache/​AnalysisResultCacheTest.php Updates expectations for metadata versions and supplied configuration hashes.
tests/​Cache/​AnalysisCacheMetadataFactoryTest.php Tests combined architecture and root-file hashing.
src/​PHPUnit/​StructArmedExtension.php Uses the combined configuration identity for analysis caches.
src/​Cli/​AnalyseCommand.php Shares the combined identity across cache operations.
src/​Cache/​ConfigurationFingerprint.php Adds fingerprinting of evaluated architecture and rule settings.
src/​Cache/​AnalysisResultCache.php Bumps the cache format to invalidate older entries.
src/​Cache/​AnalysisCacheMetadataFactory.php Combines configuration hashes and includes them in versioned metadata.

Review effort: Max

Comment on lines +209 to +211
$function->getFileName(),
$function->getStartLine(),
$function->getEndLine(),

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Known limitation. Codex recommended the same, I decided against. I'd not bypass cache by default. Given that currently everything remains cached this is already improving things. I'd leave it as is and let you, @samsonasik, decide whether custom rules at a later point might should be able to manually opt out form caching in some way (property, interface or the like).

Comment thread src/Cache/ConfigurationFingerprint.php Outdated
Comment on lines +219 to +221
return $class->isReadOnly()
? $this->properties($value, $ancestors, $depth)
: ['opaque', $this->classIdentity($class)];
@NickSdot

NickSdot commented Oct 29, 2026 •

Copy link
Copy Markdown
Author

@NickSdot needs another 3% coverage test: 97.56% of diff hit (target 100.00%

CI collects PHP 8.2 coverage only , tests alone cannot cover the PHP 8.4 paths here. If you want this at 100% we must add machinery for optional reflection, and version switches, just to make coverage happy. Worth it?

@samsonasik

Copy link
Copy Markdown
Member

@NickSdot I see, I am thinking that this ConfigurationFingerprint is a little bit too complex, probably better to make it simpler like add ->import() method, eg:

return Architecture::define()
    ->import(__DIR__ . '/module/Foo/structarmed.php')
    ->import(__DIR__ . '/module/Bar/structarmed.php');

The imported config can be:

// module/Foo/structarmed.php

return static function (Architecture $architecture): void {
    $architecture
        ->layer('Foo', 'module/Foo/src/')
        ->rule('foo-rule', new FooRule());
};

so proper imported files can be checked on Architecture.php

/** @var list<string> */
private array $importedFiles = [];

public function import(string $file): self
{
    $configure = require $file;

    if (! is_callable($configure)) {
        throw new InvalidArgumentException(
            'Imported configuration must return a callable.'
        );
    }

    $configure($this);

    $this->importedFiles[] = realpath($file) ?: $file;

    return $this;
}

/** @return list<string> */
public function getImportedFiles(): array
{
    return $this->importedFiles;
}

@samsonasik

Copy link
Copy Markdown
Member

@NickSdot I created alternative PR for it:

should be simpler to maintain ;)

@samsonasik

Copy link
Copy Markdown
Member

@NickSdot Thank you for the effort on it. I am closing it in favor of

as I am thinking that will be easier to maintain :)

@samsonasik samsonasik closed this Oct 29, 2026
@NickSdot

NickSdot commented Oct 29, 2026 •

Copy link
Copy Markdown
Author

@NickSdot I created alternative PR for it:

should be simpler to maintain ;)

Sure, if you rip our all the helpful functionality then it's simpler. ;)
Doesn't solve my problem, unfortunately. Will break down as soon as any custom rule reads any data.

Closing this in the middle of everything is quite something.

Never mind, will fork -- thanks for considering!

@samsonasik

Copy link
Copy Markdown
Member

I am sorry about that :).

This PR solution require continuation look as it verify everything in the Architecture object and the rule interfaces, which in the future is not ideal imo.

@NickSdot
NickSdot deleted the feat/cache-fingerprinting branch October 29, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants