Assume for a moment that this form utilizes an imaginary Animal document object class from a ZooCollection that has only two properties ("name" and "color") in symfony2.
I'm looking for a working simple stupid solution, to pre-fill the form fields with the given object auto-magically (eg. for updates ?).
Acme/DemoBundle/Controller/CustomController:
public function updateAnimalAction(Request $request)
{
...
// Create the form and handle the request
$form = $this->createForm(AnimalType(), $animal);
// Set the data again << doesn't work ?
$form->setData($form->getData());
$form->handleRequest($request);
...
}
You should load the animal object, which you want to update. createForm() will use the loaded object for filling up the field in your form.
Assuming you are using annotations to define your routes:
/**
* #Route("/animal/{animal}")
* #Method("PUT")
*/
public function updateAnimalAction(Request $request, Animal $animal) {
$form = $this->createForm(AnimalType(), $animal, array(
'method' => 'PUT', // You have to specify the method, if you are using PUT
// method otherwise handleRequest() can't
// process your request.
));
$form->handleRequest($request);
if ($form->isValid()) {
...
}
...
}
I think its always a good idea to learn from the code generated by Symfony and doctrine console commands (doctrine:generate:crud). You can learn the idea and the way you should handle this type of requests.
Creating your form using the object is the best approach (see #dtengeri's answer). But you could also use $form->setData() with an associative array, and that sounds like what you were asking for. This is helpful when not using an ORM, or if you just need to change a subset of the form's data.
http://api.symfony.com/2.8/Symfony/Component/Form/Form.html#method_setData
The massive gotcha is that any default values in your form builder will not be overridden by setData(). This is counter-intuitive, but it's how Symfony works. Discussion:
https://github.com/symfony/symfony/issues/7141
Related
I'm trying implement file uploading functionality for my app with Symfony 3.
I have a product entiry, that have relation to File entiry.
Part of Product:
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\File", mappedBy="product")
* #ORM\OrderBy({"weight" = "DESC"})
*/
protected $files;
and field on form:
->add('files', FileType::class, array('multiple'=> true, 'data_class'=> 'AppBundle\Entity\File'));
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Product',
));
}
As you can see, I'm set data_class.
and in controller I'm trying handle form
public function addAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$product = new Product();
$product->setAddedBy($this->getUser());
$form = $this->createForm(ProductType::class, null);
$form->handleRequest($request);
...
and I have an error:
Expected argument of type "AppBundle\Entity\File", "Symfony\Component\HttpFoundation\File\UploadedFile" given
If I drop data_class mapping I have no error and no object, just array.
How I can resolve this error, how to transform UploadedFile to File (Entiry). I'm trying to create Transformer, but I just got the ProductEntiry class, and as result can't process it, becouse it's without files.
Before I'll get to the point, just one suggest. In line:
$form = $this->createForm(ProductType::class, null);
I would provide $product variable so it will be automatically filled with data instead of creating new one. So it should be changed to :
$form = $this->createForm(ProductType::class, $product);
Ok, now, the problem occurs, because you probably have in your Product class a setter like:
public function addFile(AppBundle\Entity\File $file) { ... }
Then, after successful validation, the form tries to fill instance of Product class with data from the form, which contains Symfony's UploadedFile class instance. I hope you understand that.
Now, you have (at least) two possible solutions.
You can set "mapped" => false option for the file field. That will stop form from trying to put it's value into underlying object (Product instance).
After doing that you can handle the value on your own, which is handle file upload, create AppBundle/Entity/File instance and put it into $product variable via setter.
That the lazy solution, but if you would like to do the same in other forms, you will have to copy the code to every controller that needs it. So it's easier only for one time usage.
The right solution would be to convert UploadedFile to you File object with a Data Transformer. It's a longer topic to talk about and exact solution depends on your data flow that you want to achieve. Therefore if you want to do this right, read about Data Transformers in Symfony's docs first.
I promise that you will thank yourself later if you do that the right way. I've spent some time on understanding Symfony Form Component including Data Transformers and solved a similar issue that way. Now it pays back. I have reusable image upload form that handles even removing previously uploaded files in edit forms.
P.S.
It's "entity", not "entiry". You've wrote "entiry" twice, so I'm just saying FYI.
I'm implementing patch method using fosrestbundle and I want to create proper patch method.
To do so I've created controler and there is patchAction which takes an argument Entity, Entity is created passed via ParamConverter which I wrote myself. The Entity is passed to EntityType and here's the problem. I want to update only fields that changed and when I pass Entity to form it set nulls to object that comes from request. Entity is POPO
Here's the flow
User sends PATCH request to /entity/{Entity} let's say /entity/12
Param converter converts 12 to proper Entity asking DB for the data
EntityFormType takes Entity as argument and sets data from request to entity.
Entity is stored to DB
The problem is that form after it takes whole Entity object it sets null for fields that are null on form. I'd prefer if it took these values and set it for example as defaults.
I don't and can't use doctrine ORM.
The code:
/**
* #ParamConverter("Entity", class="Entity")
*/
public function patchAction(Entity $entity, Request $request)
{
var_dump($entity); // object mapped from DB
$form = $this->createForm(new EntityType(), $entity);
$form->handleRequest($request);
$form->submit($request);
var_dump($entity);exit; //here I get only values that i passed through patch method, rest of them is set to null
}
I was thinking about form events or creating something like diff method but probably there is better solution?
You need to create your form with method option set.
$form = $this->createForm(new EntityType(), $entity, array(
'method' => $request->getMethod(),
));
If request is send with PATH method then Symfony will update only sent fields.
How to fake PATCH method in Symfony: http://symfony.com/doc/current/cookbook/routing/method_parameters.html#faking-the-method-with-method
I use this code
$builder->add('userTasks','collection',array('type' => new UserTaskType()));
This is working fine
userTasks will be a collection different userTask objects which will in turn create the form.
Now is there any way that i can pass that individual UserTask object in the constructor like this
$builder->add('userTasks','collection',array('type' => new UserTaskType($userTask)));
so that i can use that to generate Label for the form.
Is it possible
If i use $user->UseTasks then that will be a collection of all tasks but i only want that object whose form is being created
If I understand the question correctly, you want to adjust the form based on the actual data object which the form will be bound to? It is one of the those simple sounding requirements that is actually a bit involved.
You need to use the form event system:
http://symfony.com/doc/master/cookbook/form/dynamic_form_generation.html
It's not so bad once you have worked through it.
===
To answer the first comment:
public function preSetData(DataEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$data will be your object, in this case a $userTask.
I create my forms via extending Zend_Form. And I use one Form for addAction() and editAction(). When I want to remove Elements within the editing process I can do so easily via $form->removeElement('x').
But what would be the best approach on removing a field from the validator?
1) Removing and Adding the newly set validator
//Controllers editAction()
$form->removeValidator('Db_NoRecordExists');
$form->addValidator('Db_NoRecordExists', true, array(
'table'=>'table',
'field'=>'field',
'exclude'=>array(
'field'=>'id',
'value'=>$this->_getParam('id')
)
));
2) Injecting editing ID into the Form
//Forms Contstructor
public function __construct($idToEdit=0, $options=null)
{
$this->setIdToEdit($idToEdit);
parent::__construct($options);
}
//within init()
$formField->addValidator('Db_NoRecordExists', true, array(
'table'=>'table',
'field'=>'field',
'exclude'=>array(
'field'=>'id',
'value'=>$this->getIdToEdit()
)
));
//Controller calling the form like this:
$form = new Custom_Form($this->_getParam('id'), $options);
3) Something else?
Maybe there is even something else I am missing, to me though somehow both ideas don't look too well to me.
For a cleaner use of SO here the answer as a post
//SOLUTION Okay, so while browsing to Zends Sourcecode (should have done that before asking...) i found the best solution (i guess). The Abstract DB Validation classes got a
function setExclude() so we can then use it in a nice flow:
//Inside Controller before valling $form->isValid()
$form->getElement('x')->getValidator('Db_NoRecordExists')->setExclude(array(
'field'=>'some_id',
'value'=>$idToEdit
))
I am using Symfony with propel to generate a form called BaseMeetingMeetingsForm.
In MeetingMeetingsForm.class.php I have the following configure method:
public function configure() {
$this->useFields(array('name', 'group_id', 'location', 'start', 'length'));
$this->widgetSchema['invited'] = new myWidgetFormTokenAutocompleter(array("url"=>"/user/json"));
}
In MeetingMeetings.php my save method is simply:
public function save(PropelPDO $con = null) {
$this->setOwnerId(Meeting::getUserId());
return parent::save($con);
}
However propel doesn't know about my custom field and as such doesn't do anything with it. Where and how to I put in a special section that can deal with this form field, please be aware it is not just a simple save to database, I need to deal with the input specially before it is input.
Thanks for your time and advice,
You have to define a validator (and/or create your own). The validator clean() method returns the value that needs to be persisted.
In Doctrine (I don't know Propel) the form then calls the doUpdateObject() on the form, which in turns calls the fromArray($arr) function on the model.
So if it's already a property on your model you'll only need to create the validator. If it's a more complex widget, you'll need to add some logic to the form.