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

[Console] Added support for lazy-loaded commands#13946

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

Closed
mnapoli wants to merge1 commit intosymfony:2.7frommnapoli:console

Conversation

@mnapoli
Copy link
Contributor

QA
Bug fix?no
New feature?yes
BC breaks?no
Deprecations?no
Tests pass?yes
Fixed tickets#12063
LicenseMIT
Doc PRTBD

This PR adds support for lazy-loaded commands in the Console component (#12063).

I had to refactor the commands to extract the configuration into a separate class. That allows to define the configuration of a command separately from the command itself, which means we don't have to instantiate it. The command is then loaded only when being used. This is particularly useful when writing an application with a dependency injection container.

Backward compatibility is kept (if not it's a bug to fix) asCommand extend from the newCommandConfiguration class (i.e. current commandsare their own configuration). I wish they didn't as composition would be better instead of inheritance here, suggestions are welcome. Maybe we can break BC if we are targeting 3.0 and the change is deemed good enough?

To define a lazy-loaded command, here is a simple example:

$application =newApplication();$configuration =newCommandConfiguration();$configuration    ->setName('foo:bar')    ->setDescription('description')    ->setHelp('help');$configuration->setCommand(function ($configuration) {// You might want to use a DI container herereturnnewGreetCommand(null,$configuration);});$application->addCommandConfiguration($configuration);$application->run();

Lazy-loaded commands don't have to defineconfigure():

class GreetCommandextends Command{protectedfunctionexecute(InputInterface$input,OutputInterface$output)    {$output->writeln('Hello');    }}

I hesitated between using closures that return the Command instance, or introducing aCommandResolver. I went with the simpler solution.

As explained in#12063, the current implementation forces to create all the Command instances which has the following problems for example:

  • a large part of the application might be instantiated for nothing
  • any error happening in the init of the instantiated objects will prevent the application from starting at all
  • any misconfiguration in the DI container will prevent the application from starting at all
  • admin and troubleshooting commands cannot be run if the application doesn't start (e.g. clear caches, start/stop server, show debug information, install the app, disable modules, …)
  • any logic in the object's initialization will be run, which may require external services (database, remote cache, webservice, …) or may have side-effects (which is not good obviously, but it can happen)

The impact and reality of those problems depend a lot of your application obviously. But just like an application shouldn't loadevery controller and their dependencies on each request, the console application shouldn't load every command and their dependencies on each execution if that can be avoided.

I don't believe this change has any impact on the Symfony full-stack framework but maybe it will give you some ideas.

biiccs, voda, tristanbes, tyx, and enumag reacted with thumbs up emoji
Refactored the commands to extract the configuration into a separate class. That allows to define the configuration of a command without instantiating the command. The command is then loaded only when being used.
@mnapoli
Copy link
ContributorAuthor

The build on 2.7 is currently broken which explains the failing PR, I will rebase when it is fixed. Locally tests are passing.

@unkind
Copy link
Contributor

Honestly, I don't understand why we can't (re)use existingCommand->code (Command::setCode) for lazy-loading purpose. Do I miss something?

Anyway, I definitely like to see separateCommandConfiguration class, but it would be great to have immutable object without command dependency.

@mnapoli
Copy link
ContributorAuthor

Honestly, I don't understand why we can't (re)use existing Command->code (Command::setCode) for lazy-loading purpose. Do I miss something?

Yes it's possible but I consider it a cheap workaround rather than a solution. With that you would have to write commands that do not extend theCommand class, you couldn't use helpers for example. And you can't implementinitialize() orinteract().

Instead of putting the command out of theCommand class, I'd rather put the configuration out of theCommand class ;) (makes more sense)

but it would be great to have immutable object without command dependency.

I don't I understand that part sorry?

@unkind
Copy link
Contributor

With that you would have to write commands that do not extend the Command class

Why? Closure can resolve command and execute it:

$command =new newCommand(null,$configuration);$command->setCode(function ($input,$output,$configuration) {// Feel free to resolve command instance from DI container$internal =newGreetCommand(null,$configuration);return$internal->run($input,$output);});

You can add this 3rd argument without breaking BC.

I don't I understand that part sorry?

I'd like to see a value object, not a mutable one.

@mnapoli
Copy link
ContributorAuthor

@unkind Your example is a workaround. That's also the kind of workaround I have implemented for using inPiwik and we are not using it yet (and not released) because "it's not the Symfony API so people have to learn the Piwik console version (and we have to maintain it)".

So yes of course you can always find a way, but here I'm suggesting to improve Symfony Console to have an easy and clean solution available for everyone instead of releasing a "fork" (or rather alternative to symfony/console).

@unkind
Copy link
Contributor

Your example is a workaround

I don't state it isn't. The only thing I don't like in your one is thatCommandConfiguration (meta data) depends on$command instance (or factory). It's likeRoute holds$controller instance (or factory).

AFAIK, Symfony 2.7 is already closed for new features according tohttp://symfony.com/doc/current/contributing/community/releases.html. If it isn't, I can make alternative PR. However, I don't see much sense in these workarounds for 3.0.

@fabpot
Copy link
Member

2.7 will not accept any new features at the end of the month, so we still have time.

@mnapoli
Copy link
ContributorAuthor

@unkind Ah you meanCommandConfiguration::getCommand()?

Yes I don't find this perfect too and I'd rather introduce aCommandResolver like theControllerResolver. However when I suggested that in the original issue (edit: rather I read it somewhere else) I could sense that people where not really keen on "overengineered" solutions (which I don't think it is) so that's why I went this this simple one. If there's more chance for a PR with aCommandResolver then I'll gladly add it.

@unkind
Copy link
Contributor

@mnapoli by the way, you should removeCommand::isEnabled().

@mickaelandrieu
Copy link
Contributor

@mnapoli what is the status of this pull request ?

@mnapoli
Copy link
ContributorAuthor

@mickaelandrieu it is waiting for some love :)

I.e. waiting for a core team member to tell me:

  • never going to happen
  • want to merge this PR but X or Y needs to be fixed
  • discuss alternate implementation, I'm open to implement it differently if needed

@stof
Copy link
Member

I would prefer a way staying closer to the current architecture, as I explained in#13989 (comment) as well

@mnapoli
Copy link
ContributorAuthor

@stof OK I can try a different approach, just to be clear the name and aliases would have to be defined outside the command right?

Here is a solution using callbacks as a factory:

$callable =function ($commandName) {// return the command instance};$app->addLazyCommand('name', ['aliases'],$callable);

Or we could introduce aCommandResolver like theControllerResolver:

interface CommandResolver {publicfunctiongetCommand($name);}$app->setCommandResolver($resolver);$app->addLazyCommand('name', ['aliases'],'command-id');

Thoughts before I go with an implementation?

@notrix
Copy link
Contributor

👍

@linaori
Copy link
Contributor

Any update on this? I'm having major issues right now due to aSoapClient being a dependency of a command. Due to the nature of how the php soap client works, it fails writing the wsdl into the cache with parallel running processes. Because running 1 command, triggers loading the dependencies of all, this fails and crashes.

The only way I can fix it is by fetching the soap service from the container at a later stage and this is something I'm hoping to avoid.

/ping@mnapoli

@mnapoli
Copy link
ContributorAuthor

On my side I have nothing new to add here, I opened the PR and also offered alternative ideas for implementations so I'm waiting for feedback ;)

And since I opened this PR I keep seeing this problem more and more, for example onPiwik as we introduce dependency injection more and more.

@xabbuh
Copy link
Member

@iltar For now, you coulddeclare the SoapClient service lazy, can't you?

@linaori
Copy link
Contributor

@xabbuh it's an option but I actually wrote my own interfaced wrapper for it, which in turn is a nicer solution than it originally was by calling the SoapClient directly. I don't like introducing lazy services via that package as the generated proxies provide some obstacles.

The php SoapClient is just bad by design.

@TomasVotruba
Copy link
Contributor

What state is this in?

@cedvan
Copy link

👍

@mnapoli
Copy link
ContributorAuthor

Have a look also at#22734 which seems to be a very good solution (maybe better than this one because simpler).

@nicolas-grekas
Copy link
Member

Closing since a better approach is being worked on.

@TomasVotruba
Copy link
Contributor

@nicolas-grekas Could you link that one please?

@theofidry
Copy link
Contributor

@TomasVotruba I think he was referring to#22734

nicolas-grekas and TomasVotruba reacted with thumbs up emoji

@TomasVotruba
Copy link
Contributor

@theofidry I prefer links over guessing. Thanks.

@mnapoli
Copy link
ContributorAuthor

🎉 thanks@chalasr

chalasr reacted with heart emoji

nicolas-grekas added a commit that referenced this pull requestJul 12, 2017
This PR was merged into the 3.4 branch.Discussion----------[Console] Add support for command lazy-loading| Q             | A| ------------- | ---| Branch?       | 3.4| Bug fix?      | no| New feature?  | yes| BC breaks?    | no| Deprecations? | no| Tests pass?   | yes| Fixed tickets |#12063#16438#13946#21781| License       | MIT| Doc PR        | todoThis PR adds command lazy-loading support to the console, based on PSR-11 and DI tags.(#12063 (comment))Commands registered as services which set the `command` attribute on their `console.command` tag are now instantiated when calling `Application::get()` instead of all instantiated at `run()`.__Usage__```yamlapp.command.heavy:    tags:        - { name: console.command, command: app:heavy }```This way private command services can be inlined (no public aliases, remain really private).With console+PSR11 implem only:```php$application = new Application();$container = new ServiceLocator(['heavy' => function () { return new Heavy(); }]);$application->setCommandLoader(new ContainerCommandLoader($container, ['app:heavy' => 'heavy']);```Implementation is widely inspired from Twig runtime loaders (without the definition/runtime separation which is not needed here).Commits-------7f97519 Add support for command lazy-loading
symfony-splitter pushed a commit to symfony/security-bundle that referenced this pull requestJul 12, 2017
This PR was merged into the 3.4 branch.Discussion----------[Console] Add support for command lazy-loading| Q             | A| ------------- | ---| Branch?       | 3.4| Bug fix?      | no| New feature?  | yes| BC breaks?    | no| Deprecations? | no| Tests pass?   | yes| Fixed tickets |symfony/symfony#12063symfony/symfony#16438symfony/symfony#13946 #21781| License       | MIT| Doc PR        | todoThis PR adds command lazy-loading support to the console, based on PSR-11 and DI tags.(symfony/symfony#12063 (comment))Commands registered as services which set the `command` attribute on their `console.command` tag are now instantiated when calling `Application::get()` instead of all instantiated at `run()`.__Usage__```yamlapp.command.heavy:    tags:        - { name: console.command, command: app:heavy }```This way private command services can be inlined (no public aliases, remain really private).With console+PSR11 implem only:```php$application = new Application();$container = new ServiceLocator(['heavy' => function () { return new Heavy(); }]);$application->setCommandLoader(new ContainerCommandLoader($container, ['app:heavy' => 'heavy']);```Implementation is widely inspired from Twig runtime loaders (without the definition/runtime separation which is not needed here).Commits-------7f97519 Add support for command lazy-loading
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Projects

None yet

Milestone

3.4

Development

Successfully merging this pull request may close these issues.

13 participants

@mnapoli@unkind@fabpot@mickaelandrieu@stof@notrix@linaori@xabbuh@TomasVotruba@cedvan@nicolas-grekas@theofidry@javiereguiluz

[8]ページ先頭

©2009-2025 Movatter.jp