-
Notifications
You must be signed in to change notification settings - Fork 823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NEW Adding constructor auto-DI for classes resolved through Injector #8564
NEW Adding constructor auto-DI for classes resolved through Injector #8564
Conversation
My main concern with this would be performance impact – Injector creation can be used 10,000s of times on a single request. Have we done any profiling? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a very cool change! Because this has a high likelihood of becoming an important part of the API, I think it'd be good to get the @silverstripe/core-team across it. Also @sminnee's comment about profiling is valid too, we'd need to make sure this can be performant
|
||
/** | ||
* MethodInjector constructor. | ||
* @param ReflectionMethod $method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PSR-5 (when accepted) will require an empty line above this
|
||
public function testTaggedInjectableConstructors() | ||
{ | ||
Config::nest()->set(Injector::class, TestDependency::class . '.testTag', AlternateTestDependency::class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think use Config::modify()
here
It adds time, but we're talking <1s on >1 million instantiations. Every little bit counts though - I'll add some caching per class constructor that should alleviate most concerns. I could also add config to disable it completely? |
One Thousand Milliseconds is a lot of time! :-P Can we get a bit of data on, say, one of the expensive /treeview renders that we were using for testing our CMS NFRs
If we introduce caching we'd probably need to understand how many different cache entries are used in the above |
I ran a profile locally using our beloved key project. The page I used has 63 content blocks. I'm working off the assumption that this will be representative of an average slow request. I didn't set out to create a request with lots of injector calls. The response time went up 2.85% (+ 207ms). |
On another note, I'm not sure about this reading the |
Hmmn that's not as bad as I thought it could be. You're right that it could make more sense in SilverStripe to do this with |
What about this as an implementation solution: Instead of resolving the annotations at resolve time, you could add another config transformer that would read all class annotations on flush, and transliterate these to the config equivalent, and cache them there. By merging these into config on the Injector class, you can also override these defaults with YML, and you get the benefit of config caching (i.e. no performance hit except on flush). |
/**
* Reads annotations for all classes and maps them to config injections on the
* `SilverStripe\Core\Injector\Injector` class key
*/
class AnnotationTransformer implements TransformerInterface {
} |
That's a good idea. I'll put aside some time to look at this soon. For this PR I might switch to using existing configuration facets but I'm interested in pursuing the annotation configuration idea in a separate PR/RFC. |
@ScopeyNZ Did you have a change to have another look at this? |
Haha it's on my list of "before 4.4 is feature-frozen" - So I will endeavour to look over it within the next couple of weeks. |
ping @dnsl48, we were talking about SilverStripe DI earlier =D |
yeah, I definitely love the idea! Though, as already discussed above, needs some polishing :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks cool - I'd like to see a bit more inline documentation for MethodInjector
, though
…o injected - Adding a new MethodInjector that uses a ReflectionMethod as a constructor argument and injects type hinted classes with Injector - New MethodInjector has an API that will check a docblock for an @Injectable tag - Updating Injector to check constructors for an @Injectable tag and auto-inject dependencies if tagged - Adding tests & docs for constructor DI
fe291d1
to
66974a7
Compare
Worked on this a little on hackday - going off the suggestion from @tractorcow to have an annotation based config transformer. PRs open on config and framework for that. This has been rebased onto the framework PR (#9006). Still need to run through docs again and do some performance testing. |
* @param TestDependency $protectedDependency | ||
* @param array $additionalParams | ||
* @Injectable | ||
* @Inject(TestDependency,testTag) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this annotation is used for injector “tags” like CacheInterface.permissions
for example, should we use dots in the annotation rather than commas? Sorry if I’m misunderstanding the use case here 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is a good point. I think my idea here when I wrote this was that it'd be similar to Doctrine annotations, which are based of classes. The dot notation makes more sense in the SS ecosystem though.
@ScopeyNZ Is this something you're still keen to push across the line? Looks like PHP 8 will have native attributes, so it could be worth awaiting and exploring those? |
Yeah as far as this is concerned it was pretty much good to go when I left it. There was a case for showing some proof that there's no significant effect on performance, which I haven't done. I'll run through this stuff when I get some time and put updates on it. PHP 8 native attributes are definitely nice - and a great feature for this PR, but considering how long it took for PHP 7 to be a requirement, it would be frustrating to have something similar blocking this feature. |
I haven't done a detailed review, but I'm happy to approve the approach in principal. @brynwhyman I think this is a high value PR and that a lot of work as gone into implementing it. If the rest of the Core Committers agree this new API is worthwhile, I think it would be worth adding this the the CMS Squad board to take it over the line. |
I like the idea and I think the DI system in Silverstripe could use some attention. I'm not super sold on using annotations for this. One reason is that it's not really part of the API, rather comments. Another reason is that IDEs probably won't autocomplete class names when you're using annotations. |
The annotations are there as a "switch" to enable auto-DI for constructors, as I wasn't sure that suddenly doing this for any class resolved from the container was safe. Thinking about it now though, I think The other purpose of the annotations is to inject objects in the container that have a specific tag. Not really sure of any other appropriate way to do this. Some frameworks (eg Laravel) don't let you do this. |
Note: The documentation portion should not be merged here. I have started the process of migrating docs to a new repository so the docs will need to be removed from this PR and a new PR raised in the new repo instead. |
I'm going to close this - I think if we were to ever merge something like this today it would be using attributes rather than annotations |
I can RFC whether this is a feature that the core team is interested in for 4.4 if wanted.
This adds auto-DI for constructors of classes resolved through
Injector
. This works by checking the constructor docblock for a tag@Injectable
and then using typehint reflection to resolve dependecies. There's some docs in the PR.Having dependencies expressed in the constructor is a good way to indicate a classes dependencies in a clear way - producing expressive and testable classes. I think we should be encouraging this sort of DI, and perhaps make the
@Injectable
tag not required in 5.0