Awesome
Easy Rest Bundle
Simple and lightweight bundle provides JSON based request / response and exception handling support to develop RESTful API's with Symfony.
Features include:
- Listener for decoding JSON request body and accessing it from Request class
ParamConverter
for mapping JSON request to plain PHP object (using Symfony Serializer)- (New in 1.3.0) Supports nested objects and arrays using PHPDoc hints
- Listener for creating JSON responses which is converts to JSON
- Automatically determines correct HTTP status codes for DELETE and POST response
- Exception controller for providing error details
- Supports Symfony Validation errors
- Provides stack-trace on development environment
- Supports Symfony 2 and 3
- Only uses plain listeners, easy to configure and disable certain features.
Not supports:
- XML serializer or format agnostic helpers
- Header format negotiation
Installation
Step 1: Download the Bundle
Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:
$ composer require osm/easy-rest-bundle "~1"
This command requires you to have Composer installed globally, as explained in the installation chapter of the Composer documentation.
Step 2: Enable the Bundle
Then, enable the bundle by adding it to the list of registered bundles
in the app/AppKernel.php
file of your project:
<?php
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ...
new Osm\EasyRestBundle\OsmEasyRestBundle(),
);
// ...
}
// ...
}
Step 3: Configuration
Enable the bundle's configuration in app/config/config.yml
:
osm_easy_rest: ~
With default configuration, all listeners and exception controller will be enabled. You can change this behaviour with following options:
osm_easy_rest:
enable_exception_listener: true
enable_json_param_converter: false
enable_json_response_listener: true
enable_request_body_listener: true
Usage
JSON Request and Response
Response
Responses are handled by JsonResponseListener
listener. It's directly uses Symfony JsonResponse
class for creating response. Simply you can use arrays or JsonSerializable
objects.
GET request and response:
curl -i localhost:8000/v1/users/12/details
Controller
/**
* @Method({"GET"})
* @Route("/v1/users/12/details")
*/
public function getUserDetailsSample()
{
return [
'user' => [
'id' => '8f262cd7-9f2d-4bca-825e-e2444b1a57e0',
'username' => 'o',
'isEnabled' => true,
'roles' => [
'ROLE_USER',
'ROLE_ADMIN'
]
]
];
}
Response will be,
HTTP/1.1 200 OK
Cache-Control: no-cache, private
Connection: close
Content-Type: application/json
{
"user": {
"id": "8f262cd7-9f2d-4bca-825e-e2444b1a57e0",
"isEnabled": true,
"roles": [
"ROLE_USER",
"ROLE_ADMIN"
],
"username": "o"
}
}
Request
Requests are handled by RequestContentListener
, it tries to convert request body to array and wraps in ParameterBag
. It's only activated for POST
, PUT
and PATCH
requests. So, you can access to this parameters from Symfony Request object.
POST request example with JSON body:
curl -i -X POST \
http://localhost:8000/access-tokens \
-H 'content-type: application/json' \
-d '{
"username": "o",
"password": "t0o53cur#",
}'
In controller you can access parameters like:
/**
* @Method({"POST"})
* @Route("/access-tokens")
*/
public function createTokenAction(Request $request) {
$username = $request->request->get('username'); // Will produce 'o'
$password = $request->request->get('password'); // Will produce 't0o53cur#'
....
}
Json Param Converter
You can also use PHP objects for mapping and validating request. Your request object should implement JsonRequestInterface
interface.
use Osm\EasyRestBundle\ParamConverter\JsonRequestInterface;
use Symfony\Component\Validator\Constraints as Assert;
class CreateTokenRequest implements JsonRequestInterface
{
/**
* @Assert\NotBlank()
*/
private $username;
/**
* @Assert\NotBlank()
* @Assert\Regex("/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\W])/", message="Your password should contain a digit, a lowercase, an uppercase and a special character.")
* @Assert\Length(min="8")
*/
private $password;
// Getter / setters removed for brevity
}
Accessing and validating from controller:
POST request example with JSON body:
curl -i -X POST \
http://localhost:8000/access-tokens \
-H 'content-type: application/json' \
-d '{
"username": "o",
"password": "t0o53cur#",
}'
A param converter directly unmarshalls JSON request to your object.
/**
* @Route()
* @Method("POST")
* @throws ValidationException
*/
public function createTokenAction(CreateTokenRequest $createTokenRequest, ValidatorInterface $validator)
{
$errors = $validator->validate($createTokenRequest);
if (count($errors)) {
throw new ValidationException($errors);
}
$createTokenRequest->getUsername(); // Will produce 'o'
$createTokenRequest->getPassword(); // Will produce 't0o53cur#'
}
Working with exceptions and validation errors
In default, an exception controller also converts handled exceptions to strict signature JSON responses with respective HTTP status codes.
/**
* @Route("/test/precondition-failed")
*/
public function testPreconditionFailed(Request $request) {
...
if (!$hasRequirements) {
throw new PreconditionFailedHttpException('Invalid condition');
}
}
Response will be:
$ curl -i localhost:8000/test/precondition-failed
HTTP/1.1 412 Precondition Failed
Cache-Control: no-cache, private
Connection: close
Content-Type: application/json
{
"code": 0,
"errors": [],
"message": "Invalid condition",
"status_code": 412,
"status_text": "Precondition Failed",
"trace": []
}
In the development
mode for the unhandled exceptions also a stack-trace is included in response.
Validation errors and ExceptionWrapper
For exceptions, this bundle comes with ExceptionWrapper
for creating error responses in a nice way.
Using with Symfony Validator errors:
/**
* @Method({"POST"})
* @Route("/access-tokens")
*/
public function createTokenAction(Request $request) {
....
$errors = $this->get('validator')->validate(
$request->request->all(),
new Assert\Collection(
[
'username' => [
new Assert\NotBlank(),
],
'password' => [
new Assert\NotBlank(),
new Assert\Length(['min' => 5]),
],
]
)
);
return (new ExceptionWrapper())
->setErrorsFromConstraintViolations($errors)
->setMessage(ErrorMessagesInterface::VALIDATION_ERROR)
->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY)
->getResponse();
}
An example request which is we expect to fail:
curl -i -X POST \
http://localhost:8000/access-tokens \
-H 'content-type: application/json' \
-d '{
"username": "",
"password": "t0o53cur#",
"extra_field": false
}'
Response will be:
HTTP/1.1 422 Unprocessable Entity
Cache-Control: no-cache, private
Connection: close
Content-Type: application/json
{
"code": 0,
"errors": [
{
"message": "This value should not be blank.",
"path": "username"
},
{
"message": "This field was not expected.",
"path": "extra_field"
}
],
"message": "Validation Failed",
"status_code": 422,
"status_text": "Unprocessable Entity",
"trace": []
}
You can also build your own custom error details:
/**
* @Route("/test/weird-error-test")
*/
public function getWeirdErrors()
{
return (new ExceptionWrapper())
->setMessage('Something going wrong')
->setStatusCode(Response::HTTP_I_AM_A_TEAPOT)
->addError('foo', 'I don\'t expect an input like this')
->addError('bar', 'This should be an integer')
->getResponse();
}
You will expect a response with this structure
curl -i localhost:8000/test/weird-error-test
HTTP/1.1 418 I'm a teapot
Cache-Control: no-cache, private
Connection: close
Content-Type: application/json
{
"code": 0,
"errors": [
{
"message": "I don't expect an input like this",
"path": "foo"
},
{
"message": "This should be an integer",
"path": "bar"
}
],
"message": "Something going wrong",
"status_code": 418,
"status_text": "I'm a teapot",
"trace": []
}
License
This bundle is distributed under the MIT license. Copyright (c) 2015-2018 Osman Ungur