-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[HttpKernel] Better exception page when the controller returns nothing #27138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
69dd0c0
to
31a63b9
Compare
throw $e; | ||
} | ||
|
||
$r = new \ReflectionProperty(\Exception::class, 'trace'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using a rebound closure instead of reflection? It'd make things easier to read IMHO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Warning: Cannot bind closure to scope of internal class Exception
$r->setAccessible(true); | ||
$r->setValue($e, array_merge(array( | ||
array( | ||
'line' => $e->getLine(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be even more accurate, passing the line of the call_user_fun above (using __LINE__
next to it.)
Aren't they other keys we should put in the frame? How does it look when thrown from inside the controller?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
} | ||
8000 |
|
|
if (is_object($controller)) { | ||
$r = new \ReflectionClass($controller); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
method __invoke()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
BTW, I adapted the code from
symfony/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php
Lines 373 to 425 in 667924c
protected function parseController($controller) | |
{ | |
if (is_string($controller) && false !== strpos($controller, '::')) { | |
$controller = explode('::', $controller); | |
} | |
if (is_array($controller)) { | |
try { | |
$r = new \ReflectionMethod($controller[0], $controller[1]); | |
return array( | |
'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], | |
'method' => $controller[1], | |
'file' => $r->getFileName(), | |
'line' => $r->getStartLine(), | |
); | |
} catch (\ReflectionException $e) { | |
if (is_callable($controller)) { | |
// using __call or __callStatic | |
return array( | |
'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], | |
'method' => $controller[1], | |
'file' => 'n/a', | |
'line' => 'n/a', | |
); | |
} | |
} | |
} | |
if ($controller instanceof \Closure) { | |
$r = new \ReflectionFunction($controller); | |
return array( | |
'class' => $r->getName(), | |
'method' => null, | |
'file' => $r->getFileName(), | |
'line' => $r->getStartLine(), | |
); | |
} | |
if (is_object($controller)) { | |
$r = new \ReflectionClass($controller); | |
return array( | |
'class' => $r->getName(), | |
'method' => null, | |
'file' => $r->getFileName(), | |
'line' => $r->getStartLine(), | |
); | |
} | |
return is_string($controller) ? $controller : 'n/a'; | |
} |
31a63b9
to
93ecc0b
Compare
@@ -146,7 +146,7 @@ private function handleRaw(Request $request, int $type = self::MASTER_REQUEST) | |||
$arguments = $event->getArguments(); | |||
|
|||
// call controller | |||
$response = \call_user_func_array($controller, $arguments); | |||
$response = \call_user_func_array($controller, $arguments); $line = __LINE__; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would put this on the next line using $line = __LINE__ - 1;
instead. The current code does not comply with our coding standards, and would force to exclude the whole HttpKernel.php file from the CS tooling to avoid having PHP-CS-Fixer breaking it on each run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
93ecc0b
to
36dacd7
Compare
'line' => $r->getStartLine(), | ||
); | ||
} catch (\ReflectionException $e) { | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try new \ReflectionClass($controller[0])
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To get only the file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, WDYT ? And start line of the class of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not, But I don't know how to "simulate" this fail :/ Do you know how to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at RequestDataCollector I'd say using __call
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand. Sorry.
If the method does not exist in the first place Symfony throw an error.
I don't want to add dead code to Symfony, so please give me a reproducer ;)
WDYT of moving all the added logic to a new exception class, child of LogicException? |
8c5d7b2
to
c8f75e5
Compare
@nicolas-grekas I added the new Exception class. Note: Exception::trace is private, So I need reflection. line and file are protected ;). |
@@ -162,7 +163,8 @@ private function handleRaw(Request $request, int $type = self::MASTER_REQUEST) | |||
if (null === $response) { | |||
$msg .= ' Did you forget to add a return statement somewhere in your controller?'; | |||
} | |||
throw new \LogicException($msg); | |||
|
|||
throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need a test case to ensure we won't miss updating this "17" in the future I suppose
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests added
c8f75e5
to
f1e9aa4
Compare
Thank you @lyrixx. |
… returns nothing (lyrixx) This PR was merged into the 4.2-dev branch. Discussion ---------- [HttpKernel] Better exception page when the controller returns nothing | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #27137 | License | MIT | Doc PR | --- Before:  after:  Commits ------- f1e9aa4 [HttpKernel] Better exception page when the controller returns nothing
…controller returns nothing (dimabory) This PR was merged into the 4.3-dev branch. Discussion ---------- [HttpKernel] Better exception page when the invokable controller returns nothing | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #27138 | License | MIT | Doc PR | --- __Prerequisites__ _Configure invokable controller_ ```php # config/routes.yaml index: path: / controller: App\Controller\Start ``` __Before:__  __After:__  --- Take a look for an enhancement/refactoring in `HttpKernel.php` Commits ------- f6c1622 [HttpKernel] Better exception page when the invokable controller returns nothing
Before:
after: