Skip to content

Commit

Permalink
Merge pull request #1358 from FriendsOfSymfony/EXCEPTIONWRAPPER
Browse files Browse the repository at this point in the history
Proposal to improve the ExceptionController
  • Loading branch information
GuilhemN committed Mar 6, 2016
2 parents 6744a48 + 05330ba commit ab6d26e
Show file tree
Hide file tree
Showing 37 changed files with 680 additions and 600 deletions.
233 changes: 53 additions & 180 deletions Controller/ExceptionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,241 +11,97 @@

namespace FOS\RestBundle\Controller;

use FOS\RestBundle\Negotiation\FormatNegotiator;
use FOS\RestBundle\Util\StopFormatListenerException;
use FOS\RestBundle\Util\ExceptionWrapper;
use FOS\RestBundle\View\ExceptionWrapperHandlerInterface;
use FOS\RestBundle\Util\ClassMapHandlerTrait;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandlerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;

/**
* Custom ExceptionController that uses the view layer and supports HTTP response status code mapping.
*/
class ExceptionController
{
private $exceptionWrapperHandler;
private $formatNegotiator;
use ClassMapHandlerTrait;

private $viewHandler;
private $exceptionCodes;
private $exceptionMessages;
private $showException;

public function __construct(
ExceptionWrapperHandlerInterface $exceptionWrapperHandler,
FormatNegotiator $formatNegotiator,
ViewHandlerInterface $viewHandler,
array $exceptionCodes,
array $exceptionMessages,
$showException
) {
$this->exceptionWrapperHandler = $exceptionWrapperHandler;
$this->formatNegotiator = $formatNegotiator;
$this->viewHandler = $viewHandler;
$this->exceptionCodes = $exceptionCodes;
$this->exceptionMessages = $exceptionMessages;
$this->showException = $showException;
}

/**
* @return ViewHandlerInterface
*/
protected function getViewHandler()
{
return $this->viewHandler;
}

/**
* Converts an Exception to a Response.
*
* @param Request $request
* @param FlattenException $exception
* @param DebugLoggerInterface $logger
* @param Request $request
* @param \Exception $exception
* @param DebugLoggerInterface|null $logger
*
* @throws \InvalidArgumentException
*
* @return Response
*/
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null)
public function showAction(Request $request, \Exception $exception, DebugLoggerInterface $logger = null)
{
try {
$format = $this->getFormat($request, $request->getRequestFormat());
} catch (\Exception $e) {
$format = null;
}
if (null === $format) {
$message = 'No matching accepted Response format could be determined, while handling: ';
$message .= $this->getExceptionMessage($exception);

return $this->createPlainResponse($message, Response::HTTP_NOT_ACCEPTABLE, $exception->getHeaders());
}

$currentContent = $this->getAndCleanOutputBuffering(
$request->headers->get('X-Php-Ob-Level', -1)
);
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
$code = $this->getStatusCode($exception);
$parameters = $this->getParameters($currentContent, $code, $exception, $logger, $format);
$showException = $request->attributes->get('showException', $this->showException);

try {
$view = $this->createView($format, $exception, $code, $parameters, $request, $showException);
$templateData = $this->getTemplateData($currentContent, $code, $exception, $logger);

$response = $this->viewHandler->handle($view);
} catch (\Exception $e) {
$message = 'An Exception was thrown while handling: ';
$message .= $this->getExceptionMessage($exception);
$response = $this->createPlainResponse($message, Response::HTTP_INTERNAL_SERVER_ERROR, $exception->getHeaders());
}
$view = $this->createView($exception, $code, $templateData, $request, $this->showException);
$response = $this->viewHandler->handle($view);

return $response;
}

/**
* Returns a Response Object with content type text/plain.
*
* @param string $content
* @param int $status
* @param array $headers
*
* @return Response
*/
private function createPlainResponse($content, $status, $headers)
{
$headers['content-type'] = 'text/plain';

return new Response($content, $status, $headers);
}

/**
* Creates a new ExceptionWrapper instance that can be overwritten by a custom
* ExceptionController class.
*
* @param array $parameters output data
*
* @return ExceptionWrapper ExceptionWrapper instance
*/
protected function createExceptionWrapper(array $parameters)
{
return $this->exceptionWrapperHandler->wrap($parameters);
}

/**
* @param string $format
* @param FlattenException $exception
* @param int $code
* @param array $parameters
* @param Request $request
* @param bool $showException
* @param \Exception $exception
* @param int $code
* @param array $templateData
* @param Request $request
* @param bool $showException
*
* @return View
*/
protected function createView($format, FlattenException $exception, $code, $parameters, Request $request, $showException)
protected function createView(\Exception $exception, $code, array $templateData, Request $request, $showException)

This comment has been minimized.

Copy link
@ismailbaskin

ismailbaskin Mar 7, 2016

Warning: Declaration of FOS\RestBundle\Controller\TwigExceptionController::createView($format, Symfony\Component\Debug\Exception\FlattenException $exception, $code, $parameters, Symfony\Component\HttpFoundation\Request $req
uest, $showException) should be compatible with FOS\RestBundle\Controller\ExceptionController::createView(Exception $exception, $code, array $templateData, Symfony\Component\HttpFoundation\Request $request, $showException)

{
$parameters = $this->createExceptionWrapper($parameters);
$view = View::create($parameters, $code, $exception->getHeaders());
$view->setFormat($format);
$view = new View($exception, $code, $exception instanceof HttpExceptionInterface ? $exception->getHeaders() : []);
$view->setTemplateVar('raw_exception');
$view->setTemplateData($templateData);

return $view;
}

/**
* Gets and cleans any content that was already outputted.
*
* This code comes from Symfony and should be synchronized on a regular basis
* see src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php
*
* @return string
*/
private function getAndCleanOutputBuffering($startObLevel)
{
if (ob_get_level() <= $startObLevel) {
return '';
}
Response::closeOutputBuffers($startObLevel + 1, true);

return ob_get_clean();
}

/**
* Extracts the exception message.
*
* @param FlattenException $exception
* @param array $exceptionMap
*
* @return int|false
*/
private function isSubclassOf($exception, $exceptionMap)
{
$exceptionClass = $exception->getClass();
foreach ($exceptionMap as $exceptionMapClass => $value) {
if ($value
&& ($exceptionClass === $exceptionMapClass || is_subclass_of($exceptionClass, $exceptionMapClass))
) {
return $value;
}
}

return false;
}

/**
* Extracts the exception message.
*
* @param FlattenException $exception
*
* @return string Message
*/
protected function getExceptionMessage($exception)
{
$showExceptionMessage = $this->isSubclassOf($exception, $this->exceptionMessages);

if ($showExceptionMessage || $this->showException) {
return $exception->getMessage();
}

$statusCode = $this->getStatusCode($exception);

return array_key_exists($statusCode, Response::$statusTexts) ? Response::$statusTexts[$statusCode] : 'error';
}

/**
* Determines the status code to use for the response.
*
* @param FlattenException $exception
* @param \Exception $exception
*
* @return int
*/
protected function getStatusCode($exception)
protected function getStatusCode(\Exception $exception)
{
$isExceptionMappedToStatusCode = $this->isSubclassOf($exception, $this->exceptionCodes);

return $isExceptionMappedToStatusCode ?: $exception->getStatusCode();
}
// If matched
if ($statusCode = $this->resolveValue(get_class($exception), $this->exceptionCodes)) {
return $statusCode;
}

/**
* Determines the format to use for the response.
*
* @param Request $request
* @param string $format
*
* @return string
*/
protected function getFormat(Request $request, $format)
{
try {
$accept = $this->formatNegotiator->getBest('', []);
if ($accept) {
$format = $request->getFormat($accept->getType());
}
$request->attributes->set('_format', $format);
} catch (StopFormatListenerException $e) {
$format = $request->getRequestFormat();
// Otherwise, default
if ($exception instanceof HttpExceptionInterface) {
return $exception->getStatusCode();
}

return $format;
return 500;
}

/**
Expand All @@ -256,21 +112,38 @@ protected function getFormat(Request $request, $format)
*
* @param string $currentContent
* @param int $code
* @param FlattenException $exception
* @param \Exception $exception
* @param DebugLoggerInterface $logger
* @param string $format
*
* @return array
*/
protected function getParameters($currentContent, $code, $exception, DebugLoggerInterface $logger = null, $format = 'html')
private function getTemplateData($currentContent, $code, \Exception $exception, DebugLoggerInterface $logger = null)
{
return [
'exception' => FlattenException::create($exception),
'status' => 'error',
'status_code' => $code,
'status_text' => array_key_exists($code, Response::$statusTexts) ? Response::$statusTexts[$code] : 'error',
'currentContent' => $currentContent,
'message' => $this->getExceptionMessage($exception),
'exception' => $exception,
'logger' => $logger,
];
}

/**
* Gets and cleans any content that was already outputted.
*
* This code comes from Symfony and should be synchronized on a regular basis
* see src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php
*
* @return string
*/
private function getAndCleanOutputBuffering($startObLevel)
{
if (ob_get_level() <= $startObLevel) {
return '';
}
Response::closeOutputBuffers($startObLevel + 1, true);

return ob_get_clean();
}
}
7 changes: 1 addition & 6 deletions Controller/TemplatingExceptionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

namespace FOS\RestBundle\Controller;

use FOS\RestBundle\Negotiation\FormatNegotiator;
use FOS\RestBundle\View\ExceptionWrapperHandlerInterface;
use FOS\RestBundle\View\ViewHandlerInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -23,15 +21,12 @@ abstract class TemplatingExceptionController extends ExceptionController
protected $templating;

public function __construct(
ExceptionWrapperHandlerInterface $exceptionWrapperHandler,
FormatNegotiator $formatNegotiator,
ViewHandlerInterface $viewHandler,
array $exceptionCodes,
array $exceptionMessages,
$showException,
EngineInterface $templating
) {
parent::__construct($exceptionWrapperHandler, $formatNegotiator, $viewHandler, $exceptionCodes, $exceptionMessages, $showException);
parent::__construct($viewHandler, $exceptionCodes, $showException);

$this->templating = $templating;
}
Expand Down
21 changes: 1 addition & 20 deletions Controller/TwigExceptionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;

/**
* Custom ExceptionController that uses the view layer and supports HTTP response status code mapping.
Expand All @@ -28,29 +27,11 @@ class TwigExceptionController extends TemplatingExceptionController
protected function createView($format, FlattenException $exception, $code, $parameters, Request $request, $showException)
{
$view = parent::createView($format, $exception, $code, $parameters, $request, $showException);

if ($this->getViewHandler()->isFormatTemplating($format)) {
$view->setTemplate($this->findTemplate($request, $format, $code, $showException));
$view->setData($parameters);
}
$view->setTemplate($this->findTemplate($request, $format, $code, $showException));

return $view;
}

/**
* {@inheritdoc}
*/
protected function getParameters($currentContent, $code, $exception, DebugLoggerInterface $logger = null, $format = 'html')
{
$parameters = parent::getParameters($currentContent, $code, $exception, $logger, $format);

if ($this->getViewHandler()->isFormatTemplating($format)) {
$parameters['logger'] = $logger;
}

return $parameters;
}

/**
* {@inheritdoc}
*
Expand Down
Loading

0 comments on commit ab6d26e

Please sign in to comment.