Explanation of how symfony2 annotations work - php

I am using symfony2 annotations and want to know how cascading works in this format.
Lets say I have:
/**
* #Route("/reviews/{slug}", name="reviewDetail")
* #Template()
*/
first, then I check to see if that pulls any data. If not, I do a redirect to the following controller using the following redirect:
return $this->redirect($this->generateUrl('reviewsDate', array('date' => $slug)), 301);
which should go to:
/**
* #Route("/reviews/{date}", name="reviewsDate", defaults={"date" = null})
* #Template()
*/
then check to see if that pulls any data and, if not, create a fallback to this using a redirect:
/**
* #Route("/reviews", name="reviews")
* #Template()
*/
When I run a redirect:
if ($ctx->getReview($slug)) {
$review = $ctx->getReview($slug);
} else {
return $this->redirect($this->generateUrl('reviewsDate', array('date' => $slug)), 301);
}
I get this error:
This webpage has a redirect loop
The actions are all stacked in the order of acceptance, so I would check for the slug first, then the date, then if no result, kick it to the main reviews page.
I can change the route to be more specific, which would work, but it seems not as user friendly. For instance, if I wanted to have these multiple routes:
reviews/my-review: shows the specific review
reviews/2014: shows all reviews from the 2014 year
Is this the wrong way of of executing this functionality?

/**
* #Route("/reviews/{date}", name="reviewsDate", defaults={"date" = null})
* #Template()
*/
this is this same route as
/**
* #Route("/reviews", name="reviews")
* #Template()
*/
becouse you have default value null for date parameter so if you redirect to reviews you are going to reviewDate without parameter that causes endless loop.

Related

Symfony 5, EasyAdmin 3, how to filters users by roles [ManyToMany relation User to UserRoles]

Symfony 5.2.6
EasyAdmin 3.2.8
I am not a phpdev, my eng sucks, please be understanding :}
I am trying to filter users by their roles on index.
Adding and editing users works fine, initial data is loaded from fixutres.
When using userRoles filter I get 'No results found message'. No console errors or exceptions and it looks like its working but do not get proper results. Should i build my own filter and special query to handle that (help me please how) or this is just problem with my entities?
Thx in advance.
Attaching image with phpmyadmin and eaindex for better visualisation.
User.php
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Core\UserRole", inversedBy="users")
* #ORM\JoinColumn(nullable=false)
* #Assert\Count(
* min = 1,
* minMessage = "Choose at least one role.",
* )
*/
private $userRoles;
UserRole.php
/**
* #ORM\ManyToMany(targetEntity=User::class, mappedBy="userRoles")
*/
private $users;
UserCrudController.php
public function configureFilters(Filters $filters): Filters
{
return $filters
->add('email')
->add('userRoles')
->add('isActive')
;
}
phpmyadmin and easyadmin view

RESTful API: Data from multiple endpoints in one single view

I've started creating a RESTful API (well, I did my best, I'm trying to follow the patterns) and I have stumbled upon a scenario that I'm not really sure how to handle. I will explain the current structure:
My application has 4 controllers:
Customers
Payments
Log
Taking as example the Customers controller, I have defined the following actions:
GET /customers: returns a list of customers
POST /customers: creates a new customer
GET /customers/{id}: returns the customer with the provided id
PUT /customers/{id}: updates the customer with the provided id
DELETE /customers/{id}: destroys the customer
This is the full code of the Customer controller:
namespace App\Http\Controllers;
use App\Customer;
use Illuminate\Http\Request;
class CustomerController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
return Customer::all();
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$customer = Customer::create($request->all());
return response()->json($customer, 201);
}
/**
* Display the specified resource.
*
* #param \App\Customer $customer
* #return \Illuminate\Http\Response
*/
public function show(Customer $customer)
{
return $customer;
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param \App\Customer $customer
* #return \Illuminate\Http\Response
*/
public function update(Request $request, Customer $customer)
{
$customer->update($request->all());
return response()->json($customer, 200);
}
/**
* Remove the specified resource from storage.
*
* #param \App\Customer $customer
* #return \Illuminate\Http\Response
*/
public function destroy(Customer $customer)
{
$customer->delete();
return response()->json(null, 204);
}
}
The code is very similar in the other controllers. It's also important to note that:
A Customer can have multiple Payments
A Customer can have multiple records in the Log
The problem starts here:
I need to display in the front-end a summary page with all customer data (name, email, registration date, etc) and a box showing the number of payments made and another box showing the number of entries in the Log.
Do I need to make 3 requests? (One to /customers/id, other to customers/id/payments and other to customers/id/logs)
If I return all the customer related data in the customers/id call, am I breaking the RESTful convention?
I am using apigility, but my answer still will be related to your question. According to the REST terminology (which could be find here https://apigility.org/documentation/intro/first-rest-service#terminology ) You are talking about entity and collection.
/customers/id - entity,
/customers/id/payments - collection,
/customers/id/logs - collection.
These are 3 different requests. So, yes, you need make 3 different requests.
But, to be honest, if you don't need pagination over payments and logs you can have only one request to /customers/id and within response you can have fields with array
{
"_links": {
"self": {
"href": "http://localhost:8080/status/3c10c391-f56c-4d04-a889-bd1bd8f746f0"
}
},
"id": "3c10c391-f56c-4d04-a889-bd1bd8f746f0",
...
_payments: [
...
],
_logs: [
...
],
}
Upd (duplicate from comment for future visitors).
Also, you should pay attention to DTO. I suppose this link will be interesting https://stackoverflow.com/a/36175349/1581741 .
Upd2.
At current moment I treat your collection /customers/id/payments like this:
/payments?user_id=123
where user_id is filtering field on payments table.
I think your problem that you confuse your REST API with your database. They don't have to follow the same structure. You can easily return the whole nested JSON for GET /customers/{id} if that's what you need from your REST API.

Optional get parameter (with slashes) in default / route

The main page on my website is an empty link, like:
www.randomlink.com/
That's the controller with the "/" route. The problem is that I have to use get parameters here, according to the following pattern:
key1/value1/key2/value2
I add these parameters on form submit, and the form redirects back to the main page.
The problem is that, as you can see, I get:
www.randomlink.com/key1/value1/key2/value2
And thus it opens key1 controller, instead of the default one.
/**
* Display dashboard
*
* #Route("/{path}",
* name="dashboard",
* defaults={"path" = "-1"},
* requirements={"path" = ".+"})
* #Template()
*/
public function displayAction($path, Request $request)
{
if($_POST)
{
// add get parameters to $path
return $this->redirect($this->generateUrl('dashboard', ['path' => $path]));
}
// do something
}
How can I solve this issue?
Probably your routing configuration order is not correct: see "Earlier Routes always Win" in the docs
Workaround: What about using query string like: www.randomlink.com/?path=key1/value1/key2/value2, then $request->query->get('path') ?

Symfony - validate empty query parameter values

I am using the FOSRestBundle and was wondering is it possible to validate against empty query parameters using annotations?
For example when calling: /comments/1 an exception is thrown since both dealId and source query parameters haven't been set.
However calling /comments/1?dealId=1&source= is fine even though the source value hasn't ben set and doesn't match the regex outlined in the annotation.
Controller function:
/**
* Get a single comment.
*
* #Annotations\QueryParam(name="dealId", requirements="\d+", strict=true, description="The deal the comments belong to.")
* #Annotations\QueryParam(name="source", requirements="(forum|blog)", strict=true, description="The source of the comments.")
*
* #Annotations\View()
*
* #Annotations\Get("/comments/{id}", requirements={"id" = "\d+"})
*
*/
public function getCommentAction(Request $request, ParamFetcherInterface $paramFetcher, $id)
{
$dealId = $paramFetcher->get('dealId');
$source = $paramFetcher->get('source');
// TODO: Implement
return [ 'id' => $id, 'dealId' => $dealId, 'source' => $source ];
}
Update
I raised this issue on the FOSRestBundle's GitHub repo too and it looks as if what I am asking for is currently not possible due to the limitations of the Regex validator that is being used.
https://github.com/FriendsOfSymfony/FOSRestBundle/issues/814#issuecomment-49696288
If you want to force your parameters to be checked, you can change config file as explained in the documentation, Here is the sample:
fos_rest: param_fetcher_listener: force
Then you can set other options like strict, nullable accordingly.
See more details here :
http://symfony.com/doc/current/bundles/FOSRestBundle/configuration-reference.html (archive.org)
https://symfony.com/doc/3.x/bundles/FOSRestBundle/index.html#config-reference
https://symfony.com/doc/3.x/bundles/FOSRestBundle/annotations-reference.html
Just use the allowBlank option of the QueryParam. In your case you would set the allowBlank to false to get the expected behaviour:
The allowBlank option is NOT YET in the FOSRestBundle, but I provided a patch to the FOSRestBundle which has a good chance to land in the next release, version 1.5.0 of the bundle.
This is how your Controller would look like:
/**
* Get a single comment.
*
* #Annotations\QueryParam(name="dealId", requirements="\d+", strict=true, description="The deal the comments belong to.")
* #Annotations\QueryParam(name="source", requirements="(forum|blog)", strict=true, allowBlank=false, description="The source of the comments.")
*
* #Annotations\View()
*
* #Annotations\Get("/comments/{id}", requirements={"id" = "\d+"})
*
*/
public function getCommentAction(Request $request, ParamFetcherInterface $paramFetcher, $id)
{
$dealId = $paramFetcher->get('dealId');
$source = $paramFetcher->get('source');
}
The tricky part is allowing source and dealId to be empty but I think it's possible by
adding these parameters to your route (so they must be specified in order to access the controller) and using a string prefix for each parameter (i.e. dealid_ and source_), so it's possible to specify an empty value.
You'll also need to modify the regex requirements to allow empty values.
/**
* Get a single comment.
*
* #Annotations\View()
* #Annotations\Get("/comments/{id}/dealid_{dealId}/source_{source}",
* requirements={"id" = "\d+", "dealId" = "\d*", "source" = "(forum|blog)*"})
*/
public function getCommentAction(Request $request,
ParamFetcherInterface $paramFetcher, $id, $dealId, $source)
{
return [ 'id' => $id, 'dealId' => $dealId, 'source' => $source ];
}
#Annotations\QueryParam expects a nullable parameter to be set (true or false) if the strict parameter is used. Try setting it.
I guess you want:
#Annotations\QueryParam(name="dealId", requirements="\d+", strict=true, nullable=false, description="The deal the comments belong to.")
#Annotations\QueryParam(name="source", requirements="(forum|blog)", strict=true, nullable=false, description="The source of the comments.")
Also read more about QueryParam in the docs.
I am not familiar with symfony, but I think a simple
$dealId = isset($dealId) ? $dealId : '';
Would help your problem

Unable to guess how to get a Doctrine instance from the request information

I've got this "500 Internal Server Error - LogicException: Unable to guess how to get a Doctrine instance from the request information".
Here is my controller's action definition:
/**
* #Route("/gatherplayer/{player_name}/{gather_id}")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
{
// ...
}
And it doesn't work, probably because Doctrine 2 can not "guess"... So how do I make Doctrine 2 guess, and well?
The Doctrine doesn't know how to use request parameters in order to query entities specified in the function's signature.
You will need to help it by specifying some mapping information:
/**
* #Route("/gatherplayer/{player_name}/{gather_id}")
*
* #ParamConverter("player", options={"mapping": {"player_name" : "name"}})
* #ParamConverter("gather", options={"mapping": {"gather_id" : "id"}})
*
* #Template()
*/
public function createAction(Player $player, Gather $gather)
{
// ...
}
/**
* #Route("/gatherplayer/{name}/{id}")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
I didn't find any help in paramconverter's (poor?) documentation, since it doesn't describe how it works, how it guesses with more than one parameters and stuff. Plus I'm not sure it's needed since what I just wrote works properly.
My mystake was not to use the name of my attributs so doctrine couldn't guess right. I changed {player_name} to {name} and {gather_id} to {id}.
Then I changed the names of my id in their entities from "id" to "id_gather" and "id_player" so I'm now able to do that :
/**
* #Route("/gatherplayer/{id_player}/{id_gather}")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
which is a lot more effective than
* #Route("/gatherplayer/{id}/{id}")
Now I'm wondering how I can make this work
/**
* #Route("/gatherplayer/{player}/{gather}")
* #Template()
*/
public function deleteAction(Gather_Player $gather_player)
try this:
/**
* #Route("/gatherplayer/{player_name}/{gather_id}")
* #ParamConverter("player", class="YourBundle:Player")
* #ParamConverter("gather", class="YourBundle:Gather")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
The parameters on the signature of the #Route annotation must match the entities fields, so that Doctrine makes automatically the convertion.
Otherwise you need to do the convertion manually by using the annotation #ParamConverter as it's mentionned on the other responses.
#1ed is right, you should define a #paramConverter in order to get a Player instance or a Gather instance.

Categories