Symfony2 POST Request Required, Receiving GET Instead - php

I am trying to implement a search module with clean URLs. Something like www.website.com/search/searchterm. I have made a searchable index with EWZSearchBundle, so there is no database involved and therefore, no entity required.
public function searchAction(Request $request)
{
$form = $this->createFormBuilder()
->add('query', 'text')
->getForm();
if('POST' === $request->getMethod()){
$form->bind($request);
if ($form->isValid()) {
return $this->redirect($this->generateUrl('search_process', array('query' => $request->query->get('query'))));
}
}
return array(
'form' => $form->createView(),
);
}
I created a simple form without an entity and sent the form action to itself. Where it reads a if it a POST request I validate the form and send it to search process with a clean URL (www.website.com/search/searchterm).
public function searchProcessAction($query)
{
$search = $this->get('ewz_search.lucene');
$results = $search->find($query);
return array(
'results' => $results,
);
}
In the search process I get the search term from the clean URL and search for it in my index and return the results. It should be a very simple process only one problem.
Since I don't need to use an Entity, it never becomes a POST request and never gets inside the if('POST' === $request->getMethod()), and now that it becomes a GET request, it also spoils my whole thing about keeping the URL clean.
I know my way makes and extra redirect, but I don't know how else to keep a clean URL for search. I'm open to any suggestions about the whole process.

Some thoughts:
by a rule of thumb, a search action should be performed via GET method: you aren't creating anything, you are just querying your site for some results;
though clean URLs are nice and all, search functions should still take advantage of good ol' query syntax [http://path.to/search?q=termToSearchFor]; this way query string never gets cached, and you are sure to always fetch updated content [without needing do specify cache behaviour server side];
if your concern is to protect your data from certain traffic, consider implementing either authentication or a CSRF token in the form.
regarding this:
Since I don't need to use an Entity, it never becomes a POST request and never gets inside the if('POST' === $request->getMethod()), and now that it becomes a GET request, it also spoils my whole thing about keeping the URL clean.
This is just plain wrong: a POST request has nothing to do with Entities, it's just a mode you specify in request headers, in order to ask the server for a specific behaviour.
Your url will still be "clean" if you define it as /search/{query}, and update you action as follows:
public function searchAction($query){ ... }
But as I said before, query syntax is perfectly fine for search behaviour, and POST should not be used for such task.
A smart reading about RESTful principles - http://tomayko.com/writings/rest-to-my-wife .

You have to submit your form with POST method.
in HTML
<form action="YOUR ACTION" method ="post">
if you want to be sure that no one will come on this link in some other way (GET), then modify routing
rule_name:
pattern: /search/{query}
defaults: { _controller: AcmeBundle:Search:search }
requirements:
_method: POST

I managed to get it working without using the form component. I made the form manually, also accepted the query string format suggested by #moonwave99. Using the form component gives longer names like form[query] and form[_token] where it sends that form's CSRF token in the URL. Making the form manually allows better control on URL for query string format.
Note: Beware that at the same time, it removes CSRF security from that particular form.
Thanks for all the answers.

Related

Testing for POST in Yii 2.0

In my controllers that Gii creates it is common to see the following:
if($model->load(Yii::$app->request->post()) && $model->save()){
//.....do something such as redirect after save....//
}else
{
//.....render the form in initial state.....//
}
This works to test whether a POST is sent from my form && the model that I am specifying has saved the posted information (as I understand it).
I've done this similarly in controllers that I have created myself but in some situations this conditional gets bypassed because one or both of these conditions is failing and the form simply gets rendered in the initial state after I have submitted the form and I can see the POST going over the network.
Can someone explain why this conditional would fail? I believe the problem is with the 'Yii::$app->request->post()' because I have removed the '$model->save()' piece to test and it still bypasses the conditional.
Example code where it fails in my controller:
public function actionFreqopts()
{
$join = new FreqSubtypeJoin();
$options = new Frequency();
$model = new CreateCrystal();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
$model->insertFreqopts();
return $this->redirect(['fieldmap', 'id' => $join->id]);
} else {
return $this->render('freqopts', ['join' => $join, 'options' => $options]);
}
}
My initial thought was that I'm not specifying the correct "$model" in that I'm trying to save the posted data to FreqSubtypeJoin() in this case and the $model is CreateCrystal(); however, even when I change the model in this conditional it still fails. It would be helpful if someone could briefly explain what the method 'load' is actually doing in layman's terms if possible.
The load() method of Model class is basically populating the model with data from the user, e.g. a post query.
To do this it firstly loads your array of data in a form that matches how Yii stores your record. It assumes that the data you are trying to load is in the form
_POST['Model name']['attribute name']
This is the first thing to check, and, as long as your _POST data is actually getting to the controller, is often where load() fails, especially if you've set your own field names in the form. This is why if you change the model, the model will not load.
It then check to see what attributes can be massively assigned. This just means whether the attributes can be assigned en-mass, like in the $model->load() way, or whether they have to be set one at a time, like in
$model->title = "Some title";
To decide whether or not an attribute can be massively assigned, Yii looks at your validation rules and your scenarios. It doesn't validate them yet, but if there is a validation rule present for that attribute, in that scenario, then it assumes it can be massively assigned.
So, the next things to check is scenarios. If you've not set any, or haven't used them, then there should be no problem here. Yii will use the default scenario which contains all the attributes that you have validation rules for. If you have used scenarios, then Yii will only allow you to load the attributes that you have declared in your scenario.
The next thing to check is your validation rules. Yii will only allow you to massively assign attributes that have associated rules.
These last two will not usually cause load() to fail, you will just get an incomplete model, so if your model is not loading then I'd suggest looking at the way the data is being submitted from the form and check the array of _POST data being sent. Make sure it has the form I suggested above.
I hope this helps!

Best Practices and how to find get POST data from iOS AFNetworking in Symfony2 and return JSON in GET?

I am building a mobile app (iOS) and Symfony2 REST backend. On Symfony2, my routes are working correctly and I have tested them with AJAX and httpie, all CRUD operations, etc are fine. Now, I am trying to access the routes from the app. So far, I can access the routes and when I look into the Symfony2 Profiler, I can see entries in last 10 entries to verify that I am hitting the server with my POST and GET requests. Now, I have 2 questions and I would be glad if people can point me in the direction for ** Best Practices ** on how to proceed.
Problem 1: Although I am posting data which I can see coming in under "Request", when I try to create a record, it creates only NULL records, meaning the data is being lost. This is my controller for creating users for example:
public function postUserAction(Request $request)
{
$content = $this->get('request')->getContent();
$serializer = $this->get('jms_serializer');
$entity = $serializer->deserialize($content, 'Name\BundleName\Entity\User', 'json');
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return array(
'entity' => $entity,
);
}
When I look into the log, the only things that stand out are: Request Cookies (No cookies), Request Content: "Request content not available (it was retrieved as a resource)." This tells me the data was missing, how can I get this data and use it? Or what else could it be?
Problem 2: GET returns an empty JSON response with no data just the keys when I NSlog (echo it). My code looks like:
public function getUsersAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('NameBundle:User')->findAll();
return array(
'entities' => $entities,
);
}
From the log, it has the Request Cookies set: PHPSESSID => "1udududjjs83883jdlb4ho0j4" but again the Request Content says: "Request content not available (it was retrieved as a resource)." How can I make it return the data with the JSON? This works well in the browser AJAX and httpie tests.
Problem 3: Using AFNetworking, I have a symbolic constant which I set as the APIHost (IP Address) and APIPath was the folder. Now in my earlier version using native PHP, I constructed the actual code to be executed in index.php by sending the parameter in JSON so if I wanted a login, I sent something like todo:login but with Symfony2, I am not sure or know even the best practices for this case. Ideally, I would like to specify the server-side request in the JSON request and then find the correct route in Symfony2 but is this how to do it and if yes, can you please provide an example? The workaround is to specify hard coded paths in AFNetworking each time I need to make a request which I think tightly couples the code and I need to make changes in a lot of places anytime something changes on the server side. Thanks and sorry for the long question!
You expect the jmsserializer to do magic for you. But it won't, you have to configure it first. From you code I can see that you are using jmsserializer wrong.
In getUsersAction() you have to return a serialized response, but you are returning an array of objects. This would be the right way:
public function getUsersAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('NameBundle:User')->findAll();
$serializer = $container->get('jms_serializer');
return array(
'users' => $jsonContent = $serializer->serialize($entities, 'json');,
);
}
Your post action basically looks ok, however when the json does not contain every field of entity USER the deserialization will fail. You can configure the entity for serialization/deserialization using annotations.
http://jmsyst.com/libs/serializer/master/reference/annotations
I am not sure if I understood your last problem, but I think you have to hardcode the path in your app.
Symfony2 is great and absolutely useful when writing an API. But if you don't want to deal with serialization/deserialization you can give http://laravel.com/ a try. It is build on symfony and you can generate an api on the fly.

How to bind Symfony2 form with an array (not a request)?

Let say I have an HTML form with bunch of fields. Some fields belong to Product, some to Order, some to Other. When the form is submitted I want to take that request and then create Symfony forms for Product, Order, and Other in controller. Then I want to take partial form data and bind it with appropriate forms. An example would something like this:
$productArray = array('name'=>$request->get('name'));
$pf = $this->createForm(new \MyBundle\Form\ProductType(), $product);
$pf->bind($productArray);
if($pf->isValid()) {
// submit product data
}
// Do same for Order (but use order data)
// Do same for Other (but use other data)
The thing is when I try to do it, I can't get $form->isValid() method working. It seems that bind() step fails. I have a suspicion that it might have to do with the form token, but I not sure how to fix it. Again, I build my own HTML form in a view (I did not use form_widget(), cause of all complications it would require to merge bunch of FormTypes into one somehow). I just want a very simple way to use basic HTML form together with Symfony form feature set.
Can anyone tell me is this even possible with Symfony and how do I go about doing it?
You need to disable CSRF token to manually bind data.
To do this you can pass the csrf_protection option when creating form object.
Like this:
$pf = $this->createForm(new \MyBundle\Form\ProductType(), $product, array(
'csrf_protection' => false
));
I feel like you might need a form that embed the other forms:
// Main form
$builder
->add('product', new ProductType)
->add('order', new OrderType);
and have an object that contains association to these other objects to which you bind to the request. Like so you just have to bind one object with the request and access embedded object via simple getters.
Am I clear enough?

Pass user-supplied (but unrelated to the model) parameters to the controller?

This issue came up while maintaining an application written in CakePHP 1.3. Please bear in mind that while I can and do read the docs, my experience with Cake is quite limited.
The application has a Widget model and a WidgetController. When editing a Widget, one option the user has is to massively import data into the Widget in one of three modes: add to, remove from, or replace the widget data with what is imported.
The current implementation is a total mess (there is an "edit" action which performs all the 10 or so different mutation functions a Widget supports; it decides what to do exactly by sniffing the parameters from the submitted form), so I broke the "massive stuff" into a new action:
function batch($id)
{
// massively apply data to Widget $id; either add, remove or replace
}
This action is triggered by a form in the "edit" view:
// Only relevant elements shown
echo $form->create('Widget', array('enctype'=>'multipart/form-data', 'action' => 'batch')); ?>
echo $form->input('id');
echo $form->input('action', array('type'=>'select',
'options'=>array(
'append'=>__('Append', true),
'replace'=>__('Replace existing', true),
'delete'=>__('Delete specified', true)
));
echo $form->end(); ?>
As it stands, this form bundles the action parameter into the Widget array and the only way I can get hold of that from the controller is with $this->data['Widget']['action'].
This is ugly and semantically wrong, so I hope there's a better way to do it.
Ideally I 'd want to submit to the URL /widget/batch/X/append, but that's not possible because the append part is not fixed. So I 'd settle with any of these:
somehow pass the action as a controller parameter like $id
somehow pass the action as a named parameter, allowing $this->params['named']['action']
some other way that does not require installing a custom route and does not require JavaScript
Can Cake 1.3 do this?
No
no you cannot route a request to a different action based on a post parameter, nor with php alone change the action of a form. This is likely to be the same with any framework.
Ideally I'd want to submit to the URL /widget/batch/X/append, but that's not possible because the append part is not fixed.
The only way to do that is with javascript - of course it's not hard to write some appropriate js to do that.
However
If you don't want to use javascript to manipulate the form action when the user changes their selection, or just want the simplest solution - you can use setAction to internally redirect the request to more appropriate, dedicated, controller actions:
function batch() {
if (!empty($this->data['Widget']['action'])) {
$this->setAction($this->data['Widget']['action']);
}
}
function append() {
if ($this->data) {
if ($this->Widget->append($this->data)) {
$this->Session->setFlash('Widget appended successfully');
} else {
$this->Session->setFlash('Error: Unable to append widget');
}
}
}
In this way you can likely refactor the existing code into more manageable and logical methods.

Handling input with the Zend Framework (Post,get,etc)

im re-factoring php on zend code and all the code is full of $_GET["this"] and $_POST["that"]. I have always used the more phpish $this->_request->getPost('this') and $this->_request->getQuery('that') (this one being not so much logical with the getquery insteado of getGet).
So i was wondering if my method was safer/better/easier to mantain. I read in the Zend Framework documentation that you must validate your own input since the request object wont do it.
That leaves me with 2 questions:
What is best of this two? (or if theres another better way)
What is the best practice for validating php input with this methods?
Thanks!
I usually use $this->_request->getParams(); to retrieve either the post or the URL parameters. Then I use the Zend_Filter_Input to do validation and filtering. The getParams() does not do validation.
Using the Zend_Filter_Input you can do application level validation, using the Zend Validators (or you can write your own too). For example, you can make sure the 'months' field is a number:
$data = $this->_request->getParams();
$validators = array(
'month' => 'Digits',
);
$input = new Zend_Filter_Input($filters, $validators, $data);
Extending Brian's answer.
As you noted you can also check out $this->_request->getPost() and $this->_request->getQuery(). If you generalize on getParams(), it's sort of like using the $_REQUEST superglobal and I don't think that's acceptable in terms of security.
Additional to Zend_Filter, you may also use simple PHP to cast the required.
E.g.:
$id = (int) $this->_request->getQuery('id');
For other values, it gets more complicated, so make sure to e.g. quote in your DB queries (Zend_Db, see quoting identifiers, $db->quoteIdentifier()) and in views use $this->escape($var); to escape content.
You can't write a one-size-fits-all validation function for get/post data. As in some cases you require a field to be a integer and in others a date for instance. That's why there is no input validation in the zend framework.
You will have to write the validation code at the place where you need it. You can of course write some helper methods, but you can't expect the getPost() to validate something for you all by itself...
And it isn't even getPost/getQuery's place to validate anything, it's job is to get you the data you wan't, what happens to it from there on should not be it's concern.
$dataGet = $this->getRequest()->getParam('id',null);
$valid = new Zend_Validate_Digits();
if( isset($dataGet) && $valid->isValid($dataGet) ){
// do some...
} else{
// not set
}
I have always used the more phpish $this->_request->getPost('this') and $this->_request->getQuery('that') (this one being not so much logical with the getquery insteado of getGet).
What is best of this two? (or if theres another better way)
Just a quick explanation on the choice of getQuery(). The wording choice comes from what kind of data it is, not how it got there. GET and POST are just request methods, carrying all sorts of information, including, in the case of a POST request, a section known as "post data". A GET request has no such block, any variable data it carries is part of the query string of the url (the part after the ?).
So, while getPost() gets the data from the post data section of a POST request, getQuery() retrieves data from the query string of either a GET or POST request (as well as other HTTP Request methods).
(Note that GET Requests should not be used for anything that might produce a side effect, like altering a DB row)
So, in answer to your first question, use the getPost() and getQuery() methods, this way, you can be sure of where the data source (if you don't care, getParams() also works, but may include additional data).
What is the best practice for validating php input with this methods?
The best place to validate input is where you first use it. That is to say, when you pull it from getParams(), getPost(), or getQuery(). This way, your data is always correct for where you need it, and if you pass it off, you know it is safe. Keep in mind, if you pass it to another Controller (or Controller Action), you should probably check it again there, just to be safe. How you do this depends on your application, but it still needs to be checked.
not directly related to the topic, but
to insure that you get an number in your input, one could also use $var+0
(however if $var is a float it stays a float)
you may use in most cases
$id = $this->_request->getQuery('id')+0;

Categories