Symfony2 Form Entity to String Transformer Issue - symfony

I am using Symfony 2.1.3-DEV and trying to accomplish transforming entity to string (ID of some kind) and then back from string to entity when form is submitted. The issue is the same if I'm using the transformer given in the cookbook:
http://symfony.com/doc/master/cookbook/form/data_transformers.html
Controller code:
$task = $entityManager->find('AcmeTaskBundle:Task', $id);
$form = $this->createForm(new TaskType(), $task); // so $task->issue is Issue object
I get this error:
The form's view data is expected to be an instance of class
Acme\TaskBundle\Entity\Issue, but is a(n) string. You can avoid this
error by setting the "data_class" option to null or by adding a view
transformer that transforms a(n) string to an instance of
Acme\TaskBundle\Entity\Issue.
The thing is, that I already have a transformer, which transforms TO string.
From the Form.php:
if (null !== $dataClass && !$viewData instanceof $dataClass) {
throw new FormException(
//...
);
}
Why $viewData is checked to be instance of data_class parameter (or the guessed type of given object)? Isn't view data supposed to be string/array etc.? Am I missing something?

After some digging step-by-step I found the problem that I was facing.
View data indeed must be the instance of class specified by data_class parameter. If you are using transformer Object -> string, you must set the data_class parameter to null.
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
));
}
By default, data_class is result of get_class of specified initial data. If you pass object to controller's createForm or some corresponding form-creator function, and no default value for data_class exists, it will be set to class of given object.
Still, the example given in the docs works fine - if form is inner (inside another form), data_class will not be set so it will be null.
As it's very rare to make form only from one field (text field in my transformer case), usually this form with transformer will be inside some other form, so it'll work fine.

I had the same problem because I accidentally typed in my controller:
$em->getRepository('AcmeWhateverBundle:Something')->findBy(array('id' => $id), array());
instead of:
$em->getRepository('AcmeWhateverBundle:Something')->findOneBy(array('id' => $id), array());
So If you're not using any custom data transformers check that $entity in the following line is an object of the same class defined as data_class in your FormType:
Scope Controller:
$form = $this->createForm(new SomethingType(), $entity, array( ....

Related

Entity without database

I'm using Symfony 2.7.x
My goals :
1/ A form where the user chooses the figures
2/ Form submitting
3/ Compute something with the help of the submitted data
4/ Display the value
Then I want to create a form, an entity but I don't need any database.
<?php
namespace RD\FicheBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use RD\FicheBundle\Entity\Donnees;
use RD\FicheBundle\Form\DonneesType;
class ThController extends Controller
{
// ... code
public function calculsAction(Request $request)
{
$donnees = new Donnees();
$form = $this->get('form.factory')->create(new DonneesType(), $donnees);
$form->handleRequest($request);
if ($form->isValid())
{
// What should I use right here to get my data from the form?
// ???????????????????????
// My calculation
$monney= $log_num*$surf_num*$ann_num*$chauff_num*$del_num;
$CO2 = 200*$log_num*$surf_num*$ann_num*$chauff_num*$del_num;
// Getting back the data to the template to display them
return $this->render('RDFicheBundle:Th:calculs.html.twig', array(
'form' => $form->createView(),
'monney' => $monney,
'CO2' => $CO2
));
}
// ... code
}
}
?>
How to get the data coming from the form without doing anything with a database? Should I use an entity?
Form arent't expecting any storage/database layer if you dont't tell them to. (like with field types: entity, and options on other ones.)
In your case:
Getting the data from submitted (or unsubmitted) form is simple as
$form->getData()
If you do this after your $form->handleRequest($request); line, you will get submitted data.
You can also check if you form was submitted with $form->isSubmitted(), which return an boolean true/false.
If you want your form to populate the submitted data to associated entity (object), you just need set data_class in your form type like this
/**
* #inheritdoc
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'Acme\AppBundle\Entity\Example'
]);
}
data_class option can be set also for separate, fields(types) of your form, if you got some more complex scenario.
These docs should help you here:
http://symfony.com/doc/current/book/forms.html#creating-form-classes
Also here you can see that forms are NOT persisted to db automaticly:
http://symfony.com/doc/current/book/forms.html#forms-and-doctrine

Date field type default only when no object is passed

I'm trying my hand at my first Symfony application, but as I'm working with forms, something a little counterintuitive is happening.
As you can see from my code, I have a date field that defaults to the current day. But when I pass an object to the form, this default overrides the object's current date.
I know this is how it is supposed to happen ('The default values for form fields are taken directly from the underlying data structure (e.g. an entity or an array). The data option overrides this default value.', from http://symfony.com/doc/current/reference/forms/types/date.html#data).
Is there any way to suppress this behaviour and only show the default if there is NO object passed?
$builder
// other code
->add('date', 'date', array(
'data' => new \DateTime()
))
// other code
I would probably set it directly in my new Entity, not fixed in a form
class YourClass
{
private $date;
//...
public function __construct()
{
$this->date = new \DateTime;
}
}

Nested form: passing values

I'm a bit at a loss here with Symfony nested forms..
I have Events and RoleEntries i.e. the role that people or organizations can have for an event...
So I have an EventType form, with a nested RoleEntryType:
->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$form->add('roleEntries', 'collection', array(
'type' => new RoleEntryType($data),
'allow_add' => true,
'allow_delete' => true
));
}
)
the $data variable is caught by the RoleEntryType constructor:
$this->data=$data;
And I try to add a hidden field (since the user should not modify it, in the field):
->add($this->targetScope, 'hidden', array('data'=>$this->data))
At this point, Symfony is not happy because it cannot convert the $data to string
An exception has been thrown during the rendering of a template ("Catchable Fatal Error: Object of class IH\EventManagerBundle\Entity\Event could not be converted to string") in form_div_layout.html.twig at line 13.
so I try just to give it an Id
->add($this->targetScope, 'hidden', array('data'=>$this->data->getId()))
But it doesn't work either because a string is not enough, it wants a fully fledged event:
Catchable Fatal Error: Argument 1 passed to IH\EventManagerBundle\Entity\RoleEntry::setEvent() must be an instance of IH\EventManagerBundle\Entity\Event, string given, called in /Users/MTP/Documents/dev/MTP/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 410 and defined
So I guess I'm doing it all wrong....
help!
$data contains the form data, so you need to extract your field value and pass it as value for your hidden field:
$form->add('targetScope', 'hidden', array('data' => $data['field_name']));
Without change your initial implantation, just add a __to string() magic method to Event entity and all should work as expected
Something like
Class Event
{
[...]
public function __toString()
{
return $this->name;
}
}
Of course $this->name; should be changed accordingly to your object properties.
If you need, when posting the form, to recreate (or take from db) an entity object, take a look to DataTransformers
Thanks for all your replies,
So using DataTransformer or creating a new field type (EntityHidden) both work... however, my problem stemed from something I forgot in my code which forced me to handle this in the FormType rather than in the entity...
Simply, there is no need to pass the primary key of the entity corresponding to the nesting form (Event) over to the nested form (RoleEntryType)... if I tell my Event entity setter that it should write a reference of the event in the new RoleEntry:
public function addRoleEntry(
{
$this->roleEntries[] = $roleEntry;
$roleEntry->setEvent($this);
return $this;
}
Sorry if my question wasn't clear and thanks for taking the time to answer!

Symfony2 calling controller function from buildform function

How do I call a custom function in the controller class of my bundle from the buildForm function of the AbstractType class of the same bundle?
My AbstractType:buildForm function works fine and generates my desired form but I have to add an extra field which will be a dropdown field of selectable options.
I need to dynamically generate the options for the dropdown list from data in the database - which I am already generating in the controller class.
Thanks to #sjagr, I've found a working solution.
Previously I had tried the following:
$form = $this->createForm(new SalesType(), new Sale(),
array(
'action' => $this->generateUrl('sales_add'),
'method' => 'POST',
'arguments' => array(1,2,3,4,5,6,7)
)
)
But I hadn't paid enough attention to the resulting error message: The option "arguments" does not exist. Known options are: "action", "allow_extra_fields" ...
I changed the arguments index of the array above to allow_extra_fields and my array arguments data was then available in the $options parameter of the buildForm function

Add custom validator on unmapped field, but with context of whole form submission?

tl;dr:
I need a custom validation constraint to run on an unmapped form field
I need the ID of the object the form is manipulating to eliminate it from consideration doing my validation constraint
Attaching the validation to the form itself or the unmapped field doesn't give me enough context to run my validation query
I have an unmapped field on my Person entity form that I need to run a validation on. I've followed this great article on how to do this, but my use case is slightly different and not entirely covered by the article.
I am making my own Unique Constraint that needs to run a custom query to determine the uniqueness. To run the query, I need access to the field value that was submitted, as well as the original Person object (so I can get it's ID if it's an update operation). Without also having the that Person object I won't be able to eliminate it from consideration during the uniqueness query.
If I apply the validator on the PersonType class like so:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver
->setDefaults(array(
'data_class' => 'CS\AcmeBundle\Entity\Person',
'constraints' => array(
new MyUniqueValidator(),
)
))
;
}
Then the validator gets passed the entire Person object to perform the validation on. This doesn't help me, because the submitted form data is not persisted to the Person object (it's an unmapped field that I handle after $form->isValid() is called in the controller).
If I apply the validator to the unmapped field directly instead:
$builder
->add('myUnmappedField', 'text', array(
'mapped' => false,
'constraints' => array(
new MyUniqueValidator(),
)
),
))
Then the object I get passed to the validator is just the standalone form text, and nothing else. I don't have the ID Person object (if it was an update operation) to perform by uniqueness query.
Hopefully I've explained this properly. Do I have any options to do this sort of validation gracefully?
You say you have unmapped field. Would it help, if you make it mapped to the Person entity? Just make a new property in the Person class with getter and setter methods, but not to the ORM, since you don't want it persisted.
If you do not want to polute your Person class, you can also make another composite class, which will hold your currently unmapped field and a Person object (you will then make it mapped). Ofcourse you will then set data_class to match the new object's namespace.
Both above solutions should work with the upper code you have there. Please let me know it it helped.
You can achieve this by using a callback constraint with a closure.
To access your Person entity, you will need to add the field via an event listener.
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$person = $event->getData();
$form->add('myUnmappedField', TextType::class, [
'mapped' => false,
'constraints' => [
new Symfony\Component\Validator\Constraints\Callback([
'callback' => function ($value, ExecutionContextInterface $context) use ($person) {
// Here you can use $person->getId()
// $value is the value of the unmapped field
}
])
],
]);
});

Resources