From 6a6661012e914f3f5aa49ccc64b2ac54226b18a5 Mon Sep 17 00:00:00 2001 From: Francis Besset Date: Wed, 13 Nov 2013 15:02:17 +0100 Subject: [PATCH] [SoapBundle] Enhanced exception management with Symfony2 --- composer.json | 1 + .../Controller/SoapWebServiceController.php | 52 ++++++++++-- .../BeSimpleSoapExtension.php | 2 + .../DependencyInjection/Configuration.php | 6 ++ .../EventListener/SoapExceptionListener.php | 79 +++++++++++++++++++ .../SoapBundle/Handler/ExceptionHandler.php | 43 ++++++++++ .../Resources/config/webservice.xml | 9 +++ src/BeSimple/SoapBundle/composer.json | 5 +- 8 files changed, 188 insertions(+), 9 deletions(-) create mode 100644 src/BeSimple/SoapBundle/EventListener/SoapExceptionListener.php create mode 100644 src/BeSimple/SoapBundle/Handler/ExceptionHandler.php diff --git a/composer.json b/composer.json index 61914ef..e668c44 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "ext-curl": "*", "ass/xmlsecurity": "~1.0", "symfony/framework-bundle": "~2.0", + "symfony/twig-bundle": "~2.0", "zendframework/zend-mail": "2.1.*", "zendframework/zend-mime": "2.1.*", "zendframework/zend-soap": "2.1.*" diff --git a/src/BeSimple/SoapBundle/Controller/SoapWebServiceController.php b/src/BeSimple/SoapBundle/Controller/SoapWebServiceController.php index 01cd065..9c5c046 100644 --- a/src/BeSimple/SoapBundle/Controller/SoapWebServiceController.php +++ b/src/BeSimple/SoapBundle/Controller/SoapWebServiceController.php @@ -12,14 +12,18 @@ namespace BeSimple\SoapBundle\Controller; +use BeSimple\SoapBundle\Handler\ExceptionHandler; use BeSimple\SoapBundle\Soap\SoapRequest; use BeSimple\SoapBundle\Soap\SoapResponse; use BeSimple\SoapServer\Exception as SoapException; use Symfony\Component\DependencyInjection\ContainerAware; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\FlattenException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; /** * @author Christian Kerl @@ -100,6 +104,46 @@ class SoapWebServiceController extends ContainerAware return $response; } + /** + * Converts an Exception to a SoapFault Response. + * + * @param Request $request The request + * @param FlattenException $exception A FlattenException instance + * @param DebugLoggerInterface $logger A DebugLoggerInterface instance + * + * @return Response + * + * @throws \LogicException When the request query parameter "_besimple_soap_webservice" does not exist + */ + public function exceptionAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null) + { + if (!$webservice = $request->query->get('_besimple_soap_webservice')) { + throw new \LogicException(sprintf('The parameter "%s" is required in Request::$query parameter bag to generate the SoapFault.', '_besimple_soap_webservice'), null, $e); + } + + $view = 'TwigBundle:Exception:'.($this->container->get('kernel')->isDebug() ? 'exception' : 'error').'.txt.twig'; + $code = $exception->getStatusCode(); + $details = $this->container->get('templating')->render($view, array( + 'status_code' => $code, + 'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '', + 'exception' => $exception, + 'logger' => $logger, + )); + + $server = $this + ->container + ->get(sprintf('besimple.soap.context.%s', $webservice)) + ->getServerBuilder() + ->withHandler(new ExceptionHandler($exception, $details)) + ->build() + ; + + ob_start(); + $server->handle($request->getContent()); + + return new Response(ob_get_clean()); + } + /** * This method gets called once for every SOAP header the \SoapServer received * and afterwards once for the called SOAP operation. @@ -125,13 +169,7 @@ class SoapWebServiceController extends ContainerAware ); // forward to controller - try { - $response = $this->container->get('http_kernel')->handle($this->soapRequest, HttpKernelInterface::SUB_REQUEST, false); - } catch (\Exception $e) { - $this->soapResponse = new Response(null, 500); - - throw $e instanceof \SoapFault || $this->container->getParameter('kernel.debug') ? $e : new SoapException\ReceiverSoapFault($e->getMessage()); - } + $response = $this->container->get('http_kernel')->handle($this->soapRequest, HttpKernelInterface::SUB_REQUEST, false); $this->setResponse($response); diff --git a/src/BeSimple/SoapBundle/DependencyInjection/BeSimpleSoapExtension.php b/src/BeSimple/SoapBundle/DependencyInjection/BeSimpleSoapExtension.php index 1536d8d..395e767 100644 --- a/src/BeSimple/SoapBundle/DependencyInjection/BeSimpleSoapExtension.php +++ b/src/BeSimple/SoapBundle/DependencyInjection/BeSimpleSoapExtension.php @@ -63,6 +63,8 @@ class BeSimpleSoapExtension extends Extension $serviceConfig['name'] = $name; $this->createWebServiceContext($serviceConfig, $container); } + + $container->setParameter('besimple.soap.exception_listener.controller', $config['exception_controller']); } private function registerCacheConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) diff --git a/src/BeSimple/SoapBundle/DependencyInjection/Configuration.php b/src/BeSimple/SoapBundle/DependencyInjection/Configuration.php index f7fa94d..1cba9ff 100644 --- a/src/BeSimple/SoapBundle/DependencyInjection/Configuration.php +++ b/src/BeSimple/SoapBundle/DependencyInjection/Configuration.php @@ -40,6 +40,12 @@ class Configuration $this->addServicesSection($rootNode); $this->addWsdlDumperSection($rootNode); + $rootNode + ->children() + ->scalarNode('exception_controller')->defaultValue('BeSimpleSoapBundle:SoapWebService:exception')->end() + ->end() + ; + return $treeBuilder->buildTree(); } diff --git a/src/BeSimple/SoapBundle/EventListener/SoapExceptionListener.php b/src/BeSimple/SoapBundle/EventListener/SoapExceptionListener.php new file mode 100644 index 0000000..5448ac7 --- /dev/null +++ b/src/BeSimple/SoapBundle/EventListener/SoapExceptionListener.php @@ -0,0 +1,79 @@ + + * (c) Francis Besset + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace BeSimple\SoapBundle\EventListener; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\EventListener\ExceptionListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * @author Francis Besset + */ +class SoapExceptionListener extends ExceptionListener +{ + /** + * @var ContainerInterface + */ + protected $container; + + /** + * To avoid conflict between , the logger param is not typed: + * The parent class needs and instance of `Psr\Log\LoggerInterface` from Symfony 2.2, + * before logger is an instance of `Symfony\Component\HttpKernel\Log\LoggerInterface`. + * + * @param ContainerInterface $container A ContainerInterface instance + * @param string $controller The controller name to call + * @param LoggerInterface $logger A logger instance + */ + public function __construct(ContainerInterface $container, $controller, $logger) + { + parent::__construct($controller, $logger); + + $this->container = $container; + } + + public function onKernelException(GetResponseForExceptionEvent $event) + { + if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + return; + } + + $request = $event->getRequest(); + if ('soap' !== $request->getRequestFormat()) { + return; + } + + $attributes = $request->attributes; + if (!$webservice = $attributes->get('webservice')) { + return; + } + + if (!$this->container->has(sprintf('besimple.soap.context.%s', $webservice))) { + return; + } + + // hack to retrieve the current WebService name in the controller + $request->query->set('_besimple_soap_webservice', $webservice); + + parent::onKernelException($event); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => array('onKernelException', -64), + ); + } +} diff --git a/src/BeSimple/SoapBundle/Handler/ExceptionHandler.php b/src/BeSimple/SoapBundle/Handler/ExceptionHandler.php new file mode 100644 index 0000000..56d2f18 --- /dev/null +++ b/src/BeSimple/SoapBundle/Handler/ExceptionHandler.php @@ -0,0 +1,43 @@ + + * (c) Francis Besset + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace BeSimple\SoapBundle\Handler; + +use BeSimple\SoapServer\Exception\ReceiverSoapFault; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\FlattenException; + +/** + * @author Francis Besset + */ +class ExceptionHandler +{ + protected $exception; + protected $details; + + public function __construct(FlattenException $exception, $details = null) + { + $this->exception = $exception; + $this->details = $details; + } + + public function __call($method, $arguments) + { + $code = $this->exception->getStatusCode(); + + throw new ReceiverSoapFault( + isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '', + null, + $this->details + ); + } +} diff --git a/src/BeSimple/SoapBundle/Resources/config/webservice.xml b/src/BeSimple/SoapBundle/Resources/config/webservice.xml index 8170b1f..6b38d56 100644 --- a/src/BeSimple/SoapBundle/Resources/config/webservice.xml +++ b/src/BeSimple/SoapBundle/Resources/config/webservice.xml @@ -6,6 +6,7 @@ BeSimple\SoapBundle\Soap\SoapResponse BeSimple\SoapBundle\EventListener\SoapResponseListener + BeSimple\SoapBundle\EventListener\SoapExceptionListener BeSimple\SoapBundle\WebServiceContext BeSimple\SoapBundle\ServiceBinding\RpcLiteralRequestHeaderMessageBinder BeSimple\SoapBundle\ServiceBinding\RpcLiteralRequestMessageBinder @@ -25,6 +26,14 @@ + + + + + %besimple.soap.exception_listener.controller% + + + diff --git a/src/BeSimple/SoapBundle/composer.json b/src/BeSimple/SoapBundle/composer.json index dd86aa1..95f040f 100644 --- a/src/BeSimple/SoapBundle/composer.json +++ b/src/BeSimple/SoapBundle/composer.json @@ -25,10 +25,11 @@ "besimple/soap-common": "0.2.*", "besimple/soap-wsdl": "0.2.*", "ass/xmlsecurity": "~1.0", + "symfony/framework-bundle": "~2.0", + "symfony/twig-bundle": "~2.0", "zendframework/zend-mail": "2.1.*", "zendframework/zend-mime": "2.1.*", - "zendframework/zend-soap": "2.1.*", - "symfony/framework-bundle": "~2.0" + "zendframework/zend-soap": "2.1.*" }, "suggest": { "besimple/soap-client": "0.2.*",