-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Deprecate ContainerAwareCommand #21623
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
Comments
A big 👎 from me ... unless we find a great and simple alternative to access services from commands. |
Sure, there should still be an easy way to register commands which need dependencies, but IMO not any service should be accessible from a command (what are use cases for?). Let's take the assets:install command as an example of unnecessary container injection: it gets the To compensate the fact that Before: class DoSomethingCommand extends ContainerAwareCommand
{
public function configure()
{
$fooHandler = $this->getContainer()->get('foo.handler'); // exception, not accessible yet
}
public function execute($input, $output)
{
$fooHandler = $this->getContainer()->get('foo.handler'); // what is this?
}
} After: class DoSomethingCommand extends Command implements AutowiredCommandInterface
{
private $fooHandler;
public function __construct(FooHandler $handler)
{
// command has been auto registered as a an autowired service
$this->fooHandler = $fooHandler;
}
// $fooHandler is available everywhere
} |
@chalasr thanks for the explanation ... but let me show you a counter-example. The ProblemI want to log messages during the command execution The current solution
The proposed solution
|
It's quite clear with auto-wire for services (not so easy from DX side as mentioned by Javier), but remember that container it's not only services, there are also parameters. |
@javiereguiluz @stloyd Fair enough. ContainerAware stays unfortunately the easier way to solve what you said and is probably not that evil in userland. Thanks! I would still like to avoid making use of it in the core (where the container and the whole auto registration are not needed) and register them as services instead, making it easy to identify their scope and discover their dependencies. Any thought on that? Closing due to the mentioned cons. |
Note that there is no need for any The means the only remaining boilerplate is adding a constructor. That's something I'd be happy to accept if I gain dropping this mischievous "extends ContainerAwareCommand". But then, there is item 6.: laziness. We have some new solutions about that: service locators and getter injection could be to the rescue. I share the goal of this issue thought. |
@nicolas-grekas item 4) is the problem to me. If I always think in terms of services (e.g. |
@javiereguiluz I don't get how this is related to commands? You're just describing a problem that is solved by service configuration. Something very usual to SF devs, isn't it? |
Let me explain with an example: Traditionally I use this to get the logger inside a command: $this->getContainer()->get('logger'); So easy! If you want the logger, get the This proposal says that I must do this instead: private $logger;
public function __construct(Logger???? $logger)
{
$this->logger = $logger;
} My problem is ... which class should I put in And if I use channels (e.g. |
In the same way you know that there is something you can "get" and which is called "logger" or "monolog.logger.foo": by reading some doc (which does not exist yet) - or by running a command that does service inspection.
maybe that's easy, but that's hiding and (even worse) hardcoding dependencies - which is a bad practice really (not me saying that - just joining the main stream on that topic). If we don't seek for alternative ways that might have the potential to fix this "bad" part, while making DX as good as possible - same or better than current - I don't see how we can find any. This issue is just about that to me - wondering about a better path. |
@javiereguiluz No matter if the typehint is About targeting a specific channel, I personally use A "more important" issue is parameters as mentioned before, that would require an explicit service registration too. Looking for a way to make this happen without loosing anything that should be possible to achieve . |
I agree with both camps :) My question would be: do we really need to deprecate |
Playing the devil's advocate: we don't force people to use controllers as services, nor we intend to deprecate the base New features like the service locators, or getter injection may help right now, but both are still experimental and require to design your commands "especially for it", rather than using classic dependencies declaration. So IMHO, having lazy-loaded commands is a must have before deprecating this class. |
I'd say yes on the long run, because calling Note that when extending this class,
Yet a big advantage, not a must have IMHO. ContainerAwareCommand and the container itself are actually overused is these commands. Also I think experimental features should be taken into account here, when command has such needs its design should reflect it I think; better have a wired typed getter than a |
I would be very happy with commands where the constructor is 100% mine. Current issues:
|
Way before there is any real intention to deprecate With that said, Symfony has always been extremely capable while remaining non-opinionated and often providing multiple ways to achieve the same effect. I understand where the current mainstream thought is on hard-coding dependencies, but please take a moment to consider whether deprecating something is needed for the sake of maintainability (or some otherwise important reason), or is simply the act of being opinionated about how others should code. Please don't go the opinionated route of Laravel. This is one of the reasons why I love and have pushed every company I work for to use Symfony: it's non-opinionated. To clarify, I'm not against changing and pushing new best-practices. I just don't think we should remove an alternate method altogether without reason. |
It's too early. Let's close |
How do you use the logger service if you don't know the interface? |
the main drawback of commands as a service is that all commands are eager-loaded to get all available command names. If this requires loading the object graph of all commands, it will be a huge nightmare (even more for cache:warmup and cache:clear) and will slow down all commands. So I'm also on the side of not deprecating it for now. |
This is an issue I'd like to see solved either way. I'm using command as a service, but this greatly slows down our CLI functionality. |
@javiereguiluz accessing services through You will say "hey, I know, because I know how MonologBundle works and I know there's You can say then "hey, I can use |
I'm sorry, but in fact you're saying: "I want something, but I don't know what it is". |
@Wirone I have a question for you. Imagine that I'm using Monolog channels. Today I use this: $this->getContainer()->get('monolog.logger.foo') With this proposal, I should use this: private $logger;
public function __construct(XXXXXXX $logger)
{
$this->logger = $logger;
} The question is: which class should I put in |
@javiereguiluz simple |
@unkind are you saying that, if I put this code: private $logger;
public function __construct(\Psr\Logger\LoggerInterface $logger)
{
$this->logger = $logger;
} The logger is going to use the |
|
@javiereguiluz Channels are an implementation detail of the logger: no matter which channel is used by the logger service you injects, your command must work. The only thing that makes sure your command works is that you're using the public api of |
It looks like you're trying to find arguments against auto-wiring feature and not against DI for commands in general. I think that the primary goal of this issue is to get rid of the |
@javiereguiluz Actually: class FooCommand {
public function __construct(LoggerInterface $logger) {
// ...
}
} And one of the following:
App\FooCommand: ['@monolog.logger.foo']
App\FooCommand:
arguments: ['@logger']
tags:
- { name: monolog.logger, channel: foo } Of course this requires more effort than the current approach, but allows for well designed commands respecting good DI practices, that is widely worth it imho. |
@javiereguiluz it doesn't matter if you use channel or not, at the end you will get I could answer your question with question: so how do you use your logger after getting it from container, if you don't know what it is? Or if you know, what's the problem with typehint? But here is full example of command as service with channel-based Monolog injection: namespace Your\App\Bundle\Command;
use Psr\Log\LoggerInterface;
class FooCommand {
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
} # app/config/config.yml
monolog:
channels: ['foo'] # src/Your/App/Bundle/Resources/config/config.yml
services:
foo.command:
class: Your\App\Bundle\Command\FooCommand
arguments: ['@monolog.logger.foo']
tags:
- { name: console.command } At any moment, if you would like to use some custom logger, you can configure DI to inject it to |
I guess @javiereguiluz actually wonders how to achieve this by using autowiring, as autowiring was one of the proposed "replacement to get commands with dependencies without config for newcomers and RAD". You should either declare a dedicated type to use as typehint instead of But that's only a limitation of the autowiring for a very specific case, and doesn't really challenge the issue to me. Just use explicit commands as services definitions for such cases. Your command shouldn't be aware of the logger instance nor channel used. |
I propose to deprecate
ContainerAwareCommand
, that would IMO be a great step in order to fully avoidContainerAware
stuff in final applications as well as in the core.Motivation:
AutowiredCommandInterface
which would be automatically registered as an autowired service instead?Steps would be:
What do you think? I'd be glad to do it.
The text was updated successfully, but these errors were encountered: