diff --git a/system/Router/Router.php b/system/Router/Router.php index 4c2ba230f6a0..0348daf10cdc 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -803,8 +803,8 @@ private function processRouteAttributes(): void if ($instance instanceof RouteAttributeInterface) { $this->routeAttributes['class'][] = $instance; } - } catch (Throwable) { - log_message('error', 'Failed to instantiate attribute: ' . $attribute->getName()); + } catch (Throwable $e) { + $this->logRouteAttributeInstantiationFailure($attribute->getName(), $this->controller, null, $e); } } @@ -823,14 +823,43 @@ private function processRouteAttributes(): void if ($instance instanceof RouteAttributeInterface) { $this->routeAttributes['method'][] = $instance; } - } catch (Throwable) { - // Skip attributes that fail to instantiate - log_message('error', 'Failed to instantiate attribute: ' . $attribute->getName()); + } catch (Throwable $e) { + $this->logRouteAttributeInstantiationFailure($attribute->getName(), $this->controller, $this->method, $e); } } } } + /** + * Logs an attribute instantiation failure with the resolved route context. + * + * @param string $attributeName Fully qualified attribute class name. + * @param string $controller Resolved controller class name. + * @param string|null $method Resolved controller method name, if applicable. + */ + private function logRouteAttributeInstantiationFailure( + string $attributeName, + string $controller, + ?string $method, + Throwable $e, + ): void { + $location = $controller; + + if ($method !== null && $method !== '') { + $location .= '::' . $method . '()'; + } + + log_message( + 'error', + 'Failed to instantiate route attribute "{attribute}" on "{location}": {message}', + [ + 'attribute' => $attributeName, + 'location' => $location, + 'message' => $e->getMessage(), + ], + ); + } + /** * Execute beforeController() on all route attributes. * Called by CodeIgniter before controller execution. diff --git a/tests/_support/Router/Controllers/InvalidAttributeController.php b/tests/_support/Router/Controllers/InvalidAttributeController.php new file mode 100644 index 000000000000..9caebad03508 --- /dev/null +++ b/tests/_support/Router/Controllers/InvalidAttributeController.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\Router\Controllers; + +use CodeIgniter\Controller; +use CodeIgniter\HTTP\ResponseInterface; +use CodeIgniter\Router\Attributes\Filter; + +class InvalidAttributeController extends Controller +{ + #[Filter(by: ['auth', 'csrf'])] + public function invalidMultipleFilters(): ResponseInterface + { + return $this->response->setBody('Invalid attributes'); + } +} diff --git a/tests/system/Router/RouterTest.php b/tests/system/Router/RouterTest.php index ef3418f3bcee..7eef0a7569aa 100644 --- a/tests/system/Router/RouterTest.php +++ b/tests/system/Router/RouterTest.php @@ -20,6 +20,7 @@ use CodeIgniter\HTTP\Exceptions\RedirectException; use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\Method; +use CodeIgniter\Router\Attributes\Filter; use CodeIgniter\Router\Exceptions\RouterException; use CodeIgniter\Test\CIUnitTestCase; use Config\App; @@ -29,6 +30,7 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use Tests\Support\Filters\Customfilter; +use Tests\Support\Router\Controllers\InvalidAttributeController; /** * @internal @@ -1003,4 +1005,27 @@ public function testRoutePlaceholderAnyWithMultipleSegmentsParamTrue(): void $this->assertSame('productLookup', $router->methodName()); $this->assertSame(['123/456'], $router->params()); } + + public function testLogsRouteAttributeInstantiationFailureWithContext(): void + { + $collection = clone $this->collection; + $collection->resetRoutes(); + $collection->get( + 'invalid-attribute', + 'Tests\Support\Router\Controllers\InvalidAttributeController::invalidMultipleFilters', + ); + + $router = new Router($collection, $this->request); + + $router->handle('invalid-attribute'); + + $this->assertSame( + '\\' . InvalidAttributeController::class, + $router->controllerName(), + ); + $this->assertSame('invalidMultipleFilters', $router->methodName()); + $this->assertSame([], $router->getFilters()); + $this->assertLogContains('error', 'Failed to instantiate route attribute "' . Filter::class . '" on "\Tests\Support\Router\Controllers\InvalidAttributeController::invalidMultipleFilters()":'); + $this->assertLogContains('error', 'must be of type string'); + } } diff --git a/user_guide_src/source/incoming/controller_attributes/003.php b/user_guide_src/source/incoming/controller_attributes/003.php index ef6c65ac51ca..39725375f4e2 100644 --- a/user_guide_src/source/incoming/controller_attributes/003.php +++ b/user_guide_src/source/incoming/controller_attributes/003.php @@ -18,8 +18,9 @@ public function api() { } - // Multiple filters can be applied - #[Filter(by: ['auth', 'csrf'])] + // Multiple filters can be applied by repeating the attribute + #[Filter(by: 'auth')] + #[Filter(by: 'csrf')] public function admin() { }