From 408e6d87496ce71248c9f7d46703f02fa798bee8 Mon Sep 17 00:00:00 2001 From: Jurian Sluiman Date: Thu, 3 Oct 2013 11:50:48 +0200 Subject: [PATCH] Add a listener for application/problem-api+json --- Module.php | 12 ++ config/module.config.php | 7 +- .../Mvc/JsonProblemApiListener.php | 190 ++++++++++++++++++ 3 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 src/SlmException/Mvc/JsonProblemApiListener.php diff --git a/Module.php b/Module.php index d8dc6ac..18a39ce 100644 --- a/Module.php +++ b/Module.php @@ -87,6 +87,9 @@ public function onBootstrap(EventInterface $e) if (true === $config['enable_messages']) { $this->attachExceptionMessages($e); } + if (true === $config['enable_problem_api']) { + $this->attachProblemApiListeners($e); + } } protected function attachExceptionListeners(EventInterface $e) @@ -119,4 +122,13 @@ protected function attachExceptionMessages(EventInterface $e) { // @todo Implement user defined error messages } + + protected function attachProblemApiListeners(EventInterface $e) + { + $app = $e->getApplication(); + $em = $app->getEventManager(); + + $listener = new Mvc\JsonProblemApiListener; + $listener->attach($em); + } } diff --git a/config/module.config.php b/config/module.config.php index b71bc2a..7b83c01 100644 --- a/config/module.config.php +++ b/config/module.config.php @@ -40,9 +40,10 @@ return array( 'slm_exception' => array( - 'enable_markers' => false, - 'enable_logging' => false, - 'enable_messages' => false, + 'enable_markers' => false, + 'enable_logging' => false, + 'enable_messages' => false, + 'enable_problem_api' => false, 'default_marker' => 'SlmException\Exception\ServerErrorInterface', 'exception_markers' => array( diff --git a/src/SlmException/Mvc/JsonProblemApiListener.php b/src/SlmException/Mvc/JsonProblemApiListener.php new file mode 100644 index 0000000..4d983ab --- /dev/null +++ b/src/SlmException/Mvc/JsonProblemApiListener.php @@ -0,0 +1,190 @@ + + * @copyright 2012-2013 Jurian Sluiman http://juriansluiman.nl. + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +namespace SlmException\Mvc; + +use Zend\EventManager\ListenerAggregateInterface; +use Zend\EventManager\EventManagerInterface; +use Zend\Http\Request as HttpRequest; +use Zend\Mvc\MvcEvent; +use Zend\View\Model\JsonModel; +use Zend\View\Model\ModelInterface; + +class JsonProblemApiListener implements ListenerAggregateInterface +{ + protected $listeners; + + /** + * {@inheritDoc} + */ + public function attach(EventManagerInterface $events) + { + $this->listeners[] = $events->attach(MvcEvent::EVENT_RENDER, array($this, 'onRender'), 1000); + $this->listeners[] = $events->attach(MvcEvent::EVENT_FINISH, array($this, 'onFinish')); + } + + /** + * {@inheritDoc} + */ + public function detach(EventManagerInterface $events) + { + foreach ($this->listeners as $key => $listener) { + $detached = false; + if ($listener === $this) { + continue; + } + if ($listener instanceof ListenerAggregateInterface) { + $detached = $listener->detach($events); + } elseif ($listener instanceof CallbackHandler) { + $detached = $events->detach($listener); + } + + if ($detached) { + unset($this->listeners[$key]); + } + } + } + + public function onRender(MvcEvent $e) + { + // must be an error + if (!$e->isError()) { + return; + } + + // Check the accept headers for application/json + $request = $e->getRequest(); + if (!$request instanceof HttpRequest) { + return; + } + + $headers = $request->getHeaders(); + if (!$headers->has('Accept')) { + return; + } + + $accept = $headers->get('Accept'); + $match = $accept->match('application/json'); + if (!$match || $match->getTypeString() == '*/*') { + // not application/json + return; + } + + // if we have a JsonModel in the result, then do nothing + $currentModel = $e->getResult(); + if ($currentModel instanceof JsonModel) { + return; + } + + // create a new JsonModel - use application/api-problem+json fields. + $response = $e->getResponse(); + $model = new JsonModel(array( + "httpStatus" => $response->getStatusCode(), + "title" => $response->getReasonPhrase(), + )); + + // Find out what the error is + $exception = $currentModel->getVariable('exception'); + + if ($currentModel instanceof ModelInterface && $currentModel->reason) { + switch ($currentModel->reason) { + case 'error-controller-cannot-dispatch': + $model->detail = 'The requested controller was unable to dispatch the request.'; + break; + case 'error-controller-not-found': + $model->detail = 'The requested controller could not be mapped to an existing controller class.'; + break; + case 'error-controller-invalid': + $model->detail = 'The requested controller was not dispatchable.'; + break; + case 'error-router-no-match': + $model->detail = 'The requested URL could not be matched by routing.'; + break; + default: + $model->detail = $currentModel->message; + break; + } + } + + if ($exception) { + if ($exception->getCode()) { + $e->getResponse()->setStatusCode($exception->getCode()); + } + $model->detail = $exception->getMessage(); + + // find the previous exceptions + $messages = array(); + while ($exception = $exception->getPrevious()) { + $messages[] = "* " . $exception->getMessage(); + }; + if (count($messages)) { + $exceptionString = implode("\n", $messages); + $model->messages = $exceptionString; + } + } + + // set our new view model + $model->setTerminal(true); + $e->setResult($model); + $e->setViewModel($model); + + // set our json renderer + $sm = $e->getApplication()->getServiceManager(); + $view = $sm->get('Zend\View\View'); + $strategy = $sm->get('ViewJsonStrategy'); + $view->getEventManager()->attach($strategy, 100); + } + + public function onFinish(MvcEvent $e) + { + $response = $e->getResponse(); + $headers = $response->getHeaders(); + + if ($headers->has('Content-Type') + && strpos($headers->get('Content-Type')->getFieldValue(), 'application/json') !== false + && strpos($response->getContent(), 'httpStatus') !== false + ){ + /** + * We can almost be certain the response is an api problem, + * since the JSON contains httpStatus + */ + $headers->addHeaderLine('Content-Type', 'application/api-problem+json'); + } + } +} \ No newline at end of file