Installation:
composer require friendsofsymfony/rest-bundle
Add to config/bundles.php:
return [
// ...
FriendsOfSymfony\RestBundle\FriendsOfSymfonyRestBundle::class => ['all' => true],
];
Enable Format Negotiation:
Configure config/packages/fos_rest.yaml:
fos_rest:
format_listener: true
param_fetcher_listener: true
body_listener: true
view:
view_response_listener: true
formats:
json: true
xml: true
rss: false
First Use Case:
Create a controller with a FOS\RestBundle\Controller\AbstractFOSRestController base class:
use FOS\RestBundle\Controller\AbstractFOSRestController;
use FOS\RestBundle\Controller\Annotations as Rest;
use Symfony\Component\HttpFoundation\Response;
class UserController extends AbstractFOSRestController
{
/**
* @Rest\Get("/users/{id}", name="get_user")
*/
public function getUserAction(int $id): Response
{
$user = $this->getDoctrine()->getRepository(User::class)->find($id);
return $this->handleView($this->view($user, Response::HTTP_OK));
}
}
Format-Agnostic Controllers:
Use handleView() to let FOSRestBundle handle response formatting (JSON/XML) based on Accept header.
return $this->handleView($this->view($data, Response::HTTP_CREATED));
Request Body Parsing:
Automatically deserialize request body into an object (e.g., DTO) using @Rest\RequestParam or @Rest\RequestBody.
/**
* @Rest\Post("/users")
*/
public function createUserAction(UserDTO $userDTO): Response
{
// $userDTO is automatically populated from request body
}
Exception Handling:
Map exceptions to HTTP status codes via fos_rest.exception.map in config:
fos_rest:
exception:
map:
App\Exception\NotFoundException: HTTP_NOT_FOUND
Serialization Groups: Use Symfony Serializer groups to control which properties are serialized:
use JMS\Serializer\Annotation as Serializer;
class User
{
/**
* @Serializer\Groups({"user:read"})
*/
private $name;
}
Configure in fos_rest.yaml:
fos_rest:
view:
serializer_groups: ['user:read']
Custom Formats:
Add support for custom formats (e.g., application/vnd.api+json):
fos_rest:
view:
formats:
jsonapi: true
Register a custom formatter service:
services:
App\Serializer\JsonApiFormatter:
tags: ['fos_rest.formatter']
Pagination:
Use Knp\Component\PagerFantaBundle with FOSRestBundle for paginated responses:
use Knp\Component\PagerFanta\View\ViewInterface;
public function listUsersAction(ViewInterface $pagerFantaAdapter): Response
{
return $this->handleView($this->view($pagerFantaAdapter->getCurrentPageResults()));
}
API Platform Compatibility:
FOSRestBundle works alongside API Platform. Use ApiPlatform\Core\Bridge\Symfony\FormatListener for unified format handling.
Event Listeners:
Attach listeners to fos_rest.exception or fos_rest.view events for custom logic:
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($event->getThrowable() instanceof \RuntimeException) {
$event->setResponse($this->createExceptionResponse($event->getThrowable()));
}
}
Testing:
Mock the Request object to test format negotiation:
$client->request('GET', '/users', [], [], [
'HTTP_ACCEPT' => 'application/json',
]);
CORS:
Combine with nelmio/cors-bundle for RESTful CORS support:
nelmio_cors:
defaults:
allow_origin: ['*']
allow_methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
allow_headers: ['*']
expose_headers: ['*']
Format Negotiation Overrides:
format_listener is enabled, the Accept header takes precedence over route parameters (e.g., /users.{_format}).format_listener if you need strict route-based format control.Circular References:
User->orders->user).@Serializer\MaxDepth or @Serializer\ExclusionPolicy(ALL) on problematic properties.Doctrine ORM vs. Serializer Conflicts:
__toString() may interfere with serialization.__toString():
/**
* @Serializer\ExclusionPolicy("all")
*/
public function __toString() { return ''; }
Body Listener Conflicts:
body_listener may parse the request body before your controller logic runs, causing issues with raw input (e.g., file uploads).body_listener for specific routes or use @Rest\RequestParam for manual parsing.Deprecated Annotations:
@Rest\View) are deprecated in favor of handleView().return $this->view($data) + handleView()).Check Negotiated Format: Log the negotiated format in a controller:
$format = $this->get('request')->getRequestFormat();
$this->logger->debug('Negotiated format:', ['format' => $format]);
Validate Serialization:
Use the serializer.debug option to log serialization issues:
fos_rest:
view:
serializer_debug: true
Inspect Exceptions:
Enable detailed exception rendering in dev environment:
fos_rest:
exception:
enabled: true
Common HTTP Status Codes:
Response::HTTP_CREATED (201) for successful POST/PUT.Response::HTTP_NO_CONTENT (204) for DELETE operations with no response body.Custom Formatters: Create a formatter service to support non-standard formats:
use FOS\RestBundle\Formatter\FormatterInterface;
class CustomFormatter implements FormatterInterface
{
public function supports($format, $mediaType)
{
return 'custom' === $format;
}
public function format($data, $format, $context = [])
{
return json_encode($data, JSON_PRETTY_PRINT);
}
}
Register as a service with the fos_rest.formatter tag.
Override Default View:
Extend the default FOS\RestBundle\View\View class to add custom logic:
use FOS\RestBundle\View\View;
class CustomView extends View
{
public function __construct($data, int $statusCode = 200, array $headers = [])
{
parent::__construct($data, $statusCode, $headers);
$this->setHeader('X-Custom-Header', 'value');
}
}
Dynamic Route Parameters:
Use @Rest\QueryParam or @Rest\RequestParam for dynamic filtering:
/**
* @Rest\Get("/users", name="list_users")
*/
public function listUsersAction(
int $page = 1,
int $limit = 10,
?string $filter = null
): Response {
// Use $page, $limit, $filter
}
Event Subscribers:
Subscribe to fos_rest.exception or fos_rest.view events for global modifications:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use FOS\RestBundle\Event\ExceptionEvent;
class CustomSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
'fos_rest.exception' => 'onException',
];
}
How can I help you explore Laravel packages today?