cakephp: Command class is missing a dispatchCommand method

This is a

  • feature-discussion (RFC)

  • CakePHP Version: 3.6

  • Platform and Target: N\A

What you did

I was able to dispatch a shell within another shell as $this->dispatchShell('schema', 'create', 'Blog', '--plugin', 'Blog');

Since the class is deprecated I tried to achieve the same thing with console commands.

What happened

The dispatchCommand method is missing from the Command class.

What you expected to happen

I usually separate commands in granular classes and code a few commands to use them in different aspects according to arguments.
As an example;

class DeployCommand extends Command
{
    public function buildOptionParser(ConsoleOptionParser $parser)
    {
        $parser = parent::buildOptionParser($parser);
        $parser
            ->addOption('type', [
                'help' => 'type of deployment',
                'required' => true,
                'choices' => [
                    'cms', 'app'
                ]
            ]);

        return $parser;
    }

    public function execute(Arguments $args, ConsoleIo $io)
    {
         if($args->getOption('type') === 'cms') {
            // call deploy cms command here
        } else {
            // call deploy cms here
            // call deploy app also here
        }
    }
}

So it would be useful to have a dispatchCommand method just like dispatchShell method.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 17 (13 by maintainers)

Commits related to this issue

Most upvoted comments

I was thinking a bit more about this, and while sub-processes would cover a few of the scenarios described here, it would not make accepting interactive input easy to work with. The generated sub-processes could attach to the current process’ stdin/stdout easily enough, but testing becomes challenging, as the sub-process would not be controlled by the test suite directly.

Running ‘sub-shells’ in the same PHP process presents some problems around isolation and error handling, but makes testing much simpler. I’ve been working on converting bake commands recently and have made good use of a pattern that looks like:

// In a command
$subcommand = new OtherCommand();
$newArgs = new Arguments(['widgets'], ['verbose' => true], ['name']);
$subcommand->execute($modelArgs, $io);

This pattern allows the calling command to pass in its ConsoleIo instance making tests easy, and enables commands to be constructed as needed (either from a DI container, or directly). This pattern could be wrapped into an abstraction that looks like:

// In a command. Call another command by classname
$this->executeCommand(OtherCommand::class, $io, ['widgets', '--verbose', true]);

// Or call another command by instance.
$other = new OtherCommand();
$this->executeCommand($other, $io, ['widgets', '--verbose', true]);

The executeCommand method would allow other commands, (but not shells) to be called. The executeCommand method would:

  • Create the instance if a class name is passed.
  • Build an option parser for the command, and use it to parse the argument array.
  • Run the execute method on the command and return the command’s return code.

What do folks think of that API?

Is there no way we can use internal dispatching passing around input/output and stack this way? More similar to the old shells?

I’m sure we could create a way. But why simulate subprocesses if we can use actual sub-processes. Instead of having folks use symfony/process we could provide a method on Command that opens a subprocess with the current process’s ENV data.

Would providing wrappers around running other shells/commands as subprocesses work? It would cover the application cron daemon scenario well.

One challenge I have had with invoking other shells is that they run in the same process space and their failure/global state mutation will effect the calling shell.

Instead of dispatching commands from within commands I’d suggest refactoring your code so the reusable logic is not tied to the console anymore.

Dispatching commands from another command doesn’t sound like a good idea and we should avoid that.