Browse Source

Add Command::executeCommand()

This method enables commands to dispatch each other without needing to
use the CommandRunner. This provides commands with the ability to be
composed from other commands.

Refs #12549
Mark Story 7 years ago
parent
commit
65b407f8ae

+ 30 - 0
src/Console/Command.php

@@ -249,4 +249,34 @@ class Command
     {
         throw new StopException('Command aborted', $code);
     }
+
+    /**
+     * Execute another command with the provided set of arguments.
+     *
+     * @param string|\Cake\Console\Command $command The command class name or command instance.
+     * @param \Cake\Console\ConsoleIo $io The ConsoleIo instance to use for the executed command.
+     * @param array $args The arguments to invoke the command with.
+     * @return int|null The exit code or null for success of the command.
+     */
+    public function executeCommand($command, ConsoleIo $io, array $args = [])
+    {
+        if (is_string($command)) {
+            if (!class_exists($command)) {
+                throw new InvalidArgumentException("Command class '{$command}' does not exist.");
+            }
+            $command = new $command();
+        }
+        if (!$command instanceof Command) {
+            $commandType = getTypeName($command);
+            throw new InvalidArgumentException(
+                "Command '{$commandType}' is not a subclass of Cake\Console\Command."
+            );
+        }
+
+        try {
+            return $command->run($args, $io);
+        } catch (StopException $e) {
+            return $e->getCode();
+        }
+    }
 }

+ 83 - 0
tests/TestCase/Console/CommandTest.php

@@ -21,6 +21,7 @@ use Cake\ORM\Locator\TableLocator;
 use Cake\ORM\Table;
 use Cake\TestSuite\Stub\ConsoleOutput;
 use Cake\TestSuite\TestCase;
+use InvalidArgumentException;
 use TestApp\Command\AutoLoadModelCommand;
 use TestApp\Command\DemoCommand;
 
@@ -273,6 +274,88 @@ class CommandTest extends TestCase
         $command->abort(99);
     }
 
+    /**
+     * test executeCommand with a string class
+     *
+     * @return void
+     */
+    public function testExecuteCommandString()
+    {
+        $output = new ConsoleOutput();
+        $command = new Command();
+        $result = $command->executeCommand(DemoCommand::class, $this->getMockIo($output));
+        $this->assertNull($result);
+        $this->assertEquals(['Quiet!', 'Demo Command!'], $output->messages());
+    }
+
+    /**
+     * test executeCommand with an invalid string class
+     *
+     * @return void
+     */
+    public function testExecuteCommandStringInvalid()
+    {
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage("Command class 'Nope' does not exist");
+
+        $command = new Command();
+        $command->executeCommand('Nope', $this->getMockIo(new ConsoleOutput()));
+    }
+
+    /**
+     * test executeCommand with arguments
+     *
+     * @return void
+     */
+    public function testExecuteCommandArguments()
+    {
+        $output = new ConsoleOutput();
+        $command = new Command();
+        $command->executeCommand(DemoCommand::class, $this->getMockIo($output), ['Jane']);
+        $this->assertEquals(['Quiet!', 'Demo Command!', 'Jane'], $output->messages());
+    }
+
+    /**
+     * test executeCommand with arguments
+     *
+     * @return void
+     */
+    public function testExecuteCommandArgumentsOptions()
+    {
+        $output = new ConsoleOutput();
+        $command = new Command();
+        $command->executeCommand(DemoCommand::class, $this->getMockIo($output), ['--quiet', 'Jane']);
+        $this->assertEquals(['Quiet!'], $output->messages());
+    }
+
+    /**
+     * test executeCommand with an instance
+     *
+     * @return void
+     */
+    public function testExecuteCommandInstance()
+    {
+        $output = new ConsoleOutput();
+        $command = new Command();
+        $result = $command->executeCommand(new DemoCommand(), $this->getMockIo($output));
+        $this->assertNull($result);
+        $this->assertEquals(['Quiet!', 'Demo Command!'], $output->messages());
+    }
+
+    /**
+     * test executeCommand with an invalid instance
+     *
+     * @return void
+     */
+    public function testExecuteCommandInstanceInvalid()
+    {
+        $this->expectException(InvalidArgumentException::class);
+        $this->expectExceptionMessage("Command 'stdClass' is not a subclass");
+
+        $command = new Command();
+        $command->executeCommand(new \stdClass, $this->getMockIo(new ConsoleOutput()));
+    }
+
     protected function getMockIo($output)
     {
         $io = $this->getMockBuilder(ConsoleIo::class)

+ 3 - 0
tests/test_app/TestApp/Command/DemoCommand.php

@@ -12,5 +12,8 @@ class DemoCommand extends Command
         $io->quiet('Quiet!');
         $io->out('Demo Command!');
         $io->verbose('Verbose!');
+        if ($args->hasArgumentAt(0)) {
+            $io->out($args->getArgumentAt(0));
+        }
     }
 }