Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[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

Merged
fabpot merged 1 commit intosymfony:masterfromogizanagi:feature/di/factory_class_from_def_class
Jan 24, 2017
Merged

[DependencyInjection] Use current class as default class for factory declarations#20943

fabpot merged 1 commit intosymfony:masterfromogizanagi:feature/di/factory_class_from_def_class
Jan 24, 2017

Conversation

@ogizanagi
Copy link
Contributor

@ogizanagiogizanagi commentedDec 15, 2016
edited
Loading

QA
Branch?master
Bug fix?no
New feature?yes
BC breaks?no
Deprecations?no
Tests pass?yes
Fixed tickets#20888
LicenseMIT
Doc PRShould 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:

<serviceid="app.newsletter_manager"class="AppBundle\Email\NewsletterManager">    <factoryclass="AppBundle\Email\NewsletterManager"method="create" /></service>

The class is used as a factory to create itself, thus it can be simplified to:

<serviceid="app.newsletter_manager"class="AppBundle\Email\NewsletterManager">    <factorymethod="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 thefactory key, whereas the xml format use a dedicatedfunction 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:

services:my_factory:class:Bar\Bazfactory:[~, 'create']

chalasr and yceruto reacted with thumbs up emoji
@ogizanagi
Copy link
ContributorAuthor

(Travis and fabbot failures unrelated)

$class =$factory->getAttribute('class') ?:$definition->getClass();
}

if (null ===$class) {
Copy link
Member

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) ) ?

Copy link
ContributorAuthor

@ogizanagiogizanagiDec 15, 2016
edited
Loading

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.

yceruto and HeahDude reacted with thumbs up emoji
Copy link
Contributor

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 😕

Copy link
ContributorAuthor

@ogizanagiogizanagiDec 17, 2016
edited
Loading

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.

Copy link
Contributor

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.

Copy link
ContributorAuthor

@ogizanagiogizanagiDec 17, 2016
edited
Loading

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. :)

Copy link
Contributor

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] }

Copy link
ContributorAuthor

@ogizanagiogizanagiDec 17, 2016
edited
Loading

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:

  1. [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 theContainerBuilder to alter definitions.
  2. We can resolve the factory class inDefinition::getFactory() instead of computing it inDefinition::setFactory()
  3. Or we can indeed try a more advanced solution with a newDefinition::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(

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
Copy link
Member

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
Copy link
Member

Or null~ first element?

chalasr and ogizanagi reacted with thumbs up emoji

@ogizanagi
Copy link
ContributorAuthor

Looks like a good trade-off to me and can be handled directly in theDefinition::setFactory() method.
Anything against supporting both? (i.efactory: [~, 'create'] andfactory: ['create'])

returnarray($this->resolveServices($callable[0]),$callable[1]);
}

if ('factory' ===$parameter) {
Copy link
ContributorAuthor

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.

Copy link
Member

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
Copy link
ContributorAuthor

PR updated to support this directly in theDefinition class and allowing it from theYamlFileLoader.

Exceptions messages would need to be enhanced, but it's hard to be really succinct.

@ogizanagiogizanagi changed the title[DependencyInjection] Use current class as default class for factory declarations using xml format[DependencyInjection] Use current class as default class for factory declarationsDec 16, 2016
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]}
Copy link
Member

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.

Copy link
ContributorAuthor

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
Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
ContributorAuthor

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])) {
Copy link
Member

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?

Copy link
ContributorAuthor

@ogizanagiogizanagiDec 18, 2016
edited
Loading

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();
Copy link
Member

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?

Copy link
ContributorAuthor

@ogizanagiogizanagiDec 18, 2016
edited
Loading

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
Copy link
Member

Rethinking about this one, shouldn't this logic be handled by PhpDumper instead?
Definition should have as low logic as possible. But PhpDumper is the right place tointerpret what a definitionmeans. Don't you think?

@ogizanagi
Copy link
ContributorAuthor

@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 typingBar\Baz twice in the definition. Not a magical thing allowing to guess the factory class according to the finalDefinition class if the class has been modified elsewhere, in a compiler pass for instance. Considering this, maybe I shouldn't have changed any logic in theDefinition class, but only added it to dedicated loaders.

Now, if we really want to support something like:

services:my_factory:factory:[~, 'create']

where the class can be provided later, indeedPhpDumper looks like the good candidate.

@nicolas-grekas
Copy link
Member

be able to warn the user with as much context as possible if something is missing

the PhpDumper has full context to do so

@ogizanagi
Copy link
ContributorAuthor

I'll rework this PR then.

Status: Needs work

@ro0NL
Copy link
Contributor

This makes the feature a compilation artifact.. not sure if that makes sense.

Im fine with mutating theDefinition with sensible defaults in runtime as well... perhaps a privateContainerBuilder::finalizeDefinition($id, Def $def) will do?

  • set class from id
  • set factory from class
  • etc.

@ogizanagi
Copy link
ContributorAuthor

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]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

extra space

Copy link
ContributorAuthor

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
Copy link
Member

@ogizanagi I know that fabbot.io fail is not related with your changes, but could you fix it in this PR?

@HeahDude
Copy link
Contributor

@yceruto Those changes should be ignored ;) (seePHP-CS-Fixer/PHP-CS-Fixer#2413).

yceruto reacted with thumbs up emoji

returnarray($this->resolveServices($callable[0]),$callable[1]);
}

if ('factory' ===$parameter && !isset($callable[0])) {
Copy link
Member

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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

missing comma before "but"

Copy link
ContributorAuthor

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]) {
Copy link
Member

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-grekasnicolas-grekas added this to the3.3 milestoneDec 26, 2016
@nicolas-grekas
Copy link
Member

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.
I think this PR should be enhanced up to replace#20264.

@nicolas-grekas
Copy link
Member

Hum, I completely messed up with my previous comment, you can forget about it :)
Why do we need to patch the xml & yaml loader at all? I'd expect the compiler pass to deal with the logic completely. Did I miss something?

@ogizanagi
Copy link
ContributorAuthor

ogizanagi commentedJan 1, 2017
edited
Loading

Why do we need to patch the xml & yaml loader at all?

Just in order to be able to use those formats without getting an exception thrown by the loaders because factory class is missing.
The compiler pass does all the job.

(Happy new year 🎉 🎈🍾🥂😃)

theofidry reacted with hooray emoji

@nicolas-grekas
Copy link
Member

nicolas-grekas commentedJan 1, 2017
edited
Loading

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...
PhpDumper & ContainerBuilder already have many in common and the logic we're talking about doesn't look that divergent from what they already share.

(Happy new year ! :) )

@ogizanagi
Copy link
ContributorAuthor

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 thedebug:container command?

@nicolas-grekas
Copy link
Member

@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
Copy link
ContributorAuthor

ogizanagi commentedJan 7, 2017
edited
Loading

@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 thebin/console debug:container foo command, for instance, will output:

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
Copy link
ContributorAuthor

@stof : Anything to declare? 😄

Otherwise I'll remove the last commit about the compiler pass implementation.

Thank you.

@nicolas-grekas
Copy link
Member

@ogizanagi would you mind updating the compiler pass ti make it able to handle nested inline definitions?

@ogizanagi
Copy link
ContributorAuthor

ogizanagi commentedJan 16, 2017
edited
Loading

@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$container->compile(), because:

  1. [DI] Optional class for named services #21133 uses the service id if class is not set in the definition, even for a service id likebar. If we want to avoid aclass_exists call to support generated classes, I wonder if we shouldn't at least enforce a leading\ in case someone wants to register a service named after a class in the global namespace?
  2. TheCheckDefinitionValidityPass anyway checks a definition has a class, even for definition with a factory. So maybe this test should be marked as legacy, or should be removed along with related code?

@nicolas-grekas
Copy link
Member

@ogizanagi can you rebase and take advantage of AbstractRecursivePass now that#21327 is merged?

@ogizanagi
Copy link
ContributorAuthor

Tests are failing, but that's not related AFAIK.

@nicolas-grekas
Copy link
Member

👍

@fabpot
Copy link
Member

@ogizanagi Can you add a note in the component CHANGELOG?

@ogizanagi
Copy link
ContributorAuthor

Sure. Done :)

@fabpot
Copy link
Member

Thank you@ogizanagi.

@fabpotfabpot merged commite6d8570 intosymfony:masterJan 24, 2017
fabpot added a commit that referenced this pull requestJan 24, 2017
…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
@ogizanagiogizanagi deleted the feature/di/factory_class_from_def_class branchJanuary 24, 2017 06:11
@fabpotfabpot mentioned this pull requestMay 1, 2017
OskarStark added a commit to symfony/symfony-docs that referenced this pull requestMar 10, 2023
…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
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@fabpotfabpotfabpot left review comments

@nicolas-grekasnicolas-grekasnicolas-grekas left review comments

@stofstofstof left review comments

@xabbuhxabbuhxabbuh left review comments

@ycerutoycerutoyceruto left review comments

+2 more reviewers

@ro0NLro0NLro0NL left review comments

@HeahDudeHeahDudeHeahDude left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

3.3

Development

Successfully merging this pull request may close these issues.

9 participants

@ogizanagi@xabbuh@nicolas-grekas@ro0NL@yceruto@HeahDude@stof@fabpot@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp