Register services from DI attributes found in configured directories - #6269
Register services from DI attributes found in configured directories#6269calebdw wants to merge 1 commit into
Conversation
c2b6951 to
8b7d263
Compare
|
this would indeed take away some of the very annoying parts of setting up extensions, I would love to apply this to Bladestan aswell. |
|
Hi, I'd love this (I love attributes and have been using them more and more ever since May 2025 where I found out I can use composer-attribute-collector to even support PHP 7.4) but I'd like to do this right. I think this is an ideal candidate to write out a spec in an issue by discussing together and coming up with all the concerns one might have with a feature like this. Then this while issue discussion can be fed like a prompt to a coding agent in our CI which will implement it. There's a lot of open questions about this feature and we need to discuss them first. Can you please start by opening an issue and describe what you'd like this feature to do, I'll comment on it, we'll brainstorm it and once we're happy with it, I'll fire off the agent and it will implement the code. Also this is yet another 1,000+ LoC PR from you, and we have one (you know which one ;)) which hasn't been merged yet so I'd like to avoid your frustration with multiple unmerged PR (I know how it feels). Thanks! |
PHPStan's dependency injection attributes were only usable from phpstan-src itself. They are collected at composer install time into vendor/attributes.php, which only ever covers phpstan-src's own src directory, and inside the PHAR the collector lives under a build-specific namespace prefix. Extensions shipped as separate packages therefore had no way in, and had to list every rule, type extension and collector by hand in a services: section. Add an autowiredServiceDirectories parameter. Each configuration file contributes its own directories, and because the parameter is a list the entries merge rather than overwrite, so one extension adding to it cannot hide another's classes. AutowiredServiceDiscoverer scans those directories, using the bundled parser only as a filter for classes carrying a known attribute, then reflects on the survivors to build the same TargetClass and TargetMethodParameter objects the collector produces. The compiler extensions merge both sources, so discovered classes go through the existing registration paths unchanged, including the auto-tagging derived from #[ExtensionInterface]. The scanned files feed the container cache key - otherwise editing a discovered rule would keep being served from the cached container. Parsing is deferred to the first attribute lookup, so a container that never asks does no work beyond listing the files, and the pre-filter matches the namespace the attributes live in rather than their names, which is both a single needle per file and immune to grouped or aliased use statements. getInterfaceTagMapping() now takes the ContainerBuilder, since the mapping depends on the configured directories and two containers in one process can disagree about them. Hoist its result out of the loop over autowired services in AutowiredAttributeServicesExtension: it used to be a free static memo, and calling it per service would otherwise re-derive the discoverer several hundred times per compile. #[ContainerExtension] is rejected with an explicit message. Nette snapshots its extension list before any of this runs, so compiler extensions still need an extensions: section. Mark the attributes extensions are now expected to reach for as @api.
e98d9d0 to
ae32753
Compare
Hello!
I really love the DI attributes and how much boilerplate they removed from the neon files! Unfortunately this mechanism was only usable in this repo so it couldn't be used to say clean up the Larastan
extension.neon---until nowThis adds a new
autowiredServiceDirectoriesparameter that extensions can configure like so:and then all of their classes will be automatically picked up by phpstan when running. The files are hashed by their contents, so if they change then the directory is re-walked
Additionally, this also allows conditionally registering rules with
#[RegisteredRule(level: 4, enabledBy: '%featureToggles.finiteTypesInHaystack%')]and removes a ton of boilerplate from theconditionalTagsconfigHere's an example in a third-party extension of how much it cleans up: calebdw/phpstan-laravel#10
Closes phpstan/phpstan#15114
Thanks!