10000 [DI] Allow using a class factory · Issue #23819 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[DI] Allow using a class factory #23819

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
GuilhemN opened this issue Aug 7, 2017 · 10 comments
Closed

[DI] Allow using a class factory #23819

GuilhemN opened this issue Aug 7, 2017 · 10 comments

Comments

@GuilhemN
Copy link
Contributor
GuilhemN commented Aug 7, 2017
Q A
Bug report? no
Feature request? yes
BC Break report? no
RFC? yes
Symfony version 3.4

Here's a proposal coming mostly from @nicolas-grekas (I helped a bit making the example :)).
Would you like to be able use a class factory to define your services?

Main benefits:

  • service names based on the methods return type (or @Alias)
  • arguments injection based on autowiring
  • access to php functions at runtime (no need anymore of the expression language), that also means access to getenv()
  • possibility to manually inline simple services

An example of how it could be used:

// services.yaml
imports:
    - type: service_factory
      resource: App\MyServicesFactory
// src/MyServicesFactory.php
<?php

namespace App;

use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Psr\Logger\LoggerInterface;

class MyServicesFactory
{
    private static $apiKey = 'foo';
    private $database;

    public function __construct()
    {
        // Constructor called with *no* argument
    }

    /**
     * @Tag(name="monolog.logger", channel="my_channel")
     * @Alias(id="my_service")
     * @Alias // when used with no id, the return type is used
     * @Param("strategies", tag="app.strategies")
     * 
     * @param StrategyInterface[] $strategies // it's the responsibility of a DI extension to populate this
     */
    public function myService(LoggerInterface $logger, iterable $strategies): MyServiceInterface
    {
      	$myService = new MyService($logger);
      	$myService->setSecret(self::$apiKey); // the service can be easily configured
      	$myService->setSecret(getenv('API_KEY')); // even dynamically
      	foreach ($strategies as $strategy) {
            $myService->addStrategy();
     	}

    	// Less flexible than an injected dep, but still a nice shortcut for private apps
        // benefits from type hinting (return type vs argument type can be verified by IDEs)
      	$myService->setDatabase($this->myDatabase()); 
  
      	return $myService;
    }

    /**
     * ExpressionLanguage not needed anymore.
     */
    public function myController(MyService $myService, Repository $doctrine): MyController
    {
      	return new MyController($myService, $doctrine->getRepository(Product::class));
    }

    protected function myDatabase(): DatabaseInterface
    {
      	return $this->database ?? $this->database = new Database('dns://...');
    }
}

Wdyt? This could be an alternative to #22407.

@nicolas-grekas
Copy link
Member
nicolas-grekas commented Aug 7, 2017

Disclaimer: this borrows some ideas from Disco :)
And I think it has more potential than #22407 because it allows writting code that is actually used at runtime (vs a DSL-like - which is nice BTW!)

@sstok
Copy link
Contributor
sstok commented Aug 8, 2017

Interesting, wondering would this also make https://github.com/container-interop/service-provider also easier to realize?

@shochdoerfer
Copy link

Almost as good as Disco :) I like the idea of passing dependencies via method parameter, that solves quite a bit of the magic within Disco that I do not really like.

Are you able to register multiple factories? If so, how would you solve naming collisions?

@GuilhemN
Copy link
Contributor Author
GuilhemN commented Aug 8, 2017

@sstok I don't think so, the implementation is different.

Are you able to register multiple factories? If so, how would you solve naming collisions?

@shochdoerfer yes, that's just a resource. About conflicts, I'd say like right now, the latest service defined wins.

@Tobion
Copy link
Contributor
Tobion commented Aug 8, 2017

I really like this proposal.

  • It uses normal PHP code and does not require another format/language you need to learn, e.g. compared to [DI] Add a new "PHP fluent format" for configuring the container #22407
  • Sometimes initializing services isn't that straightforward, e.g. Guzzle clients with complicated middlewares. So at first you think how to do it in PHP. And then you still have to transcode that to yaml or xml which doesn't make it easier. With this proposal you just have to do it once in PHP with a factory and you're set.

@shochdoerfer
Copy link

@nicolas-grekas Any chance to join forces with Disco? Like extending Disco to be able to use it as a common basis? Ping me if you are interested...

@fabpot
Copy link
Member
fabpot commented Aug 10, 2017

For the record, #23834 looks much better to me.

@ro0NL
Copy link
Contributor
ro0NL commented Aug 16, 2017

@GuilhemN what about favoring #23834 for annotating. So the factory would implement i.e. ServiceFactoryInterface and have a static method that receives a id + definition configurator for each class method.

The factory itself is also a service.

@chalasr
Copy link
Member
chalasr commented Sep 30, 2017

Is this still relevant?

@nicolas-grekas
Copy link
Member

Closing as I think we drafted the idea, so can be referenced in the future when needed.

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

No branches or pull requests

9 participants
2AB8
0