Skip to content

[DependencyInjection] Added closure service factories #12115

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

Closed

Conversation

unkind
Copy link
Contributor

@unkind unkind commented Oct 3, 2014

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets n/a
License MIT
Doc PR n/a

This PR adds closure service factories for the DependencyInjection component. There are 2 kinds of closures:

use Symfony\Component\DependencyInjection\ContainerInterface;

// Closure with services/parameters as arguments
$container
    ->register('bar1', 'Bar')
    ->setFactory(function (Foo $foo, $bar) {
        return new Bar($foo->getQux(), $bar);
    })
    ->addArgument(new Reference('foo'))
    ->addArgument('%bar%')
;

// Pimple-like closure with container as an argument
$container
    ->register('bar2', 'Bar')
    ->setFactory(function (ContainerInterface $container) {
        return new Bar($container->get('foo')->getQux(), $container->getParameter('bar'));
    })
;

It would be useful for something like ArrayFileLoader described in #11953.

@unkind unkind force-pushed the feature-closures-as-service-factories branch 2 times, most recently from f2cba65 to 1725733 Compare October 3, 2014 15:52
@stof
Copy link
Member

stof commented Oct 4, 2014

I don't like the idea of the Pimple-like closures. It makes the behavior of the factory callable inconsistent between the case of a closure and the case of other type of callables.
Thus, it makes the code fragile as any bundle adding an argument in the service definition will break it (as the first argument will not be the container anymore).

Thus, using such closures will require marking the dependencies as public (as they are retrieved dynamically), and prevents the ContainerBuilder to perform any optimization on the service instantiation (as it is inside the closure). So using such Pimple-like closures has lots of drawbacks.

@unkind unkind force-pushed the feature-closures-as-service-factories branch 2 times, most recently from 8af5df4 to 73ed6fc Compare October 4, 2014 17:00
@unkind
Copy link
Contributor Author

unkind commented Oct 4, 2014

Pimple-like syntax has been removed.

@unkind unkind force-pushed the feature-closures-as-service-factories branch from 73ed6fc to 3a9e4f8 Compare October 4, 2014 17:03
/**
* @author Nikita Konstantinov <unk91nd@gmail.com>
*/
final class SuperClosureDumper implements ClosureDumperInterface
Copy link
Member

Choose a reason for hiding this comment

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

we don't make classes final in Symfony generally

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's bad practice to be honest. No reason to inherit this class, you can make decorator to extend behaviour.

@unkind unkind force-pushed the feature-closures-as-service-factories branch 2 times, most recently from bff5a80 to 92b59f2 Compare October 4, 2014 18:03
return $this->services['bar'] = call_user_func(function (\stdClass $foo) {
$bar = clone $foo;
$bar->bar = 'bar';
return $bar;
Copy link
Member

Choose a reason for hiding this comment

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

add a blank line before return

@unkind unkind force-pushed the feature-closures-as-service-factories branch from 92b59f2 to b6067ff Compare October 4, 2014 22:32
@unkind
Copy link
Contributor Author

unkind commented Oct 5, 2014

Any chance to merge it in 2.6? ping @fabpot @stof

@fabpot
Copy link
Member

fabpot commented Oct 5, 2014

No, I still have too many questions/doubts to be able to merge this in a hurry, that's never a good idea.

@unkind
Copy link
Contributor Author

unkind commented Oct 11, 2014

The biggest problem of this PR is a coupling of ClosureDumperInterface with DependencyInjection component (it's bad as we need the same service in Routing configs, for instance). Probably we can move ClosureDumperInterface to the ExpressionLanguage. However, it wouldn't be perfect place as well.

@fabpot
Copy link
Member

fabpot commented Feb 5, 2015

I think I'm 👎 on this one. It adds a lot of complexity for very rare use cases that you can do another way.

@unkind
Copy link
Contributor Author

unkind commented Feb 5, 2015

I have to remind that initially it was done for the ArrayFileLoader, where closures look very nature comparing to the expression language. In our company we use Symfony DIC with such custom file loader and sometimes it would be really helpful for us.

Some use cases:

  1. We have legacy code like singleton DB connection, i.e. Database::master(), Database::slave(), etc. Just for quick solution we would write

    <?php
    
    return [
        'services' => [
            'foo' => [
                'factory' => function () {
                    return new Foo(Database::master());
                },
                'class' => Foo::class,
            ]
        ]
    ];

    to avoid coupling with singleton Database class. Also we have legacy configs with ConfigLocator class. I can reuse config and don't hurt new code by singletons. We have really huge code base and sometimes it takes much work for refactoring.

  2. The same motivation as described in http://symfony.com/doc/current/book/service_container.html#using-the-expression-language, i.e. it's much easier to make closure rather than creating separate class.

If you say that it's a "very rare case", you have to admit that you either don't want array file configs at all or expression language factory is a "very rare case" as well.

But it won't be merged in this way of course, because ClosureDumperInterface is tightly coupled with the DependencyInjection component, but I think it makes sense to raise this question again after #6129.

@unkind unkind closed this Feb 5, 2015
@unkind unkind deleted the feature-closures-as-service-factories branch February 5, 2015 20:38
@stof
Copy link
Member

stof commented Feb 5, 2015

@unkind in your specific case, I would rather define services for your database connection (using your singleton getter as factory) and then inject the service in your foo service.

The issue when introducing the dumping of closures is that we add lots of complexity for that, and that we only allow dumping a subset of closures (anonymous functions which don't depend on their context at all)

@unkind
Copy link
Contributor Author

unkind commented Feb 5, 2015

The issue when introducing the dumping of closures is that we add lots of complexity for that, and that we only allow dumping a subset of closures (anonymous functions which don't depend on their context at all)

SuperClosure allows to detect context-dependent closures, so it's not real issue IMO, we can throw an exception to avoid them. Anyway, it looks weird to have context-dependent closures in such config file.

@stof
Copy link
Member

stof commented Feb 5, 2015

sure we can throw an exception in this case. But it means we don't support them, meaning we only support a subset of closures, not all closures.

@unkind
Copy link
Contributor Author

unkind commented Feb 5, 2015

Partial support is better than nothing in this case IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants