-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Include ids from method map in known services #19690
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
|
Too quick comment, I didn't mean |
@@ -359,14 +358,19 @@ public function initialized($id) | |||
public function getServiceIds() | |||
{ | |||
$ids = array(); | |||
$reversedMethodMap = array_change_key_case(array_flip($this->methodMap), \CASE_LOWER); |
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 don't think the changes on this method are legitimate: get*Service
are the only "source", methodMap must be in sync with the correspond list and we don't need to merge it.
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.
Tests proves it works... you cant imagine how creative users can be. Sorry, couldnt resist ;-)
$this->methodMap is a compilation artifact
What if we make it a feature?
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.
you cant imagine how creative users can
except that as you spotted, it never worked :)
What if we make it feature?
then it should go on master and should have a supporting use case
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.
You're right, i didnt consider PhpDumper::generateMethodName
on this, which shows method names are generated from id.
However, they can get suffixed due duplicate methods, but that doesnt mean the id should change as well (this is the key in methodMap
which can be perfectly unique).
I covered exactly that in tests (imagine a suffixed/generated method though).
@nicolas-grekas technically, this change is needed, because On a side note, older versions were also buggy because the conversion between id to method name is ambiguous (multiple ids could generate the same so 👍 for this bugfix @ro0NL please add a test ensuring that |
Technically, |
@stof are you proposing to revert the last commit, and be safe in all cases?
If i understand correctly, i kinda did that in edd63a7#diff-8aeba2b88d0347062017ee160a6beb49R619 However i considered it from a user perspective.. (ie. map how you want) |
yeah indeed. I missed it sorry. |
I think we should be safe in all cases. Yes, it's a compilation artifact but it's more or less designed as a feature. The reverse lookup is needed for sure. Leaving; lowercasing and merging remaining service ids. Both are related to "different method convention", so if we go safe now this wont bother us ever again :) |
@@ -359,14 +358,19 @@ public function initialized($id) | |||
public function getServiceIds() | |||
{ | |||
$ids = array(); | |||
$reversedMethodMap = array_change_key_case(array_flip($this->methodMap), \CASE_LOWER); | |||
foreach (get_class_methods($this) as $method) { | |||
if (preg_match('/^get(.+)Service$/', $method, $match)) { |
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.
So, shouldn't we remove this and rely sololy on methodMap?
the full method could be reduced to the following implementation, isn't it?:
public function getServiceIds()
{
return array_keys($this->methodMap + $this->services);
}
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.
Depends how this method should work before/after compilation.
We could lazily load the method map when needed, which also happens when dumping. Im assuming the methods wont change during runtime anyway :)
@@ -357,15 +356,20 @@ public function initialized($id) | |||
*/ | |||
public function getServiceIds() | |||
{ | |||
$ids = array(); | |||
$ids = array_keys($this->services + $this->methodMap); |
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.
Still thinking we should only return this, no need to look for getters.
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 be the other way around btw: $this->methodMap + $this->services
to mimic current ordering (at prevent changing test cases, isn't it)?
So, we have 2 issues then;
Where state depends on Basically we have no clue methodMap is available by design :) there is no I tend to agree with @nicolas-grekas we should only consider state after compilation (although we can do both, like currently). However, i think we must keep it consistent, ie; removing So perhaps compiling |
Here's a wild idea, make Cleanup anything "method" related in // ProjectServiceContainer extends Container
protected $methodMap = array(
'id' => 'getFooService' // just dumped as is
);
public function has($id)
{
return isset($this->methodMap[$id]) || parent::has($id);
} This seems to follow current design of the dumper... ie. i think this is the way to go ( |
@ro0NL your proposal does not work for get (it must go in the middle of the code), and can only work for has if we kill the optimization avoiding unnecessary strtolower calls. |
True, Im not sure this approach is worth the effort.. but i do think it's better design opposed to |
@ro0NL The little refactoring needed would involve dropping the performance optimization added a few months ago. This is not a good idea IMO. |
@stof this wouldnt work? # class Container
public function has($id)
{
for ($i = 2;;) {
if ('service_container' === $id
|| isset($this->aliases[$id])
|| isset($this->services[$id])
) {
return true;
}
if (--$i && $id !== $lcId = strtolower($id)) {
$id = $lcId;
} else {
if (isset($this->privates[$id])) {
@trigger_error(sprintf('Checking for the existence of the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED);
}
- return method_exists($this, 'get'.strtr($id, $this->underscoreMap).'Service');
+ return $this->doHas($id);
}
}
}
+ // extension point for performance (opposed to overriding has()))
+ // ie. this technically finalizes has()
+ protected function doHas($normalizedId)
+ {
+ return false;
+ } Override |
Closing in favor of #20113 |
@ro0NL with extra overhead of the method call in a performance-sensitive code path (get is more sensitive than has btw) |
…for dumped containers (stof) This PR was merged into the 3.2-dev branch. Discussion ---------- Use the method map as authoritative list of factories for dumped containers | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes | New feature? | no | BC breaks? | only for weird broken cases | Deprecations? | yes (but only for people doing weird things) | Tests pass? | yes | Fixed tickets | #11761, #19690 | License | MIT | Doc PR | n/a The initial implementation of the method factory discovery was based on a naming convention for factory methods. However, this naming convention allowed to generate the same name for multiple ids. In the meantime, a method map was introduced to solve this issue (and others). When accessing a service with a different id than the official one (thanks to ambiguities), this breaks the sharing of the service, as it creates a new instance each time and replaces the existing shared instance. This was also inconsistent between a dumped container (affected by this) and a non-dumped container (reporting a service not found error for the other id). The method map is now the authoritative way to discover available service factories. When the dumped container was generated with a method map (which is the case when using the dumper shipped in the component), the logic based on defined methods is not executed anymore. This forbids using another id than the real one to access the service (preventing to trigger the broken behavior). So this breaks BC for people being lucky (i.e. they were using the broken id only once and *before* any usage of the official id) and fixes a WTF bug for all others. When using a dumper which does not fill the method map, the old logic is still applied, but deprecation warnings are triggered on access to dumped services. Currently, this will trigger a deprecation warning for each new service instantiation. I have not found an easy way to trigger it only once (except adding a private property to remember we already triggered it, but is it worth it ?), but only people writing a project container by hand or writing their own dumper would ever see such deprecation anyway (as the core dumper generates the method map). Additionally, this makes ``getServiceIds`` faster by avoiding doing a regex match for each method in the class. Commits ------- 03b9108 Use the method map as authoritative list of factories for dumped containers
Service id's mapped thru
$this->methodMap
are allowed for inget
, but apart from that they are not used. Ie.has
returns false, butget
returns.