Skip to content

Add the attributeServicesDirectories section - #6917

Closed
ondrejmirtes wants to merge 6 commits into
2.3.xfrom
attribute-services-directories
Closed

ondrejmirtes wants to merge 6 commits into
2.3.xfrom
attribute-services-directories

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Implements the attribute-services part of #6283 step by step, on top of the merged Conditional (#6914) and @api attributes (#6916).

New top-level section:

attributeServicesDirectories:
	- src

Classes in these directories can use all @api DI attributes: AutowiredService, NonAutowiredService, RegisteredRule, RegisteredCollector, GenerateFactory, Conditional, and AutowiredParameter on constructor parameters.

Commits:

  1. Extract AttributeServicesRegistrar and AttributeTargetsProvider from AutowiredAttributeServicesExtension. No behavior change, tagged services for levels 0/2/5/9/max with bleeding edge are identical.
  2. The section, its resolver and the container cache key. Directories must be covered by psr-4 or classmap autoload rules and need PHP 8.0+. Installed packages contribute their version to the key, the project's own directories and path repositories contribute hashes of files that can carry the attributes.
  3. Discovery from Composer autoload data and native reflection, no file parsing. A content prefilter keeps classes without PHPStan attributes from being autoloaded. On PHP 8.0, Conditional takes the NewExpression marker array instead of new.
  4. Result cache invalidation. Registered discovered classes go to the attributeServicesClasses parameter and are treated like classes from services:. A Composer update of a package owning a directory invalidates the whole result cache. Covered by a new e2e test.

Performance, with phpstan-strict-rules and phpstan-deprecation-rules converted to attributes in vendor/ (56 services): cold container compile 684 ms with neon vs. 681 ms with attributes, cache hit 119 ms in both. Discovery takes 5.5 ms only while compiling, the resolver 3.4 ms on every run.

Docs: phpstan/phpstan#15648

ondrejmirtes and others added 6 commits October 31, 2026 14:17
…ttribute usage

AutowiredService, NonAutowiredService, RegisteredRule, RegisteredCollector,
GenerateFactory and AutowiredParameter are the attributes third parties will
be able to use through attributeServicesDirectories. The new ApiAttributeRule
listens on every attribute position and reports non-@api PHPStan attributes
used outside PHPStan code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vw4RxhEifXdnTPw57hG3Vw
…ces seam

AttributeServicesRegistrar owns the target-to-definition logic and
AttributeTargetsProvider is the single merged view over vendor/attributes.php
and (soon) targets discovered in attributeServicesDirectories. No behavior
change; DiscoveredAttributeTargets is empty until discovery lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vw4RxhEifXdnTPw57hG3Vw
…ner-cache keying

The top-level section lists directories whose classes may carry PHPStan's DI
attributes. Entries are absolutized relative to the declaring config file by
NeonAdapter (also in ContainerFactory's pre-pass, so the resolver sees final
paths before the container compiles). The resolver validates each directory
against the owning Composer package's (or the root project's) autoload rules,
gates the feature to PHP 8.0+, and computes the directory's contribution to
the container cache key: a package version token for regularly installed
vendor packages, per-file content hashes for project-own directories and
path repositories.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vw4RxhEifXdnTPw57hG3Vw
…utoload data

AttributeServicesDiscoverer enumerates candidate classes from the PSR-4 path
contract and vendor/composer/autoload_classmap.php - no file is parsed - and
reads the allowlisted attributes (AutowiredService, NonAutowiredService,
RegisteredRule, RegisteredCollector, GenerateFactory, AutowiredParameter on
constructor parameters) with native reflection. A cheap content prefilter
keeps classes without PHPStan attributes from being autoloaded. Files that
suggest a DI attribute but yield no autoloadable class, and PHPStan-internal
attributes (#[ContainerExtension], #[ExtensionInterface], #[AutowiredExtensions],
...), are explicit errors. ContainerFactory seeds the discovery context before
each container build; AttributeTargetsProvider merges the discovered targets
with vendor/attributes.php lazily, so a container-cache hit loads nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vw4RxhEifXdnTPw57hG3Vw
…services changes

A directory listed in attributeServicesDirectories registers services straight
from its classes, so a composer update of the owning package can affect the
analysis of every file - the file-granular package re-seed is not enough.
PackageDependencyResolver::resolveDirectoryPackage() also matches a directory
that is a package's install path. The derivative (stub validator) container
re-runs discovery through ContainerFactory::create(), covered by a regression
test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vw4RxhEifXdnTPw57hG3Vw
…to attributes

Deterministically converts services:/rules: entries into #[AutowiredService],
attributeServicesDirectories, and leaves everything it cannot express (setup,
literal arguments, underivable tags, multi-registration, vendor classes) in
place with the reason reported. PHP edits are position-guided line insertions
(php-parser locates, plain text edits), NEON edits are order-mapped line
surgery that aborts on any layout it cannot map. Before keeping the changes
the command compiles the original and the converted configuration in fresh
subprocesses (the current process has the classes loaded without the new
attributes) and requires an identical tagged-services fingerprint, rolling
everything back otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vw4RxhEifXdnTPw57hG3Vw

Copy link
Copy Markdown
Member Author

Opened from the old #6283 branch by mistake, superseded by the PR from attribute-services-section.

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.

1 participant