Symfony 2 Validate route params with FOSRestBundle - symfony

I have a delete route like so, using FOSRestBundle
/**
* #Route("/delete/{id}")
* #Security("has_role('ROLE_ADMIN')")
* #Rest\View
*/
public function deleteAction(Request $request, $id)
{
...
}
I want to make sure that the id parameter is numeric.
I tried :
#Route("/delete/{id}", requirements={"id" = "\d+"})
And
#QueryParam(name="id", requirements="\d+", description="User id")
And
#RequestParam(name="id", requirements="\d+", description="User id")
But none of these solutions work. Either the route is not found, or the constraint is not respected.

This works, but does not return a JSON encoded 404 response when trying to access a route like api/users/xx.json
#Rest\Delete("/{id}", requirements={"id" = "\d+"}, defaults={"id" = 1})

Related

Symfony Serializer with Groups not working - empty output

I am trying to serialise data as JSON with the default Symfony Serializer.
To do that I'm trying to use #Groups() as explained here:
https://symfony.com/doc/current/serializer.html
After adding the #Groups annotation as shown below:
class User implements UserInterface
{
// ...
/**
* #ORM\OneToMany(targetEntity=PortfolioItem::class, mappedBy="user", orphanRemoval=true)
* #ORM\OrderBy({"id" = "DESC"})
* #Groups({"show_user"})
*/
private $portfolioItems;
}
On my controller I have the following:
/**
* #param Request $request
* #return JsonResponse
* #Route("/async/portfolio/brands/get_chart", name="portfolio.brands.chart.data", options={"expose"=true}, methods={"POST", "GET"})
* #IsGranted("ROLE_USER")
*/
public function getDataForBrandsChart(Request $request): JsonResponse
{
$user = $this->getUser();
$portfolioItems = $user->getPortfolioItems();
$output = $this->serializer->serialize($portfolioItems, "json", ["groups" => "show_user"]);
return new JsonResponse($output, 200);
}
This always gives the following output:
[[]]
Why is it always empty?
The reason I am using the Groups is because without them I have the following error:
A circular reference has been detected when serializing the object of class "App\Entity\PortfolioItem" (configured limit: 1).
The problem was cache.
Restarting the server after the extra-bundle composer installation and running bin/console cache:clear solved the issue.

Route #Method annotation doesn't seem to get respected when matching routes

I understand when allowing similarly accessible routes, that the order of the routes matter.
Where I'm confused is why when submitting a DELETE request to this route, does it match to the GET route, instead of ignoring it and trying the matched method one below it?
/**
* #Route("/{game}")
* #Method({"GET"})
*/
public function single(Request $request, GameSerializer $gameSerializer, Game $game) {
$out = $gameSerializer->bind($game);
return new JsonResponse($out);
}
/**
* #Route("/{game}")
* #Method({"DELETE"})
*/
public function remove(Request $request, Game $game) {
$em = $this->getDoctrine()->getManager();
$em->remove($game);
$em->flush();
return new JsonResponse([], 200);
}
Full disclosure
I understand why it matches the top most route based on strictly patterns
I dont understand why the access method is getting ignored when doing so
So, just to test, I adjusted to move the DELETE based route up above the GET route
/**
* #Route("/{game}")
* #Method({"DELETE"})
*/
public function remove(Request $request, Game $game) {
$em = $this->getDoctrine()->getManager();
$em->remove($game);
$em->flush();
return new JsonResponse([], 200);
}
/**
* #Route("/{game}")
* #Method({"GET"})
*/
public function single(Request $request, GameSerializer $gameSerializer, Game $game) {
$out = $gameSerializer->bind($game);
return new JsonResponse($out);
}
only.. for this to happen when I tried getting an existing non-test record by performing a basic operation of visiting the url in a browser (so, GET)
and oh boy, did it ever delete that record.
Why is the Access Method being ignored?
First of all, careful of which SensioFrameworkExtraBundle version you are using because the #Method annotation from SensioFrameworkExtraBundle has been removed in latest version. Instead, the Symfony #Route annotation defines a methods option to restrict the HTTP methods of the route:
*
* #Route("/show/{id}", methods={"GET","HEAD"})
*
But in your case, if you're using HTML forms and HTTP methods other than GET and POST, you'll need to include a _method parameter to fake the HTTP method.
See How to Change the Action and Method of a Form for more information.
I think you have to add route name and it must be unique.
Try with following way:
/**
* #Route("/{game}",name="api_remove")
* #Method({"DELETE"})
*/
public function remove(Request $request, Game $game) {
...
}
/**
* #Route("/{game}",name="single_remove")
* #Method({"GET"})
*/
public function single(Request $request, GameSerializer $gameSerializer, Game $game) {
...
}

set Nelmio ApiDoc return parameter description

On the ApiDoc for our controller we have specified the output response object and now we see a list of all the parameters that get returned.
How do we provide values for the version and/or description fields on this list?
I have tried adding #ApiDoc(description="text") to the response object's parameters but that doesn't seem to be doing anything.
Thanks in advance.
This is a working API method from one of my projects:
/**
* Get an extended FB token given a normal access_token
*
* #ApiDoc(
* resource=true,
* requirements={
* {
* "name"="access_token",
* "dataType"="string",
* "description"="The FB access token",
* "version" = "1.0"
* }
* },
* views = { "facebook" }
* )
* #Get("/extend/token/{access_token}", name="get_extend_fb_token", options={ "method_prefix" = false }, defaults={"_format"="json"})
*/
public function getExtendTokenAction(Request $request, $access_token)
{
//...
}
All APIDoc parameters that get returned are grouped under "requirements".
I stepped through the ApiDocBundle today and see that Description comes from the comment on the model property or method with #VirtualProperty.
For example:
/**
* This text will be displayed as the response property's description
*
* #var \DateTime
* #JMS\Type("DateTime<'Y-m-d\TH:i:sO'>")
*/
protected $dateTimeProperty;
or
/**
* VirtualProperty comment
*
* #JMS\Type("integer")
* #JMS\VirtualProperty()
* #return integer
*/
public function getVirtualProperty()
{
return $this->someFunc();
}
The same applies to the all comments on the controller method.
I haven't used nelmioApiDoc but looking at the documentation for it, using description="text" in the annotation section seems correct. Have you tried clearing you cache:
php bin/console cache:clear --env=prod
Not sure if it is related.
This section describes how versioning objects is used, and looks like you have to use #Until("x.x.x") and #Since("x.x") in your JMSSerializerBundle classes. See this link.

Set requirements for get request parameters in symfony's controller

I have a controller which handles a GET request. I need to set requirement parameters for GET request, e.g.: 'http://localhost/site/main?id=10&sort=asc
My controller class
class IndexController extends Controller {
` /**
* #Route
* (
* "/site/main",
* name="main"
* )
*
* #Method("GET")
*/
public function mainAction(Request $request)
{
return new Response('', 200);
}
}
How could I do that?
UPD: I need to set requirement for URL parameters like
id: "\d+",
sort: "\w+"
Etc.
The same as symfony allows to do with POST request.
You can specify the requirements in the "#Route" annotation like this:
class IndexController extends Controller {
` /**
* #Route
* (
* "/site/main",
* name="main",
* requirements={
* "id": "\d+",
* "sort": "\w+"
* })
* )
*
* #Method("GET")
*/
public function mainAction(Request $request)
{
return new Response('', 200);
}
}
#Method is what you need http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/routing.html#route-method
If you try to use this route with POST, you will have 404
I couldn't understand your question well.
However, if what you need is to set up a filter mechanism for the GET method parameters as it is already available for the URL using route requirements, I think there is no ready to use tools for this in the Route component, as commented #Yoshi.
I had to do this kind of work myself and used this. I hope it helps you too
public function indexAction(Request $request)
{
// Parameter names used in the current request
$current_request_params=array_keys($request->query->all());
// $ALLOWED_INDEX_PARAMS should be declared as Class static property array and hold names of the query parameters you want to allow for this method/action
$unallowed_request_params=array_diff($current_request_params,PersonController::$ALLOWED_INDEX_PARAMS);
if (!empty($unallowed_request_params))
{
$result=array("error"=>sprintf("Unknown parameters: %s. PLease check the API documentation for more details.",implode($unallowed_request_params,", ")));
$jsonRsp=$this->get("serializer")->serialize($result,"json");
return new Response($jsonRsp,Response::HTTP_BAD_REQUEST,array("Content-Type"=>"application/json"));
}
// We are sure all parameters are correct, process the query job ..
}

Symfony2 locale isn't working properly

After reading documentation and looking for it with Google, I've to ask you.
I want to switch between 3 languages: ca_ES, es_ES and en_GB
So I did a controller like this:
/**
* #Route("/canviar-idioma/{locale}", name="change_lang")
* #Template()
*
* #return array
*/
public function canviarIdiomaAction($locale){
$request = $this->getRequest();
if ($locale == 'cat'){
$this->get('translator')->setLocale('ca_ES');
return new Response('ca');
} else if ($locale == 'es'){
$this->get('translator')->setLocale('es_ES');
return new Response('es');
} else if ($locale == 'eng'){
$this->get('session')->set('_locale', 'en_GB');
return new Response('en');
}
return new Response(null);
}
This controller is being called by ajax, when an user clicks a flag with the language. I receive the "ca" or "es" or "en" correctly, so controller is "working" somehow.
As you can see, I've tried using it by session or getting the translator. Both ways same results.
But, I made this controller to check if my locale really changed:
/**
* #Route("/quinlocaletinc", name="quinlocaletinc")
* #Template()
*
* #return array
*/
public function quinlocaletincAction(){
$request = $this->getRequest();
return new Response($request->getLocale());
}
And this locale ALWAYS gives "ca_ES" as it's the one defined in my parameters file:
locale: ca_ES
And my config.yml:
default_locale: %locale%
translator: { fallback: %locale% }
You need to use the "special" _locale variable in the route, Symfony will then properly set the locale for your application.
You can read more about this in the documentation
Your route should look like this:
/**
* #Route("/canviar-idioma/{_locale}", requirements={"_locale" = "ca_ES|es_ES|en_GB"}, name="change_lang")
* #Template()
*
* #return array
*/
public function canviarIdiomaAction() {
$locale = $request->getLocale();
// ...
Your second route will also need the parameter
/**
* #Route("/quinlocaletinc/{_locale}", name="quinlocaletinc")
* #Template()
*
* #return array
*/
public function quinlocaletincAction() {
$request = $this->getRequest();
return new Response($request->getLocale());
}
A good convention is to prefix all routes with the locale rather than postfix
/**
* #Route("/{_locale}/quinlocaletinc", name="quinlocaletinc")
* #Template()
*
* #return array
*/
public function quinlocaletincAction() {
$request = $this->getRequest();
return new Response($request->getLocale());
}
By using the _locale variable in Symfony, everything just "works" (i.e. if you visit /ca_ES/page all links on that page will include the right url).
Also when using the _locale parameter in your route, $this->get('translator')->setLocale('ca_ES'); is un-necessary as it will happen automatically.
Your annotation routing and Controller argument should be {_locale} and $_locale.
/**
* #Route("/canviar-idioma/{_locale}", name="change_lang")
* #Template()
*
* #return array
*/
public function canviarIdiomaAction($_locale)
{
// ...

Resources