diff --git a/src/Illuminate/Routing/RouteGroup.php b/src/Illuminate/Routing/RouteGroup.php index 4dad20d3da7b..18dbb5245b8f 100644 --- a/src/Illuminate/Routing/RouteGroup.php +++ b/src/Illuminate/Routing/RouteGroup.php @@ -20,6 +20,10 @@ public static function merge($new, $old, $prependExistingPrefix = true) unset($old['domain']); } + if (isset($new['controller'])) { + unset($old['controller']); + } + $new = array_merge(static::formatAs($new, $old), [ 'namespace' => static::formatNamespace($new, $old), 'prefix' => static::formatPrefix($new, $old, $prependExistingPrefix), diff --git a/src/Illuminate/Routing/RouteRegistrar.php b/src/Illuminate/Routing/RouteRegistrar.php index 46ba84c80892..64c1359bd61d 100644 --- a/src/Illuminate/Routing/RouteRegistrar.php +++ b/src/Illuminate/Routing/RouteRegistrar.php @@ -17,6 +17,7 @@ * @method \Illuminate\Routing\Route options(string $uri, \Closure|array|string|null $action = null) * @method \Illuminate\Routing\Route any(string $uri, \Closure|array|string|null $action = null) * @method \Illuminate\Routing\RouteRegistrar as(string $value) + * @method \Illuminate\Routing\RouteRegistrar controller(string $controller) * @method \Illuminate\Routing\RouteRegistrar domain(string $value) * @method \Illuminate\Routing\RouteRegistrar middleware(array|string|null $middleware) * @method \Illuminate\Routing\RouteRegistrar name(string $value) @@ -58,6 +59,7 @@ class RouteRegistrar */ protected $allowedAttributes = [ 'as', + 'controller', 'domain', 'middleware', 'name', diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index 64e32373ace6..26f6ec9ba28d 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -515,10 +515,11 @@ protected function convertToControllerAction($action) $action = ['uses' => $action]; } - // Here we'll merge any group "uses" statement if necessary so that the action - // has the proper clause for this property. Then we can simply set the name - // of the controller on the action and return the action array for usage. + // Here we'll merge any group "controller" and "uses" statements if necessary so that + // the action has the proper clause for this property. Then, we can simply set the + // name of this controller on the action plus return the action array for usage. if ($this->hasGroupStack()) { + $action['uses'] = $this->prependGroupController($action['uses']); $action['uses'] = $this->prependGroupNamespace($action['uses']); } @@ -544,6 +545,31 @@ protected function prependGroupNamespace($class) ? $group['namespace'].'\\'.$class : $class; } + /** + * Prepend the last group controller onto the use clause. + * + * @param string $class + * @return string + */ + protected function prependGroupController($class) + { + $group = end($this->groupStack); + + if (! isset($group['controller'])) { + return $class; + } + + if (class_exists($class)) { + return $class; + } + + if (strpos($class, '@') !== false) { + return $class; + } + + return $group['controller'].'@'.$class; + } + /** * Create a new Route object. * diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index 4daf41f813f7..62825149f17d 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -3,9 +3,11 @@ namespace Illuminate\Tests\Routing; use BadMethodCallException; +use FooController; use Illuminate\Container\Container; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Http\Request; +use Illuminate\Routing\Route; use Illuminate\Routing\Router; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -346,6 +348,79 @@ public function testCanRegisterGroupWithDomainAndNamePrefix() $this->assertSame('api.users', $this->getRoute()->getName()); } + public function testCanRegisterGroupWithController() + { + $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) { + $router->get('users', 'index'); + }); + + $this->assertSame( + RouteRegistrarControllerStub::class.'@index', + $this->getRoute()->getAction()['uses'] + ); + } + + public function testCanOverrideGroupControllerWithStringSyntax() + { + $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) { + $router->get('users', 'UserController@index'); + }); + + $this->assertSame( + 'UserController@index', + $this->getRoute()->getAction()['uses'] + ); + } + + public function testCanOverrideGroupControllerWithClosureSyntax() + { + $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) { + $router->get('users', function () { + return 'hello world'; + }); + }); + + $this->seeResponse('hello world', Request::create('users', 'GET')); + } + + public function testCanOverrideGroupControllerWithInvokableControllerSyntax() + { + $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) { + $router->get('users', InvokableRouteRegistrarControllerStub::class); + }); + + $this->assertSame( + InvokableRouteRegistrarControllerStub::class.'@__invoke', + $this->getRoute()->getAction()['uses'] + ); + } + + public function testWillUseTheLatestGroupController() + { + $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) { + $router->group(['controller' => FooController::class], function ($router) { + $router->get('users', 'index'); + }); + }); + + $this->assertSame( + FooController::class.'@index', + $this->getRoute()->getAction()['uses'] + ); + } + + public function testCanOverrideGroupControllerWithArraySyntax() + { + $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) { + $router->get('users', [FooController::class, 'index']); + }); + + $this->assertSame( + FooController::class.'@index', + $this->getRoute()->getAction()['uses'] + ); + } + public function testRouteGroupingWithoutPrefix() { $this->router->group([], function ($router) { @@ -848,6 +923,14 @@ public function destroy() } } +class InvokableRouteRegistrarControllerStub +{ + public function __invoke() + { + return 'controller'; + } +} + class RouteRegistrarMiddlewareStub { //