From 933151f9f4c2df2f57688d555e53ce4c8740a59e Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 1 May 2018 14:00:11 -0400 Subject: [PATCH 1/3] Load routing in console environments. Ensure that the CommandRunner framework uses the application routes() hook to load routes. This enables the routes shell and other commands/shells to generate URLs properly. Refs #12030 --- src/Console/CommandRunner.php | 19 ++++++++ tests/TestCase/Console/CommandRunnerTest.php | 46 +++++++++++++++----- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/Console/CommandRunner.php b/src/Console/CommandRunner.php index 2b33bf49143..7dc30bbfeb0 100644 --- a/src/Console/CommandRunner.php +++ b/src/Console/CommandRunner.php @@ -26,6 +26,7 @@ use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; use Cake\Event\EventManager; +use Cake\Routing\Router; use Cake\Utility\Inflector; use InvalidArgumentException; use RuntimeException; @@ -145,6 +146,7 @@ public function run(array $argv, ConsoleIo $io = null) } $this->checkCollection($commands, 'pluginConsole'); $this->dispatchEvent('Console.buildCommands', ['commands' => $commands]); + $this->loadRoutes(); if (empty($argv)) { throw new RuntimeException("Cannot run any commands. No arguments received."); @@ -358,4 +360,21 @@ protected function createShell($className, ConsoleIo $io) return $shell; } + + /** + * Ensure that the application's routes are loaded. + * + * Console commands and shells often need to generate URLs. + * + * @return void + */ + protected function loadRoutes() + { + $builder = Router::createRouteBuilder('/'); + + $this->app->routes($builder); + if ($this->app instanceof PluginApplicationInterface) { + $this->app->pluginRoutes($builder); + } + } } diff --git a/tests/TestCase/Console/CommandRunnerTest.php b/tests/TestCase/Console/CommandRunnerTest.php index ffed6405616..7e8ac265e51 100644 --- a/tests/TestCase/Console/CommandRunnerTest.php +++ b/tests/TestCase/Console/CommandRunnerTest.php @@ -24,6 +24,7 @@ use Cake\Event\EventList; use Cake\Event\EventManager; use Cake\Http\BaseApplication; +use Cake\Routing\Router; use Cake\TestSuite\Stub\ConsoleOutput; use Cake\TestSuite\TestCase; use InvalidArgumentException; @@ -160,7 +161,7 @@ public function testRunMissingRootCommand() $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('Cannot run any commands. No arguments received.'); $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap']) + ->setMethods(['middleware', 'bootstrap', 'routes']) ->setConstructorArgs([$this->config]) ->getMock(); @@ -178,7 +179,7 @@ public function testRunInvalidCommand() $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('Unknown command `cake nope`. Run `cake --help` to get the list of valid commands.'); $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap']) + ->setMethods(['middleware', 'bootstrap', 'routes']) ->setConstructorArgs([$this->config]) ->getMock(); @@ -194,7 +195,7 @@ public function testRunInvalidCommand() public function testRunHelpLongOption() { $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap']) + ->setMethods(['middleware', 'bootstrap', 'routes']) ->setConstructorArgs([$this->config]) ->getMock(); @@ -216,7 +217,7 @@ public function testRunHelpLongOption() public function testRunHelpShortOption() { $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap']) + ->setMethods(['middleware', 'bootstrap', 'routes']) ->setConstructorArgs([$this->config]) ->getMock(); @@ -237,7 +238,7 @@ public function testRunHelpShortOption() public function testRunNoCommand() { $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap']) + ->setMethods(['middleware', 'bootstrap', 'routes']) ->setConstructorArgs([$this->config]) ->getMock(); @@ -260,7 +261,7 @@ public function testRunNoCommand() public function testRunVersionAlias() { $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap']) + ->setMethods(['middleware', 'bootstrap', 'routes']) ->setConstructorArgs([$this->config]) ->getMock(); @@ -278,7 +279,7 @@ public function testRunVersionAlias() public function testRunValidCommand() { $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap']) + ->setMethods(['middleware', 'bootstrap', 'routes']) ->setConstructorArgs([$this->config]) ->getMock(); @@ -301,7 +302,7 @@ public function testRunValidCommand() public function testRunValidCommandInflection() { $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap']) + ->setMethods(['middleware', 'bootstrap', 'routes']) ->setConstructorArgs([$this->config]) ->getMock(); @@ -432,7 +433,7 @@ public function testRunValidCommandClassHelp() public function testRunTriggersBuildCommandsEvent() { $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap']) + ->setMethods(['middleware', 'bootstrap', 'routes']) ->setConstructorArgs([$this->config]) ->getMock(); @@ -454,7 +455,10 @@ public function testRunTriggersBuildCommandsEvent() public function testRunCallsPluginHookMethods() { $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap', 'pluginBootstrap', 'pluginEvents', 'pluginConsole']) + ->setMethods([ + 'middleware', 'bootstrap', 'routes', + 'pluginBootstrap', 'pluginConsole', 'pluginRoutes' + ]) ->setConstructorArgs([$this->config]) ->getMock(); @@ -468,6 +472,8 @@ public function testRunCallsPluginHookMethods() ->will($this->returnCallback(function ($commands) { return $commands; })); + $app->expects($this->at(3))->method('routes'); + $app->expects($this->at(4))->method('pluginRoutes'); $output = new ConsoleOutput(); $runner = new CommandRunner($app, 'cake'); @@ -475,10 +481,28 @@ public function testRunCallsPluginHookMethods() $this->assertContains(Configure::version(), $output->messages()[0]); } + /** + * Test that run() loads routing. + * + * @return void + */ + public function testRunLoadsRoutes() + { + $app = $this->getMockBuilder(BaseApplication::class) + ->setMethods(['middleware', 'bootstrap']) + ->setConstructorArgs([TEST_APP . 'config' . DS]) + ->getMock(); + + $output = new ConsoleOutput(); + $runner = new CommandRunner($app, 'cake'); + $runner->run(['cake', '--version'], $this->getMockIo($output)); + $this->assertGreaterThan(2, count(Router::getRouteCollection()->routes())); + } + protected function makeAppWithCommands($commands) { $app = $this->getMockBuilder(BaseApplication::class) - ->setMethods(['middleware', 'bootstrap', 'console']) + ->setMethods(['middleware', 'bootstrap', 'console', 'routes']) ->setConstructorArgs([$this->config]) ->getMock(); $collection = new CommandCollection($commands); From 1f6d066d7b7dff689e335c5e3659af3ea5b1d1e5 Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 1 May 2018 17:08:26 -0400 Subject: [PATCH 2/3] Add check before calling methods that may not exist. --- src/Console/CommandRunner.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Console/CommandRunner.php b/src/Console/CommandRunner.php index 7dc30bbfeb0..80f745159ac 100644 --- a/src/Console/CommandRunner.php +++ b/src/Console/CommandRunner.php @@ -372,7 +372,9 @@ protected function loadRoutes() { $builder = Router::createRouteBuilder('/'); - $this->app->routes($builder); + if ($this->app instanceof HttpApplicationInterface) { + $this->app->routes($builder); + } if ($this->app instanceof PluginApplicationInterface) { $this->app->pluginRoutes($builder); } From f14fa623902e96808696656bc40390fe2db146fe Mon Sep 17 00:00:00 2001 From: Mark Story Date: Thu, 3 May 2018 21:53:34 -0400 Subject: [PATCH 3/3] Add missing import. --- src/Console/CommandRunner.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Console/CommandRunner.php b/src/Console/CommandRunner.php index 80f745159ac..894c9ae4dd1 100644 --- a/src/Console/CommandRunner.php +++ b/src/Console/CommandRunner.php @@ -22,6 +22,7 @@ use Cake\Console\Exception\StopException; use Cake\Console\Shell; use Cake\Core\ConsoleApplicationInterface; +use Cake\Core\HttpApplicationInterface; use Cake\Core\PluginApplicationInterface; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait;