Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.7k
[DependencyInjection] Use current class as default class for factory declarations#20943
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
[DependencyInjection] Use current class as default class for factory declarations#20943
Uh oh!
There was an error while loading.Please reload this page.
Conversation
ogizanagi commentedDec 15, 2016
(Travis and fabbot failures unrelated) |
| $class =$factory->getAttribute('class') ?:$definition->getClass(); | ||
| } | ||
| if (null ===$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.
Should we check if the class doesn't exist also ( i.e.... || false === class_exists($class) ) ?
ogizanagiDec 15, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
Not sure it's the loader responsibility.
Also the class might not be available at this point, in case it's generated for instance.
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.
Imo. thenull === $class check also doesnt belong to the loaders 😕
ogizanagiDec 17, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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 check is only here to provide more detailed information (file, service id, ...).
A similar exception would be thrown from the definition, but not DX friendly because those information are lost.
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.
Point is the required information can actually be available later on, hence see#20264 (comment) :)
We gain some DX maybe, but it increases complexity. It just duplicates logic.
ogizanagiDec 17, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
Right now, I don't see the benefits, nor how this method would be helpful for this PR. BTW, you still miss information about the place from where was loaded the service.
DX indeed always increases complexity, as it requires writing specific code and sometimes preventing doing wrong things ahead. But I think we're going to survive this extraif 😅
However, if you think you have a point about theDefinition::resolve($id) suggestion, I think it's worth a PR in order to show us the benefits. :)
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 both PR's are merged, ill provide a failing test ;-) Think
services:Namespace\Class:{ factory: [~, create] }
ogizanagiDec 17, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
Sure it's something that could be enhanced then.
Actually, I already had in mind definitions are defined and altered at different places. With the current state of this PR, if the definition class is changed in a compiler pass for instance, the factory class used won't change.
I assumed it'll be a known limitation that should not challenge this PR, because it only provides syntax sugar over declaring a factory using YAML/XML formats.
Now, if we should consider this, there are other solutions:
- [DependencyInjection] Optional class for class named services #20264 could set the class according to the service id from loaders, because it's probably not the role of the
ContainerBuilderto alter definitions. - We can resolve the factory class in
Definition::getFactory()instead of computing it inDefinition::setFactory() - Or we can indeed try a more advanced solution with a new
Definition::resolve($id)method as you suggest, but I don't think we should provide this in this PR nor in[DependencyInjection] Optional class for class named services #20264
(2 and 3 would indeed challenge these extra checks in the loaders)
| } | ||
| if (null ===$class) { | ||
| thrownewInvalidArgumentException(sprintf( |
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.
we prefer a single line here
xabbuh commentedDec 16, 2016
Couldn't we support this in the YAML Format too by specifying an array with only one element (same for the PHP loader)? |
nicolas-grekas commentedDec 16, 2016
Or null |
ogizanagi commentedDec 16, 2016
Looks like a good trade-off to me and can be handled directly in the |
| returnarray($this->resolveServices($callable[0]),$callable[1]); | ||
| } | ||
| if ('factory' ===$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.
Should we blindly support this for configurators instead? IMHO it makes no sense for a service to configure itself.
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.
indeed, configurators should not support this
ogizanagi commentedDec 16, 2016
PR updated to support this directly in the Exceptions messages would need to be enhanced, but it's hard to be really succinct. |
| new_factory2:{ class: FooBarClass, factory: ['@baz', getClass]} | ||
| new_factory3:{ class: FooBarClass, factory: [BazClass, getInstance]} | ||
| new_factory4:{ class: BazClass, factory: [~, getInstance]} | ||
| new_factory5:{ class: BazClass, factory: [getInstance]} |
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 we need to support this one, it's rather confusing to have an array with just one element. Usingnull seems more discoverable and easy enough to understand.
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.
Fair enough. I'll remove it 👍
| * @param string|null $class The definition class | ||
| * | ||
| * @throws InvalidArgumentException When errors areoccuried | ||
| * @throws InvalidArgumentException When errors areoccurred |
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 cs fix should target 3.2.
| if (null ===$this->class) { | ||
| thrownewInvalidArgumentException(sprintf('First value of the $factory array should either be a class name, a reference or the definition class should be set. "%s" given.',isset($factory[0]) ?gettype($factory[0]) :'NULL')); | ||
| } | ||
| $factory[0] =$this->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.
See also#20264 (comment)
I think we should resolve properties at once, in the right order. So everythings works as expected.
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.
| $factory =explode('::',$factory,2); | ||
| } | ||
| if (is_array($factory) && !isset($factory[0])) { |
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 explicitly checking fornull? Why should we support empty arrays here?
ogizanagiDec 18, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
We shouldn't support empty arrays, but there wasn't such checks before.
I can explicitly check fornull, without the isset. Then it'll fail badly in case of an empty array.
I think the fact the array should contain 2 elements is almost part of the signature (through phpdoc), hence there is not any check about it. If we add them, it should be considered as a bug fix I guess?
| $class =newReference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE,false); | ||
| }else { | ||
| $class =$factory->getAttribute('class'); | ||
| $class =$factory->getAttribute('class') ?:$definition->getClass(); |
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.
Do we still need that? This is now handled in theDefinition, isn't it?
ogizanagiDec 18, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
Right. It's not needed, but it allows to provide a better exception message. See#20943 (comment)
nicolas-grekas commentedDec 18, 2016
Rethinking about this one, shouldn't this logic be handled by PhpDumper instead? |
ogizanagi commentedDec 18, 2016
@nicolas-grekas : I can give a look, but the idea was to be able to warn the user with as much context as possible if something is missing, and limit things done in his back (if the definition class is altered somewhere, the end user might not expect the factory class to change magically). services:my_factory:class:Bar\Bazfactory:[~, 'create'] Is only syntax sugar to avoid typing Now, if we really want to support something like: services:my_factory:factory:[~, 'create'] where the class can be provided later, indeed |
nicolas-grekas commentedDec 18, 2016
the PhpDumper has full context to do so |
ogizanagi commentedDec 18, 2016
I'll rework this PR then. Status: Needs work |
ro0NL commentedDec 18, 2016
This makes the feature a compilation artifact.. not sure if that makes sense. Im fine with mutating the
|
ogizanagi commentedDec 18, 2016
Updated. Status: Needs Review |
| $factory->setAttribute('method',$callable[1]); | ||
| }elseif (is_array($callable)) { | ||
| $factory->setAttribute($callable[0]instanceof Reference ?'service' :'class',$callable[0]); | ||
| if (null !==$callable[0]) { |
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.
extra space
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.
Oops. Good catch. Fixed :)
yceruto commentedDec 19, 2016
@ogizanagi I know that fabbot.io fail is not related with your changes, but could you fix it in this PR? |
HeahDude commentedDec 19, 2016
@yceruto Those changes should be ignored ;) (seePHP-CS-Fixer/PHP-CS-Fixer#2413). |
| returnarray($this->resolveServices($callable[0]),$callable[1]); | ||
| } | ||
| if ('factory' ===$parameter && !isset($callable[0])) { |
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.
null === $callable[0]
| } | ||
| if (null ===$class =$definition->getClass()) { | ||
| thrownewRuntimeException(sprintf('The "%s" service is defined to be created by a factory but is missing the factory class. Did you forget to define the factory or service class?',$id)); |
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.
missing comma before "but"
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.
Thank you
| returnarray($this->resolveServices($callable[0]),$callable[1]); | ||
| } | ||
| if ('factory' ===$parameter &&null ===$callable[0]) { |
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 need to validateisset($callable[1]) though, to accept it only when the second value is not null
nicolas-grekas commentedDec 30, 2016
There is a competition between this PR and#20264: both try to define the class when it's missing based on different rules. But if we're not careful, those rules can collide and we can have priority issues, with one implem taking over the other one. |
nicolas-grekas commentedJan 1, 2017
Hum, I completely messed up with my previous comment, you can forget about it :) |
ogizanagi commentedJan 1, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Just in order to be able to use those formats without getting an exception thrown by the loaders because factory class is missing. (Happy new year 🎉 🎈🍾🥂😃) |
nicolas-grekas commentedJan 1, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
The compiler pass is missing recursivity handing into arguments, etc. Note that I'd be really fine with considering that PhpDumper + ContainerBuilder::createService have this responsibility of interpreting such state correctly as I proposed previously... (Happy new year ! :) ) |
ogizanagi commentedJan 4, 2017
As I don't have a strong opinion on the Compiler pass vs. PhpDumper + ContainerBuilder, I can update both commits and we can choose then...but that would help me not doing everything twice if someone else has a stronger opinion on this. Isn't a complete Definition better, for instance when using the |
nicolas-grekas commentedJan 6, 2017
@ogizanagi can you please amend the first commit so that it gets in what's fixed by the 2nd one (ie everything but the compiler pass). We miss a test + implementation for ContainerBuilder. @stof please confirm you'd be ok with only the first commit. Personally, I am. A recursive compiler pass vs 4 lines in the PhpDumper + 4 lines in ContainerBuilder, I prefer the 4x2 lines. |
ogizanagi commentedJan 7, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@nicolas-grekas : amended + added implementation for the ContainerBuilder + test. Despite it's the most straightforward solution, we should be aware that it'll still allow an "incomplete" Definition. Thus, the following: foo:class:Foofactory:[~, create] along with the Information for Service "foo"============================= ------------------ -------- Option Value ------------------ -------- Service ID foo Class Foo Tags - Public yes Synthetic no Lazy no Shared yes Abstract no Autowired no Autowiring Types - Factory Class Factory Method create ------------------ -------- (i.e no factory class, unless we adjust the command or opt for the pass) EDIT: I've added the required changes in descriptors regarding this in the first commit. Let me know. |
ogizanagi commentedJan 12, 2017
@stof : Anything to declare? 😄 Otherwise I'll remove the last commit about the compiler pass implementation. Thank you. |
nicolas-grekas commentedJan 16, 2017
@ogizanagi would you mind updating the compiler pass ti make it able to handle nested inline definitions? |
ogizanagi commentedJan 16, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@nicolas-grekas : I was going to remove the second commit and go forward. Did you change your mind, or should we simply wait for more opinions on this? BTW, regarding the PHPDumper + ContainerBuilder implementation, now that dumping a non-compiled container is deprecated and#21133 is merged,this test is failing when adding
|
nicolas-grekas commentedJan 23, 2017
@ogizanagi can you rebase and take advantage of AbstractRecursivePass now that#21327 is merged? |
ogizanagi commentedJan 23, 2017
Tests are failing, but that's not related AFAIK. |
nicolas-grekas commentedJan 23, 2017
👍 |
fabpot commentedJan 23, 2017
@ogizanagi Can you add a note in the component CHANGELOG? |
ogizanagi commentedJan 23, 2017
Sure. Done :) |
fabpot commentedJan 24, 2017
Thank you@ogizanagi. |
…ss for factory declarations (ogizanagi)This PR was merged into the 3.3-dev branch.Discussion----------[DependencyInjection] Use current class as default class for factory declarations| Q | A| ------------- | ---| Branch? | master| Bug fix? | no| New feature? | yes| BC breaks? | no| Deprecations? | no| Tests pass? | yes| Fixed tickets |#20888| License | MIT| Doc PR | Should update the notice about the "class" attribute onhttp://symfony.com/doc/current/service_container/factories.html#20888 makes sense to me, considering the following sample extracted from the documentation:```xml<service> <factory method="create" /></service>```The class is used as a factory to create itself, thus it can be simplified to:```xml<service> <factory method="create" /></service>```However, it's not possible to provide the same feature for the YAML format, because it doesn't allow to distinct a function from a method call if the class is not provided explicitly under the `factory` key, whereas the xml format use a dedicated `function` attribute.Would this inconsistency between those two formats be a no-go for this feature?The doc notices:> When using a factory to create services, the value chosen for the class option has no effect on the resulting service. The actual class name only depends on the object that is returned by the factory. However, the configured class name may be used by compiler passes and therefore should be set to a sensible value.If this is merged, it should be updated wisely in order to not confuse everyone with this feature when using the xml format.UPDATE: The yaml format is now supported when the class is not provided in the factory array:```ymlservices: my_factory: class: Bar\Baz factory: [~, 'create']```Commits-------e6d8570 [DependencyInjection] Use current class as default class for factory declarations
…elf as factory (OskarStark)This PR was squashed before being merged into the 5.4 branch.Discussion----------[DependencyInjection] Explain how to use the class itself as factoryFollows*symfony/symfony#20943Commits-------95a06a1 [DependencyInjection] Explain how to use the class itself as factory
Uh oh!
There was an error while loading.Please reload this page.
#20888 makes sense to me, considering the following sample extracted from the documentation:
The class is used as a factory to create itself, thus it can be simplified to:
However, it's not possible to provide the same feature for the YAML format, because it doesn't allow to distinct a function from a method call if the class is not provided explicitly under the
factorykey, whereas the xml format use a dedicatedfunctionattribute.Would this inconsistency between those two formats be a no-go for this feature?
The doc notices:
If this is merged, it should be updated wisely in order to not confuse everyone with this feature when using the xml format.
UPDATE: The yaml format is now supported when the class is not provided in the factory array: