The Big Brains Company - TbbcRestUtilBundle
, (*1)
A bundle for integrating tbbc/rest-util lib in a Symfony application, (*2)
Table of contents
- Installation
- Quick start
- Usage
- Run the test
- Contributing
- Requirements
- Authors
- License
Installation
Using Composer, just $ composer require tbbc/rest-util-bundle
package or:, (*3)
{
"require": {
"tbbc/rest-util-bundle": "@stable"
}
}
Quick start
Handling errors in a REST(ful) API
 Configuration
tbbc_rest_util:
error:
use_bundled_factories: true
exception_mapping:
FormErrorException:
class: "Tbbc\\RestUtilBundle\\Error\\Exception\\FormErrorException"
factory: tbbc_rest_util_form_error
http_status_code: 400
error_code: 400101
error_message: "Invalid input"
error_more_info_url: "http://api.my.tld/doc/error/400101"
AccessDeniedException:
class: "Symfony\\Component\\Security\\Core\\AccessDeniedException"
factory: default
http_status_code: 401
error_code: 401001
error_message: "Access denied"
extended_message: "The given token don't have enough privileges for accessing to this resource"
error_more_info_url: "http://api.my.tld/doc/error/401001"
CustomException:
class: "My\\ApiBundle\\Exception\\CustomException"
factory: my_api_custom
http_status_code: 501
error_code: 501001
error_more_info_url: "http://api.my.tld/doc/error/501001"
Exception:
class: "\\Exception"
factory: default
http_status_code: 500
error_code: 501203
error_message: "Server error"
Custom Symfony Exception Listener
errorResolver = $errorResolver;
parent::__construct($controller, $logger);
}
/**
* @param GetResponseForExceptionEvent $event
* @return void
*/
public function onKernelException(GetResponseForExceptionEvent $event)
{
static $handling;
if (true === $handling) {
return;
}
$exception = $event->getException();
$error = $this->errorResolver->resolve($exception);
if (null == $error) {
return;
}
$handling = true;
$response = new Response(json_encode($error->toArray()), $error->getHttpStatusCode(), array(
'Content-Type' => 'application/json'
));
$event->setResponse($response);
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::EXCEPTION => array('onKernelException', 10),
);
}
}
```
```xml
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="my_api.event_listener.rest_exception.class">My\ApiBundle\EventListener\RestExceptionListener</parameter>
</parameters>
<services>
<service id="my_api.event_listener.rest_exception" class="%my_api.event_listener.rest_exception.class%">
<tag name="kernel.event_subscriber" />
<tag name="monolog.logger" channel="request" />
<argument type="service" id="tbbc_rest_util.error.error_resolver" />
<argument>%twig.exception_listener.controller%</argument>
<argument type="service" id="logger" on-invalid="null" />
</service>
</services>
</container>
Api Controller code
get('security.context')->isGranted('COMMENT', $post)) {
throw new AccessDeniedException('Access denied');
}
$commentResource = new CommentResource();
$form = $this->createNamedForm('', new CommentResourceType(), $commentResource);
$form->bind($this->getRequest());
if (!$form->isValid()) {
throw new FormErrorException($form);
}
// another error
if (....) {
throw new CustomException('Something bad just happened!');
}
// ... save comment or whatever
}
}
```
#### MyApiCustomException error factory
```php
supportsException($exception)) {
return null;
}
$errorMessage = $mapping->getErrorMessage();
if (empty($errorMessage)) {
$errorMessage = $exception->getMessage();
}
$extendedMessage = $exception->getMoreExtendedMessage();
// Or whatever you need to do with your exception here
return new Error($mapping->getHttpStatusCode(), $mapping->getErrorCode(), $errorMessage,
$extendedMessage, $mapping->getErrorMoreInfoUrl());
}
/**
* {@inheritDoc}
*/
public function supportsException(\Exception $exception)
{
return $exception instanceof CustomException;
}
}
```
```xml
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="my_api.error.custom_error_factory.class">My\ApiBundle\Error\Factory\CustomErrorFactory</parameter>
</parameters>
<services>
<service id="my_api.error.custom_error_factory" class="%my_api.error.custom_error_factory.class%">
<tag name="tbbc_rest_util.error_factory" />
</service>
</services>
</container>
ENJOY!, (*4)
For the exceptions thrown in the previous PostCommentsController
class example, the response body will be respectively
something like the following:, (*5)
For the AccessDeniedException exception:, (*6)
{
"http_status_code": 401,
"code": 401001,
"message": "Access denied",
"extended_message": "The given token don't have enough privileges for accessing to this resource",
"more_info_url": "http:\/\/api.my.tld\/doc\/error\/401001"
}
For the FormErrorException exception:, (*7)
{
"http_status_code": 400,
"code": 400101,
"message": "Invalid input",
"extended_message": {
"global_errors": [
"Bubbled form error!"
],
"property_errors": {
"content": [
"The comment content should not be blank",
]
}
},
"more_info_url": "http:\/\/api.my.tld\/doc\/error\/400101"
}
For the CustomException exception:, (*8)
{
"http_status_code": 501,
"code": 501001,
"message": "Something bad just happened!",
"extended_message": null,
"more_info_url": "http:\/\/api.my.tld\/doc\/error\/501001"
}
Usage
Run the test
First make sure you have installed all the dependencies, run:, (*9)
$ composer install --dev
, (*10)
then, run the test from within the root directory:, (*11)
$ vendor/bin/phpunit
, (*12)
Contributing
- Take a look at the list of issues.
- Fork
- Write a test (for either new feature or bug)
- Make a PR
Requirements
Authors
- Benjamin Dulau - benjamin.dulau@gmail.com
- Valentin Ferriere - valentin@v-labs.fr
License
The Big Brains Company - TbbcRestUtilBundle
is licensed under the MIT License - see the LICENSE file for details, (*13)
, (*14)