Create a route on symfony based on a json body - php

My goal is to have a route that if the body which is a json string contains the string "type":"pay" to apply the route.
I tried this:
/*
* #Route(
* path="/myfunction",
* condition="request.getContent() matches '/\\b\\"type\\":\\"pay\\"\\b/i'"
* )
*/
But until now I get exception because when it finds the double quote " before type it thinks that the condition part should be over.
And I get exception like this:
Caused by
Symfony\Component\Config\Exception\FileLoaderLoadException: [Syntax Error] Expected Doctrine\Common\Annotations\DocLexer::T_CLOSE_PARENTHESIS, got 'type' at position 147
Until now I tried the following
condition="request.getContent() matches '/\\b"type":"pay"\\b/i'"
condition="request.getContent() matches '/\\b\"type\":\"pay\"\b/i'"
condition="request.getContent() matches '/\\b\\\\\\\\"type\\\\\\\\":\\\\\\\\"pay\\\\\\\\"\\b/i'"
According to the documentation http://symfony.com/doc/3.4/components/expression_language/syntax.html
I am using symfony 3.4

What you could do, is create an EventListener that listens to the kernel.request-event and has a priority that puts it before Symfony's RouterListener::onKernelRequest() for finding routes.
In this custom listener you can add an attribute to the request, which you can fill with the data from the request body. It could look something like this:
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
// Maybe abort before reading content, if we don't expect a JSON-body
$jsonEncodedContent = $request->getContent();
// Decode JSON, validate
$request->attributes->set('content_type', ...);
}
Then in your route you should be able to access this attribute instead of reading the json body, you can access this attribute:
condition="request.attributes.content_type == 'pay'"
The downside to this approach is, that your listener must run at the right point, which might cause some issues later on as it's hard to test for this, and this might be hard to find and understand for new developers on your project.

Related

in Laravel route , why using default and why to struct route like this

Route::get('/atomic/{id}',[ApiController::class,'index'])->defaults('task', 'atomic');
why use defaults here and what is a task & atomic, and Api controller does not have an index function. Please explain this route properly.
I am new to laravel I tried to google for a solution but no result
defaults method helps to pass extra params to controller without passing as route params
As a backend engineer you’ll often be asked to produce URL patterns
that just don’t work with the rest of the site without breaking your
current routing structure. Often you’ll create what’s known as a slug
for your content, a simple hyphen separated string which is unique in
the system. A typical slug would be just generated from the title like
“My Simple Article” becomes as a slug my-simple-article. This way
there’s a unique string in the system for each post.
If you’ve already been implementing routes like this in your system
you’ll likely have urls that look like /post/{slug} but you know now
that’s not going to be good enough. Your company’s marketing team or
SEO wizards want it to be /{slug} and that’s pretty tricky. You can’t
create the pattern /{post-slug} because it’s going to confuse the
system. What is you have an About Us page or a Contact Us page which
equally important urls like /about-us and /contact-us respectively.
The problem here being that the routing system might pick up the
/about-us link and believe it’s meant to be a slug for a Post model.
At this point Laravel will simply not find the model and throw a HTTP
404 error instead. Not good.
This is where the ‘defaults’ method on routes comes into use to save
the day.
if I consider your example then
Route::get('/atomic/{id}',[ApiController::class,'index'])->defaults('task', 'atomic');
while hitting URL http://127.0.0.1:8002/atomic/1 then in the controller,you will get both params $id and $task
public function index($id,$task){
dump($task);
dump($id);
}
the output of the above will be atomic and 1
defaults() method nothing but key-value pair params
/**
* Set a default value for the route.
*
* #param string $key
* #param mixed $value
* #return $this
*/
public function defaults($key, $value)
{
$this->defaults[$key] = $value;
return $this;
}
suppose if you want to pass multiple array params then use setDefaults method like below
Route::get('/atomic/{id}',[ApiController::class,'index'])->setDefaults([
'tasks'=> 'atomics',
'postTitle'=>'post title goes here'
]);
then in controller
public function index($id,$tasks,$postTitle){
dump($tasks);
dump($postTitle);
dump($id);
}
now if you hit URL http://127.0.0.1:8002/atomic/1 then it will print
atomics
post title goes here
1
Ref : The Power of Laravel’s Route ‘defaults’ for making root level SEO pages

How to map Restler classes to POST method instead of GET?

I've used Restler v2 for years, and have finally started to work with v3. In v2, it seemed to simply be a matter of preceding the function name with get or post for it to be visible only to the specified method.
I'm still trying to understand how v3 implements the DocBLock comments to control all of this, and am simply unable to determine the correct way to make the API visible only with POST. I've been reading Routing Example, but am clearly missing something in the explanation.
I've tried variations of most everything shown on the example below as the starting point. It authenticates and works perfectly fine with GET - How do I make it visible only with POST? The error I get is typically 404 - not found, unless the DocBlock variations cause a general PHP error of some kind.
/**
* #param string $action {#from path}
* #param string $service {#from path}
*
* #return array
*/
protected function Perform ($action, $service)
{
...
}
As it turns out, a POST isn't just a POST....I was using the GET method with my browser and AJAX to test the API. When I switched AJAX to use POST, I didn't specify the datatype for the post. Once I did that, adding the post prefix worked as expected.

Symfony/Doctrine: Validating ID of relation BEFORE symfony resolves it to an entity on form submission

class Thing {
/*** loads of properties ***/
/**
* This is a many to many relation from Thing to Tags
* #var \Doctrine\Common\Collections\Collection
*
* #Assert\All({
* #Assert\Uuid()
* })
*/
private $tags;
/*** moar stuff ***/
}
/*** On the controller ***/
protected function validateAndPersist(Request $request, AbstractEntity $entity, $successHttpCode)
{
$form = $this->createForm($this->getFormDefinition(), $entity);
$form->submit($request);
if ($form->isValid() === true) {
$this->getDoctrineManager()->persist($entity);
$this->getDoctrineManager()->flush();
return $this->createView($entity, $successHttpCode);
}
return $form;
}
So I have this entity above, which has a matching Symfony form and it's all tied up nicely by a controller. The payload coming in is meant to send an array of UUIDs on to the tags property. This works just fine, relations are saved correctly.
So there's some validation in place to make sure we're receiving an array of UUIDs.
Problem is, however, precisely when I'm not receiving an array of UUIDs. $form->submit() is trying to resolve the incoming IDs into actual tags via doctrine. The postgres database is complaining that it's not a valid UUID (the relevant field is of type guid), and I get a Doctrine error. It feels as if this is happening BEFORE form validation, or perhaps validation is not performed at all on this field because of the many to many relation.
Either way this results on a Doctrine\DBAL\DBALException on which the message includes details of the SQL query and whatnot and a big juicy 500 error:
{
"code": 500,
"message": "An exception occurred while executing 'SELECT t0_.tag_id AS tag_id_0, t0_.name AS name_1 FROM tag t0_ WHERE t0_.tag_id IN (?)' with params [\"comedy\"]:\n\nSQLSTATE[22P02]: Invalid text representation: 7 ERROR: invalid input syntax for uuid: \"comedy\""
}
What I would obviously want is for validation defined on the entity to actually happen, as that would trigger a proper HTTP response without any boilerplate code to handle this specific case.
Any ideas?
Are you using another an embedded form for Tags in the main form?
In this case, you need to set cascade_validation property to true on it.

Using custom ParamConverter for POST Request in Symfony 2

I'm using Symfony 2.6 and the FOS Rest Bundle.
Param converters for PATCH , DELETE and GET requests work nicely and reduce the code in the controller actions. However for POST requests I have a problem. The default \Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter gets called every time. This results in an exception:
Unable to guess how to get a Doctrine instance from the request information.
I checked the \Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener and saw that it's always including the Doctrine param converter in the onKernelController method. From the documentation it seems that the doctrine param converter is automatically applied for all type hinted controller actions unless you set it to off:
sensio_framework_extra:
request:
converters: true
auto_convert: false
I found a kind of hacky way to prevent this. The array of param converters to be applied will be indexed by the name of the type hinted argument in the controller method (which symfony gets by reflection). If I just name my param converter the same as this type hint then the default doctrine param converter will not be added to the list of param converters. For example:
...
* #ParamConverter(
* "MyEntity",
* class="Foo\Bar\MyEntity",
* converter="my_custom_converter"
* )
*
* #param MyEntity $myEntity
* #return MyEntity
*/
public function postMyEntityAction(MyEntity $myEntity)
{
I sort of wrote this question as I was digging deeper into the code and I'm not even really sure what my question is anymore. I guess it's "Is it logical to apply multiple param converters?" or would also like to know if it's possible to turn off param converters for certain actions. Maybe my logic is completely wrong here and this isn't what param converters were intended for.
I'd appreciate any advice.
Alright, I realized where I was going wrong. It was a simple case of not returning true from my custom paramConverter apply method. If it does return true then the doctrine param converter won't be applied.

Unable to generate URL for named route

I'm trying to generate link to URL which contains two parameters (both of those parameters are not really necessary but I do it for practice). I created custom showAction in DiscovererController
/**
* #Route("/rivers/{river_id}/discoverers/{id}", name="discoverer_show")
* #Template
*/
public function showAction($river_id, $id){
$em = $this->getDoctrine()->getEntityManager();
$river = $em->getRepository('MyOwnBundle:River')->find($river_id);
if(!$river){
throw $this->createNotFoundException("no river with provided id");
}
$entity = $river->getDiscoverer();
return array('entity' => $entity);
}
As you can see two parameters are passed, id of the river and id of the discoverer (which is absurd but as I said, practice...).
In show action of a river (/rivers/1) I decided to put following code:
<p>{{entity.discoverer.name}}</p>
Note that 'entity' is a river here, and river has a discoverer. Unfortunatelly, when I try to render this action, I get error which tells me that:
An exception has been thrown during the rendering of a template ("Unable to generate a URL for the named route "discoverer_show" as such route does not exist.") in /path/to/project/src/My/OwnBundle/Resources/views/River/show.html.twig at line 9.
I dont have a clue what is wrong, I provided both necessary parameters and used "discoverer_show" which I defined in my controller. How to correctly render this link?
A piece of advice: do not use tabs in your source code at all! Make your IDE to replace tab character with 4 spaces. This could save you a lot of trouble... Tabs does not behave well in git too.
Ok, by accident i figured it out. Turns out annotations in symfony2 CANNOT begin with tab.
So this thing right here is NOT going to work
/**
* #Route("/people")
*/
But this will work like a charm:
/**
* #Route("/people")
*/

Categories