Skip to content

Commit

Permalink
Add a listener for application/problem-api+json
Browse files Browse the repository at this point in the history
  • Loading branch information
Jurian Sluiman committed Oct 3, 2013
1 parent 1a4ba39 commit 408e6d8
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 3 deletions.
12 changes: 12 additions & 0 deletions Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
}
7 changes: 4 additions & 3 deletions config/module.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
190 changes: 190 additions & 0 deletions src/SlmException/Mvc/JsonProblemApiListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<?php
/**
* Copyright (c) 2012-2013 Jurian Sluiman.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the names of the copyright holders nor the names of the
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @author Jurian Sluiman <[email protected]>
* @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');
}
}
}

0 comments on commit 408e6d8

Please sign in to comment.