Can someone clearly explain how routes are supposed to be configured for REST requests using FOSRest? Every tutorial seems to do it differently.
My Controller:
<?php
namespace Data\APIBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DatasetController extends Controller{
protected function postDatasetAction(Request $request){
//Query here
}
The URL should look something like this: Symfony/web/app_dev.php/api/dataset. So I thought the routes should be something like...
app/config/routes.yml
data_api:
resource: "#DataAPIBundle/Resources/config/routing.yml"
prefix: /api
type: rest
And....
Data/APIBundle/Resources/config/routing.yml
data_query:
type: rest
pattern: /dataset
defaults: {_controller: DataAPIBundle:Dataset:datasetAction, _format: json }
requirements:
_method: POST
Please follow the next URL to read the official documentation:
http://symfony.com/doc/master/bundles/FOSRestBundle/index.html
To start with this bundle, I would suggest following the single restful controller documentation:
http://symfony.com/doc/master/bundles/FOSRestBundle/5-automatic-route-generation_single-restful-controller.html
You will also find clear examples (https://github.com/liip/LiipHelloBundle) about what this bundle can offer.
Few things from the snippets you have posted drew my attention:
The visibility of your controller method is protected whereas it should be public (http://symfony.com/doc/current/book/controller.html)
public function postDatasetAction(Request $request) {
// your code
}
The "routing.yml" file created to configure your route shall contain the name of the aforementioned controller method (postDatasetAction instead of DatasetAction):
# routing.yml
data_query:
type: rest
pattern: /dataset
defaults: {_controller: DataAPIBundle:Dataset:postDatasetAction, _format: json }
requirements:
_method: POST
Please find below an example to setup a route like :
get_items GET ANY ANY /items.{json}
# config.yml
fos_rest:
allowed_methods_listener: true
format_listener:
default_priorities: ['json', html, '*/*']
fallback_format: json
prefer_extension: true
param_fetcher_listener: true
routing_loader:
default_format: json
view:
formats:
json: true
mime_types:
json: ['application/json', 'application/x-json']
force_redirects:
html: true
view_response_listener: force
# routing.yml
categories:
type: rest
resource: Acme\DemoBundle\Controller\ItemController
<?php
namespace Acme\DemoBundle\Controller
use FOS\RestBundle\Request\ParamFetcher;
use FOS\RestBundle\Controller\Annotations as Rest;
class ItemController
{
/**
* Get items by constraints
*
* #Rest\QueryParam(name="id", array=true, requirements="\d+", default="-1", description="Identifier")
* #Rest\QueryParam(name="active", requirements="\d?", default="1", description="Active items")
* #Rest\QueryParam(name="from", requirements="\d{4}-\d{2}-\d{2}", default="0000-00-00", description="From date")
* #Rest\QueryParam(name="to", requirements="\d{4}-\d{2}-\d{2}", default="0000-00-00", description="End date")
* #Rest\QueryParam(name="labels", array=true, requirements="\d+", default="-1", description="Labels under which items have been classifed")
*
* #Rest\View()
*
* #param ParamFetcher $paramFetcher
*/
public function getItemsAction(ParamFetcher $paramFetcher) {
$parameters = $paramFetcher->all();
// returns array which will be converted to json contents by FOSRestBundle
return $this->getResource($parameters);
}
}
P.S. : You will need to add a view to display the resource as an HTML page
you are missing the routing part of FOSRestbundle in the controller:
protected function postDatasetAction(Request $request){
//Query here
} // "post_dataset" [POST] /dataset
Related
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.
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 }
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);
}
}
I'm trying to set up some routes in symfony2 for the following pattern:
www.myaweseomesite.com/payment/customer/{customernumber}/{invoicenumber}
Both parameters are optional - so the following scenarios must work:
www.myaweseomesite.com/payment/customer/{customerNumber}/{invoiceNumber}
www.myaweseomesite.com/payment/customer/{customerNumber}
www.myaweseomesite.com/payment/customer/{invoiceNumber}
I set up my routing.yml according to the symfony2 doc.
payment_route:
pattern: /payment/customer/{customerNumber}/{invoiceNumber}
defaults: { _controller: PaymentBundle:Index:payment, customerNumber: null, invoiceNumber: null }
requirements:
_method: GET
This works great so far. The problem is, that if both parameters are missing or empty, the route should not work. So
www.myaweseomesite.com/payment/customer/
should not work. Is there any way to do this with Symfony2?
You can define it in two routes to be sure to have only 1 slash.
payment_route_1:
pattern: /payment/customer/{customerNumber}/{invoiceNumber}
defaults: { _controller: PaymentBundle:Index:payment, invoiceNumber: null }
requirements:
customerNumber: \d+
invoiceNumber: \w+
_method: GET
payment_route_2:
pattern: /payment/customer/{invoiceNumber}
defaults: { _controller: PaymentBundle:Index:payment, customerNumber: null }
requirements:
invoiceNumber: \w+
_method: GET
Please note that you might have to change the regex defining the parameters depending of your exact needs. You can look at this. Complex regex have to be surrounded by ". (Example myvar : "[A-Z]{2,20}")
To elaborate on #Hugo answer, please find below the config with annotations :
/**
* #Route("/public/edit_post/{post_slug}", name="edit_post")
* #Route("/public/create_post/{root_category_slug}", name="create_post", requirements={"root_category_slug" = "feedback|forum|blog|"})
* #ParamConverter("rootCategory", class="AppBundle:Social\PostCategory", options={"mapping" : {"root_category_slug" = "slug"}})
* #ParamConverter("post", class="AppBundle:Social\Post", options={"mapping" : {"post_slug" = "slug"}})
* #Method({"PUT", "GET"})
* #param Request $request
* #param PostCategory $rootCategory
* #param Post $post
* #return array|\Symfony\Component\HttpFoundation\RedirectResponse
*/
public function editPostAction(Request $request, PostCategory $rootCategory = null, Post $post = null)
{ Your Stuff }
As per documentation:
http://symfony.com/doc/current/routing/optional_placeholders.html
set a default value for optional parameters in the annotations in the controller:
/**
* #Route("/blog/{page}", defaults={"page" = 1})
*/
public function indexAction($page)
{
// ...
}
This way you only need one route in routing.yml
I'm trying to make my controller work with param fetcher. I did all instructions specified in the documentation. So what I have:
config
fos_rest.yml:
fos_rest:
param_fetcher_listener: 'force'
view:
formats:
json: true
xml: false
rss: false
mime_types:
json: ['application/json', 'application/x-json', 'application/vnd.example-com.foo+json']
png: 'image/png'
failed_validation: HTTP_BAD_REQUEST
default_engine: twig
view_response_listener: 'force'
format_listener:
default_priorities:
- json
routing_loader:
default_format: json
sensio_framework_extra:
view: { annotations: false }
router: { annotations: true }
And my controller
<?php
namespace Push\PointsBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\View\View;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Request\ParamFetcherInterface;
use FOS\RestBundle\Controller\Annotations\RequestParam;
use FOS\RestBundle\Request\ParamFetcher;
class RestController extends Controller
{
/**
* #QueryParam(name="latitude", requirements="[0-9.]+", default="0", description="")
* #ApiDoc()
*/
public function checkPointsAction(ParamFetcher $params)
{
$view = new View();
return $this->get('fos_rest.view_handler')->handle($view);
}
}
When I calls method I get:
Controller
"Push\PointsBundle\Controller\RestController::checkPointsAction()"
requires that you provide a value for the "$params" argument (because
there is no default value or because there is a non optional argument
after this one).
What I did wrong or missed something? Thank you.
I often have problems with FOSRestBundle and outdated cache. It simply doesn't refresh sometimes if I make a change in annotations. First, try removing contents of your app/cache directory (rm -rf app/cache/*).
I didn't check if parameters are mapped to method arguments by a type or a name. If it's the later than your parameter should be called $paramFetcher (not $params):
public function checkPointsAction(ParamFetcher $paramFetcher) { }
Edit: have you enabled param fetcher listener?
fos_rest:
param_fetcher_listener: true
I had the same issue, as pointed out in the comment. I solved this by changing the Variable name from
public function addMapAction(ParamFetcher $pf) {
to
public function addMapAction(ParamFetcher $paramFetcher) {
And all the sudden i worked like a charm, apparently $paramFetcher is required.