From 51d24dfd35572a70ff82f634961a496b4691d3e5 Mon Sep 17 00:00:00 2001 From: Ben Bjurstrom Date: Thu, 21 May 2026 14:18:02 -0700 Subject: [PATCH 1/3] add metadata to action --- .../Routing/PendingResourceRegistration.php | 16 +++ .../PendingSingletonResourceRegistration.php | 16 +++ src/Illuminate/Routing/ResourceRegistrar.php | 7 ++ src/Illuminate/Routing/Route.php | 30 ++++++ src/Illuminate/Routing/RouteGroup.php | 56 +++++++++- src/Illuminate/Routing/RouteRegistrar.php | 30 +++++- src/Illuminate/Routing/Router.php | 4 + src/Illuminate/Support/Facades/Route.php | 1 + tests/Routing/RouteCollectionTest.php | 20 ++++ tests/Routing/RouteRegistrarTest.php | 100 ++++++++++++++++++ 10 files changed, 278 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Routing/PendingResourceRegistration.php b/src/Illuminate/Routing/PendingResourceRegistration.php index a190538e9536..4a1ad192194f 100644 --- a/src/Illuminate/Routing/PendingResourceRegistration.php +++ b/src/Illuminate/Routing/PendingResourceRegistration.php @@ -241,6 +241,22 @@ public function where($wheres) return $this; } + /** + * Add metadata to the registered resource routes. + * + * @param array $metadata + * @return $this + */ + public function metadata(array $metadata) + { + $this->options['metadata'] = RouteGroup::mergeMetadata( + $this->options['metadata'] ?? [], + $metadata + ); + + return $this; + } + /** * Indicate that the resource routes should have "shallow" nesting. * diff --git a/src/Illuminate/Routing/PendingSingletonResourceRegistration.php b/src/Illuminate/Routing/PendingSingletonResourceRegistration.php index 2c20e3c3168f..c9ff6c7539e8 100644 --- a/src/Illuminate/Routing/PendingSingletonResourceRegistration.php +++ b/src/Illuminate/Routing/PendingSingletonResourceRegistration.php @@ -265,6 +265,22 @@ public function where($wheres) return $this; } + /** + * Add metadata to the registered singleton resource routes. + * + * @param array $metadata + * @return $this + */ + public function metadata(array $metadata) + { + $this->options['metadata'] = RouteGroup::mergeMetadata( + $this->options['metadata'] ?? [], + $metadata + ); + + return $this; + } + /** * Register the singleton resource route. * diff --git a/src/Illuminate/Routing/ResourceRegistrar.php b/src/Illuminate/Routing/ResourceRegistrar.php index 82ccc1620b83..ebab33b0f9b6 100644 --- a/src/Illuminate/Routing/ResourceRegistrar.php +++ b/src/Illuminate/Routing/ResourceRegistrar.php @@ -656,6 +656,13 @@ protected function getResourceAction($resource, $controller, $method, $options) $action['missing'] = $options['missing']; } + if (isset($options['metadata'])) { + $action['metadata'] = RouteGroup::mergeMetadata( + $action['metadata'] ?? [], + $options['metadata'] + ); + } + return $action; } diff --git a/src/Illuminate/Routing/Route.php b/src/Illuminate/Routing/Route.php index 341d07f0e36b..3a81e461f681 100755 --- a/src/Illuminate/Routing/Route.php +++ b/src/Illuminate/Routing/Route.php @@ -993,6 +993,36 @@ public function getAction($key = null) return Arr::get($this->action, $key); } + /** + * Add metadata to the route. + * + * @param array $metadata + * @return $this + */ + public function metadata(array $metadata) + { + $this->action['metadata'] = RouteGroup::mergeMetadata( + $this->action['metadata'] ?? [], + $metadata + ); + + return $this; + } + + /** + * Get metadata for the route. + * + * @param string|null $key + * @param mixed $default + * @return mixed + */ + public function getMetadata($key = null, $default = null) + { + $metadata = $this->action['metadata'] ?? []; + + return is_null($key) ? $metadata : Arr::get($metadata, $key, $default); + } + /** * Set the action array for the route. * diff --git a/src/Illuminate/Routing/RouteGroup.php b/src/Illuminate/Routing/RouteGroup.php index cca24b29234d..d9a49e3b25b6 100644 --- a/src/Illuminate/Routing/RouteGroup.php +++ b/src/Illuminate/Routing/RouteGroup.php @@ -25,16 +25,70 @@ public static function merge($new, $old, $prependExistingPrefix = true) } $new = array_merge(static::formatAs($new, $old), [ + 'metadata' => static::formatMetadata($new, $old), 'namespace' => static::formatNamespace($new, $old), 'prefix' => static::formatPrefix($new, $old, $prependExistingPrefix), 'where' => static::formatWhere($new, $old), ]); return array_merge_recursive(Arr::except( - $old, ['namespace', 'prefix', 'where', 'as'] + $old, ['metadata', 'namespace', 'prefix', 'where', 'as'] ), $new); } + /** + * Merge route metadata arrays. + * + * @param array ...$metadata + * @return array + */ + public static function mergeMetadata(...$metadata) + { + $merged = []; + + foreach ($metadata as $metadata) { + foreach ($metadata as $key => $value) { + if (isset($merged[$key]) && static::mergesMetadata($merged[$key], $value)) { + $value = static::mergeMetadata($merged[$key], $value); + } + + $merged[$key] = $value; + } + } + + return $merged; + } + + /** + * Determine if the given metadata values should be merged. + * + * @param mixed $old + * @param mixed $new + * @return bool + */ + protected static function mergesMetadata($old, $new) + { + return is_array($old) && + is_array($new) && + Arr::isAssoc($old) && + Arr::isAssoc($new); + } + + /** + * Format the metadata for the new group attributes. + * + * @param array $new + * @param array $old + * @return array + */ + protected static function formatMetadata($new, $old) + { + return static::mergeMetadata( + $old['metadata'] ?? [], + $new['metadata'] ?? [] + ); + } + /** * Format the namespace for the new group attributes. * diff --git a/src/Illuminate/Routing/RouteRegistrar.php b/src/Illuminate/Routing/RouteRegistrar.php index 6da02f501fef..5da35a0089bf 100644 --- a/src/Illuminate/Routing/RouteRegistrar.php +++ b/src/Illuminate/Routing/RouteRegistrar.php @@ -22,6 +22,7 @@ * @method \Illuminate\Routing\RouteRegistrar can(\UnitEnum|string $ability, array|string $models = []) * @method \Illuminate\Routing\RouteRegistrar controller(string $controller) * @method \Illuminate\Routing\RouteRegistrar domain(\BackedEnum|string $value) + * @method \Illuminate\Routing\RouteRegistrar metadata(array $metadata) * @method \Illuminate\Routing\RouteRegistrar middleware(array|string|null $middleware) * @method \Illuminate\Routing\RouteRegistrar missing(\Closure $missing) * @method \Illuminate\Routing\RouteRegistrar name(\BackedEnum|string $value) @@ -149,6 +150,22 @@ public function attribute($key, $value) return $this; } + /** + * Add metadata to routes registered by the registrar. + * + * @param array $metadata + * @return $this + */ + public function metadata(array $metadata) + { + $this->attributes['metadata'] = RouteGroup::mergeMetadata( + $this->attributes['metadata'] ?? [], + $metadata + ); + + return $this; + } + /** * Route a resource to a controller. * @@ -272,7 +289,18 @@ protected function compileAction($action) ]; } - return array_merge($this->attributes, $action); + $metadata = RouteGroup::mergeMetadata( + $this->attributes['metadata'] ?? [], + $action['metadata'] ?? [] + ); + + $action = array_merge($this->attributes, $action); + + if ($metadata !== []) { + $action['metadata'] = $metadata; + } + + return $action; } /** diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index 24ef495b398b..085f137befc6 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -1520,6 +1520,10 @@ public function __call($method, $parameters) return (new RouteRegistrar($this))->attribute($method, [$parameters]); } + if ($method === 'metadata') { + return (new RouteRegistrar($this))->metadata(...$parameters); + } + if ($method !== 'where' && Str::startsWith($method, 'where')) { return (new RouteRegistrar($this))->{$method}(...$parameters); } diff --git a/src/Illuminate/Support/Facades/Route.php b/src/Illuminate/Support/Facades/Route.php index 7b0e1c0c5280..f6a8232d7a0c 100755 --- a/src/Illuminate/Support/Facades/Route.php +++ b/src/Illuminate/Support/Facades/Route.php @@ -83,6 +83,7 @@ * @method static mixed macroCall(string $method, array $parameters) * @method static \Illuminate\Support\HigherOrderTapProxy|\Illuminate\Routing\Router tap(callable|null $callback = null) * @method static \Illuminate\Routing\RouteRegistrar attribute(string $key, mixed $value) + * @method static \Illuminate\Routing\RouteRegistrar metadata(array $metadata) * @method static \Illuminate\Routing\RouteRegistrar whereAlpha(array|string $parameters) * @method static \Illuminate\Routing\RouteRegistrar whereAlphaNumeric(array|string $parameters) * @method static \Illuminate\Routing\RouteRegistrar whereNumber(array|string $parameters) diff --git a/tests/Routing/RouteCollectionTest.php b/tests/Routing/RouteCollectionTest.php index f84dae01a10e..d495ccc3b1ca 100644 --- a/tests/Routing/RouteCollectionTest.php +++ b/tests/Routing/RouteCollectionTest.php @@ -3,9 +3,12 @@ namespace Illuminate\Tests\Routing; use ArrayIterator; +use Illuminate\Container\Container; +use Illuminate\Events\Dispatcher; use Illuminate\Http\Request; use Illuminate\Routing\Route; use Illuminate\Routing\RouteCollection; +use Illuminate\Routing\Router; use LogicException; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; @@ -284,6 +287,23 @@ public function testCannotCacheDuplicateRouteNames() $this->routeCollection->compile(); } + public function testCompiledRouteCollectionPreservesRouteMetadata() + { + $this->routeCollection->add( + new Route('GET', 'users', [ + 'uses' => 'UsersController@index', + 'as' => 'users', + 'metadata' => ['head' => ['title' => 'Users']], + ]) + ); + + $route = $this->routeCollection + ->toCompiledRouteCollection(new Router(new Dispatcher, new Container), new Container) + ->getByName('users'); + + $this->assertSame(['title' => 'Users'], $route->getMetadata('head')); + } + public function testRouteCollectionDontMatchNonMatchingDoubleSlashes() { $this->expectException(NotFoundHttpException::class); diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index f94ca8e55c54..e1d38308011d 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -572,6 +572,72 @@ public function testCanSetScopeBindingsOnGroup() $this->assertTrue($route->enforcesScopedBindings()); } + public function testCanSetRouteMetadata() + { + $route = $this->router + ->metadata(['head' => ['title' => 'Users']]) + ->get('users', function () { + return 'all-users'; + }) + ->metadata(['head' => ['description' => 'All users.']]); + + $this->assertSame([ + 'title' => 'Users', + 'description' => 'All users.', + ], $route->getMetadata('head')); + $this->assertSame('Users', $route->getMetadata('head.title')); + } + + public function testCanSetRouteMetadataOnGroup() + { + $this->router + ->metadata(['head' => ['robots' => ['noindex', 'nofollow']]]) + ->group(function ($router) { + $router + ->metadata(['head' => ['title' => 'Users']]) + ->get('users', function () { + return 'all-users'; + }); + }); + + $route = $this->router->getRoutes()->getRoutes()[0]; + + $this->assertSame([ + 'robots' => ['noindex', 'nofollow'], + 'title' => 'Users', + ], $route->getMetadata('head')); + } + + public function testRouteMetadataListValuesReplaceParentValues() + { + $this->router + ->metadata(['head' => ['robots' => ['index', 'follow']]]) + ->group(function ($router) { + $router + ->metadata(['head' => ['robots' => ['noindex']]]) + ->get('users', function () { + return 'all-users'; + }); + }); + + $route = $this->router->getRoutes()->getRoutes()[0]; + + $this->assertSame(['noindex'], $route->getMetadata('head.robots')); + } + + public function testRouteMetadataDoesNotCollideWithRouteActions() + { + $route = $this->router + ->middleware('web') + ->metadata(['middleware' => 'metadata']) + ->get('users', function () { + return 'all-users'; + }); + + $this->assertSame('metadata', $route->getMetadata('middleware')); + $this->assertSame(['web'], $route->getAction('middleware')); + } + public function testCanRegisterResource() { $this->router->middleware('resource-middleware') @@ -581,6 +647,29 @@ public function testCanRegisterResource() $this->seeMiddleware('resource-middleware'); } + public function testCanSetRouteMetadataOnResource() + { + $this->router->resource('users', RouteRegistrarControllerStub::class) + ->metadata(['head' => ['title' => 'Users']]); + + $this->assertSame( + ['title' => 'Users'], + $this->router->getRoutes()->getByName('users.index')->getMetadata('head') + ); + } + + public function testCanSetRouteMetadataOnResourceGroup() + { + $this->router + ->metadata(['head' => ['title' => 'Users']]) + ->resource('users', RouteRegistrarControllerStub::class); + + $this->assertSame( + ['title' => 'Users'], + $this->router->getRoutes()->getByName('users.index')->getMetadata('head') + ); + } + public function testCanRegisterResourcesWithExceptOption() { $this->router->resources([ @@ -1350,6 +1439,17 @@ public function testCanRegisterSingleton() $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.update')); } + public function testCanSetRouteMetadataOnSingleton() + { + $this->router->singleton('user', RouteRegistrarControllerStub::class) + ->metadata(['head' => ['title' => 'User']]); + + $this->assertSame( + ['title' => 'User'], + $this->router->getRoutes()->getByName('user.show')->getMetadata('head') + ); + } + public function testCanRegisterApiSingleton() { $this->router->apiSingleton('user', RouteRegistrarControllerStub::class); From 8e76f95aa89c1e24b1211af0fc6c8927e2a0d098 Mon Sep 17 00:00:00 2001 From: Ben Bjurstrom Date: Thu, 21 May 2026 15:20:32 -0700 Subject: [PATCH 2/3] simplify merge metadata --- src/Illuminate/Routing/Route.php | 13 +++++ src/Illuminate/Routing/RouteGroup.php | 21 ++++---- src/Illuminate/Routing/RouteRegistrar.php | 15 +++--- src/Illuminate/Routing/Router.php | 4 -- tests/Routing/RouteRegistrarTest.php | 60 +++++++++++++++++++++++ 5 files changed, 91 insertions(+), 22 deletions(-) diff --git a/src/Illuminate/Routing/Route.php b/src/Illuminate/Routing/Route.php index 3a81e461f681..d8ef3171c46f 100755 --- a/src/Illuminate/Routing/Route.php +++ b/src/Illuminate/Routing/Route.php @@ -1009,6 +1009,19 @@ public function metadata(array $metadata) return $this; } + /** + * Set the metadata for the route, replacing any existing metadata. + * + * @param array $metadata + * @return $this + */ + public function setMetadata(array $metadata) + { + $this->action['metadata'] = $metadata; + + return $this; + } + /** * Get metadata for the route. * diff --git a/src/Illuminate/Routing/RouteGroup.php b/src/Illuminate/Routing/RouteGroup.php index d9a49e3b25b6..8ca7f75edf08 100644 --- a/src/Illuminate/Routing/RouteGroup.php +++ b/src/Illuminate/Routing/RouteGroup.php @@ -39,24 +39,21 @@ public static function merge($new, $old, $prependExistingPrefix = true) /** * Merge route metadata arrays. * - * @param array ...$metadata + * @param array $old + * @param array $new * @return array */ - public static function mergeMetadata(...$metadata) + public static function mergeMetadata(array $old, array $new) { - $merged = []; - - foreach ($metadata as $metadata) { - foreach ($metadata as $key => $value) { - if (isset($merged[$key]) && static::mergesMetadata($merged[$key], $value)) { - $value = static::mergeMetadata($merged[$key], $value); - } - - $merged[$key] = $value; + foreach ($new as $key => $value) { + if (isset($old[$key]) && static::mergesMetadata($old[$key], $value)) { + $value = static::mergeMetadata($old[$key], $value); } + + $old[$key] = $value; } - return $merged; + return $old; } /** diff --git a/src/Illuminate/Routing/RouteRegistrar.php b/src/Illuminate/Routing/RouteRegistrar.php index 5da35a0089bf..8e062c58b998 100644 --- a/src/Illuminate/Routing/RouteRegistrar.php +++ b/src/Illuminate/Routing/RouteRegistrar.php @@ -73,6 +73,7 @@ class RouteRegistrar 'can', 'controller', 'domain', + 'metadata', 'middleware', 'missing', 'name', @@ -129,6 +130,13 @@ public function attribute($key, $value) } } + if ($key === 'metadata') { + $value = RouteGroup::mergeMetadata( + $this->attributes['metadata'] ?? [], + is_array($value) ? $value : [] + ); + } + $attributeKey = Arr::get($this->aliases, $key, $key); if ($key === 'withoutMiddleware') { @@ -158,12 +166,7 @@ public function attribute($key, $value) */ public function metadata(array $metadata) { - $this->attributes['metadata'] = RouteGroup::mergeMetadata( - $this->attributes['metadata'] ?? [], - $metadata - ); - - return $this; + return $this->attribute('metadata', $metadata); } /** diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index 085f137befc6..24ef495b398b 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -1520,10 +1520,6 @@ public function __call($method, $parameters) return (new RouteRegistrar($this))->attribute($method, [$parameters]); } - if ($method === 'metadata') { - return (new RouteRegistrar($this))->metadata(...$parameters); - } - if ($method !== 'where' && Str::startsWith($method, 'where')) { return (new RouteRegistrar($this))->{$method}(...$parameters); } diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index e1d38308011d..d2f11f729e12 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -638,6 +638,44 @@ public function testRouteMetadataDoesNotCollideWithRouteActions() $this->assertSame(['web'], $route->getAction('middleware')); } + public function testRouteMetadataMergesThroughDeeplyNestedGroups() + { + $this->router + ->metadata(['head' => ['title' => 'Outer', 'description' => 'Outer description']]) + ->group(function ($router) { + $router + ->metadata(['head' => ['title' => 'Middle', 'author' => 'Taylor']]) + ->group(function ($router) { + $router + ->metadata(['head' => ['title' => 'Inner']]) + ->get('users', function () { + return 'all-users'; + }); + }); + }); + + $route = $this->router->getRoutes()->getRoutes()[0]; + + $this->assertSame([ + 'title' => 'Inner', + 'description' => 'Outer description', + 'author' => 'Taylor', + ], $route->getMetadata('head')); + } + + public function testSetMetadataReplacesExistingMetadata() + { + $route = $this->router + ->metadata(['head' => ['title' => 'Original', 'description' => 'Goes away']]) + ->get('users', function () { + return 'all-users'; + }); + + $route->setMetadata(['head' => ['title' => 'Replaced']]); + + $this->assertSame(['head' => ['title' => 'Replaced']], $route->getMetadata()); + } + public function testCanRegisterResource() { $this->router->middleware('resource-middleware') @@ -670,6 +708,17 @@ public function testCanSetRouteMetadataOnResourceGroup() ); } + public function testCanSetRouteMetadataOnApiResource() + { + $this->router->apiResource('users', RouteRegistrarControllerStub::class) + ->metadata(['head' => ['title' => 'Users']]); + + $this->assertSame( + ['title' => 'Users'], + $this->router->getRoutes()->getByName('users.index')->getMetadata('head') + ); + } + public function testCanRegisterResourcesWithExceptOption() { $this->router->resources([ @@ -1460,6 +1509,17 @@ public function testCanRegisterApiSingleton() $this->assertTrue($this->router->getRoutes()->hasNamedRoute('user.update')); } + public function testCanSetRouteMetadataOnApiSingleton() + { + $this->router->apiSingleton('user', RouteRegistrarControllerStub::class) + ->metadata(['head' => ['title' => 'User']]); + + $this->assertSame( + ['title' => 'User'], + $this->router->getRoutes()->getByName('user.show')->getMetadata('head') + ); + } + public function testCanRegisterCreatableSingleton() { $this->router->singleton('user', RouteRegistrarControllerStub::class)->creatable(); From 93fb40d25cbd9af63531e0d778b3fb8bdc9bbbcf Mon Sep 17 00:00:00 2001 From: Ben Bjurstrom Date: Tue, 16 Jun 2026 09:54:28 -0700 Subject: [PATCH 3/3] normalize route metadata groups --- src/Illuminate/Routing/RouteGroup.php | 12 +++++-- src/Illuminate/Routing/RouteRegistrar.php | 7 +++-- tests/Routing/RouteRegistrarTest.php | 38 +++++++++++++++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Routing/RouteGroup.php b/src/Illuminate/Routing/RouteGroup.php index 8ca7f75edf08..6624e3396980 100644 --- a/src/Illuminate/Routing/RouteGroup.php +++ b/src/Illuminate/Routing/RouteGroup.php @@ -24,20 +24,28 @@ public static function merge($new, $old, $prependExistingPrefix = true) unset($old['controller']); } + $metadata = static::formatMetadata($new, $old); + + unset($new['metadata']); + $new = array_merge(static::formatAs($new, $old), [ - 'metadata' => static::formatMetadata($new, $old), 'namespace' => static::formatNamespace($new, $old), 'prefix' => static::formatPrefix($new, $old, $prependExistingPrefix), 'where' => static::formatWhere($new, $old), ]); + if ($metadata !== []) { + $new['metadata'] = $metadata; + } + return array_merge_recursive(Arr::except( $old, ['metadata', 'namespace', 'prefix', 'where', 'as'] ), $new); } /** - * Merge route metadata arrays. + * Associative array values are merged recursively, while all other + * values, including lists, replace the existing value entirely. * * @param array $old * @param array $new diff --git a/src/Illuminate/Routing/RouteRegistrar.php b/src/Illuminate/Routing/RouteRegistrar.php index 8e062c58b998..5cdc7f39d671 100644 --- a/src/Illuminate/Routing/RouteRegistrar.php +++ b/src/Illuminate/Routing/RouteRegistrar.php @@ -131,9 +131,12 @@ public function attribute($key, $value) } if ($key === 'metadata') { + if (! is_array($value)) { + throw new InvalidArgumentException('Attribute [metadata] expects an array.'); + } + $value = RouteGroup::mergeMetadata( - $this->attributes['metadata'] ?? [], - is_array($value) ? $value : [] + $this->attributes['metadata'] ?? [], $value ); } diff --git a/tests/Routing/RouteRegistrarTest.php b/tests/Routing/RouteRegistrarTest.php index d2f11f729e12..4194d691bbf3 100644 --- a/tests/Routing/RouteRegistrarTest.php +++ b/tests/Routing/RouteRegistrarTest.php @@ -8,6 +8,7 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Http\Request; use Illuminate\Routing\Router; +use Illuminate\Routing\RouteRegistrar; use Mockery as m; use PHPUnit\Framework\TestCase; use Stringable; @@ -625,6 +626,43 @@ public function testRouteMetadataListValuesReplaceParentValues() $this->assertSame(['noindex'], $route->getMetadata('head.robots')); } + public function testCanSetRouteMetadataOnGroupUsingArraySyntax() + { + $this->router->group(['metadata' => ['head' => ['title' => 'Users']]], function ($router) { + $router->get('users', function () { + return 'all-users'; + }); + }); + + $route = $this->router->getRoutes()->getRoutes()[0]; + + $this->assertSame(['title' => 'Users'], $route->getMetadata('head')); + } + + public function testEmptyRouteMetadataArrayReplacesParentValue() + { + $this->router + ->metadata(['head' => ['title' => 'Users']]) + ->group(function ($router) { + $router + ->metadata(['head' => []]) + ->get('users', function () { + return 'all-users'; + }); + }); + + $route = $this->router->getRoutes()->getRoutes()[0]; + + $this->assertSame([], $route->getMetadata('head')); + } + + public function testRouteMetadataAttributeRequiresArray() + { + $this->expectExceptionObject(new \InvalidArgumentException('Attribute [metadata] expects an array.')); + + (new RouteRegistrar($this->router))->attribute('metadata', 'invalid'); + } + public function testRouteMetadataDoesNotCollideWithRouteActions() { $route = $this->router