Symfony2 FOSRESTBundle REST API to return PDF - php

I've made a Bundle and a REST controller inside. The "index" method return array in JSON-format, it's ok:
MyBundle/Controller/Api/Rest/BaconController.php
class BaconController extends Controller implements ClassResourceInterface
{
/**
* #var Request $request
* #return array
* #Rest\View
*/
public function cgetAction(Request $request)
{
$mediaType = $request->attributes->get('media_type');
$format = $request->getFormat($mediaType);
my_dump($format);
return array(
array("id" => 1, "title" => "hello",),
array("id" => 2, "title" => "there",),
);
}
}
MyBundle/Resources/config/api/routing_rest.yml
my_api_rest_bacon:
type: rest
resource: "MyBundle:Api/Rest/Bacon"
name_prefix: api_rest_bacon_
prefix: /my/bacon
So, at this point JSON results get returned perfectly:
mysite.com/app_dev.php/api/my/bacon/bacons.json
returns my array.
But now I need to get my controller generate a PDF with the data. So I want it to return PDF document when I call:
mysite.com/app_dev.php/api/my/bacon/bacons.pdf
I've found some half-manuals: RSS view handler, RSS config.ynal, CSV issue with answers. And tried to make something similar:
I've added these lines to
Symfony/app/config/config.yml
framework:
[...some old stuff here...]
request:
formats:
pdf: 'application/pdf'
fos_rest:
body_converter:
enabled: true
format_listener:
rules:
# Prototype array
-
# URL path info
path: ~
# URL host name
host: ~
prefer_extension: true
fallback_format: html
priorities: [html,json]
-
path: ~
host: ~
prefer_extension: true
fallback_format: pdf
priorities: [pdf]
view:
# #View or #Template
view_response_listener: force #true
formats:
json: true
pdf: true
xls: true
html: false
templating_formats:
pdf: false
xls: false
mime_types: {'pdf': ['application/pdf']}
routing_loader:
default_format: html
param_fetcher_listener: true
body_listener: true
allowed_methods_listener: true
services:
my.view_handler.pdf:
class: Lobster\MyBundle\View\PdfViewHandler
my.view_handler:
parent: fos_rest.view_handler.default
calls:
- ['registerHandler', [ 'pdf', [#my.view_handler.pdf, 'createResponse'] ] ]
MyBundle/View/PdfViewHandler.php
namespace Lobster\MyBundle\View;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class PdfViewHandler
{
public function createResponse(ViewHandler $handler, View $view, Request $request, $format)
{
my_dump('pdf createResponse started');
$pdf = "some pdf";
return new Response($pdf, 200, $view->getHeaders());
}
}
So now when I call
mysite.com/app_dev.php/api/my/bacon/bacons.pdf
I see an error An Exception was thrown while handling: Format html not supported, handler must be implemented and my function my_dump saves to a text file info about file format: it is html, not pdf.
Also pdf createResponse didn't work. Why?

So I've found the solution (I will describe how to enable 2 output formats: PDF and XLS):
1) This section in config.yml is not needed:
framework:
[...some old stuff here...]
request:
formats:
pdf: 'application/pdf'
2) fos_rest.format_listener section in config.yml should look like this:
format_listener:
rules:
-
path: '^/api/my/bacon.*\.xls$'
host: ~
prefer_extension: false
fallback_format: json
priorities: [xls, json]
-
path: '^/api/my/bacon.*\.pdf$'
host: ~
prefer_extension: false
fallback_format: json
priorities: [pdf, json]
-
path: ~
host: ~
prefer_extension: true
fallback_format: html
priorities: [html,json]
3) need to add service section into fos_rest in config.yml
fos_rest:
[...]
service:
view_handler: my.view_handler
4) services root section in config.yml should look like
services:
my.view_handler.xls:
class: Lobster\MyBundle\View\XlsViewHandler
my.view_handler.pdf:
class: Lobster\MyBundle\View\PdfViewHandler
my.view_handler:
parent: fos_rest.view_handler.default
calls:
- ['registerHandler', ['xls', [#my.view_handler.xls, 'createResponse'] ] ]
- ['registerHandler', ['pdf', [#my.view_handler.pdf, 'createResponse'] ] ]
And this is it. Now it works perfect

If files will have different data content, then Controller could as well generate file on their own, returning results in BinaryFileResponse.
No need to change any configuration
_format can be used to pick desired file format
All code reside inside controller (and some services related to particular format gen), so adding new stuff or changing existing require changes in small number of files.

Related

FOSRestBundle returning empty content

I'm implementing FOSUserBundle in my project. I want a basic GET action to return a json object of the ContactList entity.
Controller:
class ContactListController extends FOSRestController
{
use ViewContextTrait;
const DEFAULT_GROUPS = ['organization_list'];
/**
* #ParamConverter("contactList", class="SchemaBundle:ContactList")
* #param ContactList $contactList
* #return Response
*/
public function getAction(ContactList $contactList)
{
return $this->handleView($this->viewWithContext($contactList, Response::HTTP_OK));
}
Trait:
use FOS\RestBundle\Context\Context;
use FOS\RestBundle\View\View;
trait ViewContextTrait
{
public function viewWithContext($data, $statusCode = null, $groups = self::DEFAULT_GROUPS)
{
$context = new Context();
$context->setGroups($groups);
return View::create($data, $statusCode)->setContext($context);
}
}
My config.yml:
fos_rest:
routing_loader:
include_format: false
body_listener:
array_normalizer: fos_rest.normalizer.camel_keys
param_fetcher_listener: true
view:
view_response_listener: 'force'
format_listener:
rules:
- { path: '^/api', priorities: ['json'], fallback_format: json, prefer_extension: false }
The problem: When I call this route via postman (/api/contact-list/1), I always get a {} for my content in the Response object.
This is the dumped response:
What am I missing in order to return the serialized ContactList entity w/ the context group in my Response?
Solution:
First of all, my annotations weren't included. I had to add this to my config.yml:
framework:
serializer:
enabled: true
enable_annotations: true
Next, I forgot I had previously included JMS serializer into my project awhile back. Apparently the ViewHandler for the Rest Bundle has a default order of serializer services that it uses. I ended up having to include this in my config.yml:
fos_rest:
service:
serializer: fos_rest.serializer.symfony
By default, FOSRest was using JMS Serializer which wasn't configured properly.

SensioFrameworkExtraBundle

I'm using Symfony 3.3 and i'm trying to use FOSRestController to make an API.
Here is my config files :
# SensioFrameworkExtra Configuration
sensio_framework_extra:
view: { annotations: false }
# FOSRest Configuration
fos_rest:
format_listener:
rules:
- { path: '^/api', priorities: ['json'], fallback_format: 'json' }
- { path: '^/', stop: true }
view:
view_response_listener: true
Controller :
<?php
namespace AppBundle\Api;
use FOS\RestBundle\Controller\Annotations as REST;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\View\View;
class MyController extends FOSRestController
{
/**
* #REST\Get("/some-url")
* #REST\View()
*
* #return View
*/
public function getSomethingAction()
{
$view = View::create();
return $view;
}
}
The issue is about the view_response_listener, i'm having this error message :
(1/1) RuntimeException
You must enable the SensioFrameworkExtraBundle view annotations to use the ViewResponseListener.
Routing :
api_something:
type: rest
resource: AppBundle\Api\MyController
The bundle is already installed and added to the AppKernel.php file
Can something help me with this ?
Thanks
Remove your configuration of sensio_framework_extra :
sensio_framework_extra:
view: { annotations: false }
Because the default configuration is annotations: true (you can look at vendor/sensio/framework-extra-bundle/DependencyInjection/Configuration.php)
Let the default configuration of sensio_framework_extra https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html#configuration
You may have forgotten to activate the annotations of serializer in config.yml (https://symfony.com/doc/current/serializer.html#using-serialization-groups-annotations)
I suggest you to try this configs :
#app/config/config.yml
framework:
....
serializer: { enable_annotations: true }
fos_rest:
....
view:
view_response_listener: 'force'
Documentation of FosRestBundle view_response_listener :
http://symfony.com/doc/master/bundles/FOSRestBundle/3-listener-support.html
http://symfony.com/doc/master/bundles/FOSRestBundle/view_response_listener.html
Try to create the directory AppBundle/Api/Controller. And put your MyController.php into it. Your controller will be named
\AppBundle\Api\Controller\MyController
Copy the following code to your main services.yml
sensio_framework_extra.view.listener:
alias: Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener
Source of the solution:
https://github.com/FriendsOfSymfony/FOSRestBundle/issues/1768#issuecomment-340294485
another solution from mentioned source:
in bundles.php the Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle must be located after FOS\RestBundle\FOSRestBundle to work properly
Answer for the same question with Symfony 4.0
routes/rest.yaml Routing configuration
app_admin_users:
type: rest
resource: App\Controller\Api\Admin\User\UsersController
prefix: /api
fos_rest.yaml view response listener enabled
fos_rest:
view:
view_response_listener: true
framework_extra.yaml view annotations enabled
sensio_framework_extra:
view: { annotations: true }
config.yaml imports fos_rest and framework_extra
imports:
- { resource: fos_rest.yaml }
- { resource: framework.yaml }
- { resource: framework_extra.yaml }

Symfony3 Fos Rest Bundle - download pdf but return json on error

i have a problem with Fos Rest Bundle - everything works fine with jsons (response is always in this format, including errors). Now i'm stuck with PDF download. I have two problems:
I have to send Accept header with "application/pdf" to get pdf file, if i use other type, i'll get an error, that it can't be serialized to json.
Uncaught PHP Exception RuntimeException: "Your data could not be encoded because it contains invalid UTF8 characters."
I want to force the endpoint to always use pdf format, no matter what is in the Accept header.
When there's an error while generating PDF, the response should be in json format, with error details (it works for other endpoints). Right now, errors are returned in html format.
Keep in mind, that i'm using dev environment for now, so i.e. exception part in config.yml is commented out.
My routing:
/doc is nelmio api doc - works fine
/pdf/{id} should accept json and return pdf (with one exception - json on error) - not working as i want
/ (other endpoints) should accept json, and returns json (errors too) - works fine
What i have right now:
config.yml
fos_rest:
access_denied_listener:
html: true
twig: true
body_converter:
enabled: true
view:
default_engine: json
view_response_listener: force
templating_formats:
html: true
twig: false
mime_types:
pdf: ['application/pdf']
formats:
json: true
pdf: false
routing_loader:
default_format: json
include_format: false
format_listener:
rules:
- { path: '^/pdf', priorities: ['pdf'], fallback_format: json, prefer_extension: false }
# /doc is nelmio api doc, so html is correct here
- { path: '^/doc', priorities: ['html'], fallback_format: ~, prefer_extension: false }
- { path: '^/', priorities: ['json'], fallback_format: json, prefer_extension: false }
#exception:
#codes:
# 'ApiBundle\Exception\InvalidArgumentException': 400
# 'ApiBundle\Exception\Exception': true
serializer:
serialize_null: true
service:
view_handler: api.fos_rest.view_handler
services.yml
api.fos_rest.view_handler:
parent: fos_rest.view_handler.default
calls:
- ['registerHandler', ['pdf', ["#api.fos_rest.view_handler.pdf", 'createResponse']]]
api.fos_rest.view_handler.pdf:
class: ApiBundle\View\PdfViewHandler
PdfViewHandler.php
<?php
namespace ApiBundle\View;
use FOS\RestBundle\View\View;
use FOS\RestBundle\View\ViewHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class PdfViewHandler
{
public function createResponse(ViewHandler $handler, View $view, Request $request, $format)
{
return new Response($view->getData(), 200, $view->getHeaders());
}
}
DocumentController.php
<?php
namespace ApiBundle\Controller;
use ApiBundle\Document\FilledSurvey;
use FOS\RestBundle\Controller\Annotations as Rest;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
/**
* Class DocumentController
* #package ApiBundle\Controller
*
* #Rest\Route("/pdf")
*/
class DocumentController extends AbstractController
{
/**
* #param FilledSurvey $filledSurvey
* #return mixed
*
* #ApiDoc(
* section = "Document",
* description = "Get document",
* statusCodes = {
* 200 = "Document returned",
* }
* )
*
* #Rest\Get("/{id}", requirements={"id": "[a-zA-Z0-9]+"}, name="document_get")
* #Rest\View(serializerGroups={"???"})
*/
public function getAction(FilledSurvey $filledSurvey)
{
$path = $this->get('api.pdf.generator')->generate($filledSurvey);
return file_get_contents($path);
}
}

How can I separate POST requests by content-type?

I want to execute different actions depending on the client-provided content-type. I've configured 2 controller actions for this:
/**
* #Post("", requirements={"_format": "json"})
*/
public function postJsonAction(Request $request)
{
//
}
/**
* #Post("", requirements={"_format": "csv"})
*/
public function postCsvAction(Request $request)
{
//
}
However, this is not working for me, first action is always executed. What am I doing wrong here?
Here is my fos_rest configuration in the config.yml:
fos_rest:
param_fetcher_listener: true
allowed_methods_listener: true
routing_loader:
default_format: json
include_format: false
view:
mime_types: { 'csv': ['text/csv'], 'xlsx': ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'] }
formats:
json: true
csv: true
xlsx: true
format_listener:
rules:
- { path: '^/api', priorities: ['json', 'csv', 'xlsx'], fallback_format: ~, exception_fallback_format: json, prefer_extension: false }
service:
view_handler: app.view_handler
You can configure this in your route. For example, if you are using a yml route configuration.
csv:
path: /article/format/csv/
defaults: { _controller: AppBundle:Article:postCsv }
json:
path: /article/format/json/
defaults: { _controller: AppBundle:Article:postJson }
Then, using JS to change your form action.
if ("csv" === $("#format").val()) {
$('#your_form').attr('action', '/article/format/csv/');
}
else {
$('#your_form').attr('action', '/article/format/json/');
}

Cannot import resource error

I just installed FOSRestBundle I´m getting this error when I run cache:clear
[Symfony\Component\Config\Exception\FileLoaderLoadException]
Cannot import resource "/Users/gitek/www/hotel/src/Gitek/RegistroBundle/Resources/config/routing_incidencia.yml" from "/Users/gitek/www/hotel/app/config/routing.yml".
[RuntimeException]
The autoloader expected class "Gitek\RegistroBundle\Controller\IncidenciaController" to be defined in file "/Users/gitek/www/hotel/app/../src/Gitek/RegistroBundle/Controller/IncidenciaController.php". The file was found but the class was not in it, the class name or namespace probably has a typo.
This is my controller:
<?php
namespace Gitek\RegistroBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\RestBundle\View\View;
use Gitek\RegistroBundle\Entity\Registro;
use Gitek\HotelBundle\Entity\Incidencia;
class UsuarioController extends Controller
{
public function putIncidenciaAction($registro_id, $incidencia_id)
{
$em = $this->get('doctrine')->getEntityManager();
$registro = $em->getRepository('RegistroBundle:Registro')->find($registro_id);
$incidencia = $em->getRepository('HotelBundle:Incidencia')->find($incidencia_id);
$request = $this->getRequest();
$registro->setIncidencia($incidencia);
$em->persist($registro);
$em->flush();
$view = View::create();
$view->setData($registro);
return $view;
} // "put_incidencia" [PUT] /incidencia/{registro_id, incidencia_id}
}
This is my #app/config/routing.yml
incidencias:
resource: "#RegistroBundle/Resources/config/routing_incidencia.yml"
prefix: /
type: rest
This is my #src/Gitek/RegistroBundle/Resources/config/routing_incidencia.yml
incidencia:
type: rest
resource: Gitek\RegistroBundle\Controller\IncidenciaController
name_prefix: api_
Finally, this is my config for fos_rest on #app/config.yml:
fos_rest:
routing_loader:
default_format: null
view:
default_engine: twig
force_redirects:
html: true
formats:
json: true
xml: true
templating_formats:
html: true
view_response_listener: 'force'
failed_validation: HTTP_BAD_REQUEST
exception:
codes: ~
messages: ~
body_listener:
decoders:
json: fos_rest.decoder.json
xml: fos_rest.decoder.xml
format_listener:
default_priorities: [json, html, '*/*']
fallback_format: html
prefer_extension: true
service:
router: router
templating: templating
serializer: serializer
view_handler: fos_rest.view_handler.default
Any help or clue??
Your controller class should be named IncidenciaController instead of UsuarioController

Categories