-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[WIP] Annotation support for Services #21376
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
Conversation
*/ | ||
public function process(ContainerBuilder $container) | ||
{ | ||
if (class_exists(AnnotationReader::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.
if (!class_exists(AnnotationReader::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.
thanks :)
*/ | ||
class Service | ||
{ | ||
private $shared; |
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.
Should we add autowired
too?
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.
Definitely - it's one of my todos :)
} | ||
} | ||
|
||
private function augmentServiceDefinition(Definition $definition) |
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.
It should read recursively annotations from parent classes and implemented interfaces.
|
||
$onInvalid = $arg->getOnInvalid(); | ||
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; | ||
if ('ignore' == $onInvalid) { |
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.
===
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; | ||
if ('ignore' == $onInvalid) { | ||
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; | ||
} elseif ('null' == $onInvalid) { |
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.
===
{ | ||
$arguments = array(); | ||
$i = 0; | ||
foreach ($method->getParameters() as $parameter) { |
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.
Why not using the key instead of the $i
variable?
foreach($method->getParameters() as $i => $parameter) {
$arguments[$parameter->getName()] = $i;
}
It should do the same thing: https://3v4l.org/Zpq4W
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.
Updated!
I'm still not a fond of having (non-standard) annotations for DI. I've explained my reasons in depth here: #21103 (comment) However I recognize that it is practical for parameters injection. I've proposed a less intrusive alternative in #20738, but I'm not very convinced by it either. Maybe should we keep with the current solution for now: _defaults:
autowire: true
parameters:
mandrill_api_key: XXXXXXXXX
services:
App\NewsletterManager: ['', '', '%mandrill_api_key%'] # This is already working |
f61962d
to
f3da69a
Compare
@dunglas I agree with much of what you are saying :), and I see that we're both after trying to solve the "parameter" problem in some decent way. I'm also not convinced by #20738, so we could either stay with the current YAML solution or use annotations (or maybe there's a 3rd option). About your comments (#21103 (comment)), I don't see these as a significant problem. Overall, the fact that this feature would be meant for end-users - and not library authors - puts most of those concerns to rest. And in app code, coupling your code to your framework isn't a problem (actually, I see it as an advantage for speed and clarity). Also, it may help the container to be seen as simply an "internal detail" to end-users. If auto-wiring were perfect, then life would be good. But since there will always be edge-cases (parameters being the big one), with annotations, the user could avoid needing to suddenly (and strangely) go into But, we'll see what others think - I wanted to get some real code to help with this exact discussion! Cheers! |
This made me think, can't we have named arguments in yaml? (e.g. services:
Foo:
arguments:
mandrillApiKey: %mandrill_api_key% ). Having annotations look huge to me if this is the only motivation, don't you think ? |
@GuilhemN I've proposed this exact solution to @nicolas-grekas today and I'm working on a PR to implement it 🙌 |
Alternative proposal: #21383 |
Note that to me, this PR and #21383 do not compete. We could very well have both onboard. |
return; | ||
} | ||
|
||
$this->reader = new AnnotationReader(); |
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.
what about using $container->get('annotation_reader')
instead? this service is already used at compile time by some other extensions.
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.
and it already causes issues regarding incomplete service configuration (remember the burden when switching the cache configuration in 3.2 ?). We always documented that getting service instances during a compiler pass is an unsupported usage of the container (and so you are on your own and it may break in weird ways), so we should not do it in Symfony itself (especially when it can still break because of a change done by another bundle)
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 more than remember since I worked on that again in #21381.
Yet it allowed to fix a core issue, which is that there should be no cache enabled at build time.
Thus, using annotation_reader
could be also seen as a way for us to support using it at compile time a first class citizen - which we have to anyway, as history proved us.
|
||
private $lazy; | ||
|
||
public function __construct(array $data) |
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.
why not removing the constructor and using public properties, letting the annotation parser validate properties itself ? This is the recommended usage of doctrine/annotations when annnotation classes don't need to be reused for other purposes (which is why we don't do in the validator component: constraints themselves are used as annotations, but they are not only annotations)
…(dunglas, nicolas-grekas) This PR was merged into the 3.3-dev branch. Discussion ---------- [DependencyInjection] Add support for named arguments | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | todo This PR introduces named arguments for services definitions. It's especially useful to inject parameters in an autowired service. It is (at least partially) an alternative to #21376 and #20738. Usage: ```yml services: _defaults: { autowire: true } Acme\NewsletterManager: { $apiKey: "%mandrill_api_key%" } # Alternative (traditional) syntax services: newsletter_manager: class: Acme\NewsletterManager arguments: $apiKey: "%mandrill_api_key%" autowire: true ``` ```php use Doctrine\ORM\EntityManager; use Psr\Log\LoggerInterface; namespace Acme; class NewsletterManager { private $logger; private $em; private $apiKey; public function __construct(LoggerInterface $logger, EntityManager $em, $apiKey) { $this->logger = $logger; $this->em = $em; $this->apiKey = $apiKey; } } ``` Commits ------- 8a126c8 [DI] Deprecate string keys in arguments 2ce36a6 [DependencyInjection] Add a new pass to check arguments validity 6e50129 [DependencyInjection] Add support for named arguments
I'll close this for now (thanks to #21383). We may still want this in the future, to avoid needing to touch a YAML file once you need to map a custom, non-autowired arg... but let's see how this all sorts out. |
Hi guys!
This is a WIP solution for adding annotations to services, and is similar to some proposals in #21103. Notice, this does NOT include a "discovery" mechanism for services: you must still register your service like normal (e.g. in YML or XML file), but then the annotations in the service are used to augment the service Definition. But, if #21289 is merged, then that'll cover automatic "discovery" for these services :).
For me, the BIG motivation is to augment autowiring, when you have just one argument that can't be autowired. Annotations read much better than, for example, setting the index "2" argument in YAML.
Pretty much anything that you can set in YAML would be supported (most is still a TODO). The feature is currently activated by a tag... which is really ugly (though
_defaults
at least helps this). We could add this to theDefinition
object (like we did withautowire
) if we want.In #22103, there was some talk of following a Spring standard. While I think it IS important to look and learn from them, this keeps very close to our existing standard: effectively re-using all our existing terminology, but in YAML. The
@Argument
annotation re-uses all the options from the<argument>
tag in XML service config.Cheers!
TODOS
@Tag
annotation