Let extensions and projects register services through DI attributes in attributeServicesDirectories - #6283
Open
ondrejmirtes wants to merge 6 commits into
Open
Let extensions and projects register services through DI attributes in attributeServicesDirectories#6283ondrejmirtes wants to merge 6 commits into
ondrejmirtes wants to merge 6 commits into
Conversation
…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], #[NonAutowiredService], #[RegisteredRule(level: 0)], #[RegisteredCollector] and #[AutowiredParameter] on the classes, declares the edited directories in 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the design from the issue discussion (following the constraints laid out there, as an implementation to compare with #6269):
NeonAdapter— also inContainerFactory's pre-pass, which turned out to need no second config pass (NeonCachedFileReadercaches raw decodes only).vendor/composer/autoload_classmap.php; no file is ever parsed. A cheap content prefilter ('#['+ case-insensitivephpstan, 100% recall) keeps classes without PHPStan attributes from being autoloaded. A file that suggests a DI attribute but yields no autoloadable class is an explicit error, not a fallback.AutowiredService,NonAutowiredService,RegisteredRule,RegisteredCollector,GenerateFactory, andAutowiredParameteron constructor parameters — now@api, enforced by the newApiAttributeRule(fires for every attribute position, reports non-@apiPHPStan attributes outside PHPStan code).#[ContainerExtension],#[ExtensionInterface],#[AutowiredExtensions]and the other internal attributes are explicit discovery errors, sogetInterfaceTagMapping()stays process-static.installed.phpinstall paths) — vendor packages count onlyautoload(Composer never installs a dependency's autoload-dev), the root project also countsautoload-devunless Composer ran with--no-dev(dedicated error); psr-0/files-only coverage errors too. On PHP < 8.0 a non-empty section is a clean configuration error.version@referencetoken — a warm run reads nothing under them; project-own directories and path repositories contribute per-file content hashes (same mechanism as config files). A changed package owning a configured directory invalidates the whole result cache (its rules can affect every file). Editing a discovered class rebuilds the container.AttributeServicesRegistrar+AttributeTargetsProviderserve bothvendor/attributes.phpand the discovered targets; discovery is lazy, so a container-cache hit never autoloads a third-party class. The stub-validator derivative container re-runs discovery throughContainerFactory::create()(regression-tested).neon2attributescommand: deterministically convertsservices:/rules:entries into the attributes, declares the edited directories in the section, reports everything it keeps in NEON with the reason (setup, literal arguments, underivable or partial tags, multi-registration, vendor classes, unusual constructor layouts), and verifies itself: it compiles the original and the converted configuration in fresh subprocesses and requires an identical tagged-services fingerprint (tag → classes → multiplicities), rolling everything back otherwise.Real-world check on
calebdw/phpstan-laravel:neon2attributes extension.neonconverts 133 entries and keeps 22 with reasons — the plan matches the hand-made conversion in calebdw/phpstan-laravel#10 exactly (118#[AutowiredService]+ 14autoTag: false+ 4 rules; 3 later kept because their constructors put parameters on one line) — and the fingerprint verification confirms identical tagged services.Compatibility note for extension authors: a migrated extension can only require PHPStan versions that know the section — older PHPStan fails with
Found section 'attributeServicesDirectories'.Closes phpstan/phpstan#15114
🤖 Generated with Claude Code
https://claude.ai/code/session_01Vw4RxhEifXdnTPw57hG3Vw