Non-mapped form, EntityType and data attribute - symfony

I use an EntityType to create a form, but not mapped on an entity. The form is long and the user re-use the same options many times, then when he valid the form, if it's valid, I store the $form->getData() in session.
When I generate the form I inject the $data. It works well for all options, except the EntityType, I don't understand why...
In the $data, I've an ArrayCollection with the objects selected in the EntityType, but the form doesn't select it. I've used mapped = false, because if I remove it I've an error:
Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
Someone I've an idea how to do ?

Settings mapped = false shouldn't be the solution in this case, because you need to write the stored data into this field, right? so mapped = false avoid it (see more about mapped option here)
The problem here is that the EntityType need to get the id value from each item and it requires that these items are managed actually by EntityManager:
Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
An entity is in MANAGED state when its persistence is managed by an EntityManager. In other words, an entity is managed if its fetched from the database or registered as new through EntityManager#persist.
In you case, these entities comes from session, so you have two option:
Re-query the stored entities from database:
if (isset($data['foo']) && $data['foo'] instanceof Collection) {
$data['foo'] = $this->getDoctrine()->getRepository(Foo::class)->findBy([
'id' => $data['foo']->toArray(),
]);
}
Or, set a custom choice_value option to avoid the default one:
$form->add('foo', EntityType::class, [
'class' => Foo::class,
'choice_value' => 'id', // <--- default IdReader::getIdValue()
]);

Related

Within a Callback validator for a Symfony sub-form collection, how do I get the value of a different field?

Let's say I have a Callback validator attached to a field in a collection sub-form that looks like this:
$callback = new Callback([
'callback' => function($obj, $context) {
// Value of field to validate, no problem
$value = $context->getValue();
// Get the form, but gets my parent form
$form = $context->getRoot();
// Try and get other field value I need to validate current field,
// but gets "child "otherfield" does not exist" error.
$otherValue = $form->get('otherfield')->getData();
// continue validation logic...
}
]);
I need the value of another field from the specific form instance for the collection item, but I can't find a way to access the child form instance. How do I do this? I can access the list of forms for the collection by using the parent field that holds the collection, but I need the specific instance that this is validating.
It's Symfony version 3.3, but I can't find anything in a later version, either.

Give formbuilder class value as 'hidden'

I would like to give formBuilder User Entity as hidden value.
$form->add('user','hidden',array("data" => $user))
$user is User Entity.
However it shows this error.
Expected argument of type "Acme\UserBundle\Entity\User", "string" given
If I use 'null' instead of 'hidden'
$form->add('user',null,array("data" => $user))
it doesn't show the error and shows the select box of user Entity.
However I would like to use hidden.
How can I make it??
You did't specify the field type correctly - this is the correct way:
...
$formBuilder->add('user', HiddenType::class);
...
...
$form = $formBuilder->getForm();
$form->get('user')->setData($user->getId());
But you can't assign entity to the hidden field, so you can assign user's id for user identification.
Another option is to make data transformer and define own EntityHiddenType - more on this here: symfony : can't we have a hidden entity field?

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
}
])
],
]);
});

Symfony creates empty association when checkbox used on embedded form

I have a form for Person that has an embedded form for Address (bi-directional one to one relationship, with Address as the owning side w/ FK).
If a user submits the form, Symfony will initialize an empty Address object and assign it to the $address property on the $person object. Instead, I want Symfony to recognize all the form fields for Address were blank and it should NOT initialize an empty Address object.
Is this possible?
EDIT: I discovered that this only happens when I have a checkbox on the embedded form type. If there is no checkbox, Symfony will NOT create an empty association object. I think the problem is an unchecked checkbox is assumed to be a "false" value, so Symfony has no choice but to interpret that as a submitted value. Still looking for a reasonable workaround.
It's possible to do this with form events
http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html
Search for FORM_SUBMIT
However, I find it a bit convoluted.
Consider instead treating Person and Address as independent objects then setting the relation in your controller after checking Address. Something like:
$person = new Person();
$address = new Address();
$formData = array('person' => $person, 'address' => $address);
$builder = $this->formFactory->create('form',$formData);
$builder->add('person', new PersonFormType());
$builder->add('address', new AddressFormType());
...
if ($form->isValid()
{
if ($address was filled in properly)
{
$person->setAddress($address);
}
---
I find the above approach a bit easier to understand than going through the form event stuff.

What's a good way to create an "agree to terms" checkbox in Symfony2?

I have a form that has an "I have read and agree to the terms of service" checkbox on it. The box must be checked in order for the form to be considered valid, but there's no reason to save that value to the database, of course, and there's no reason to add an attribute to the Entity.
What's a good way to implement this kind of functionality in Symfony2 such that the form won't be considered valid unless the box is checked?
This question is quite old and Symfony has updated this a lot. For Symfony 3.x you can do something like:
$builder->add('terms', CheckboxType::class, [
'mapped' => false,
'constraints' => new IsTrue(),
]);
From symfony docs:
When mapping forms to objects, all fields are mapped. Any fields on the form that do not exist on the mapped object will cause an exception to be thrown.
In cases where you need extra fields in the form (for example: a "do you agree with these terms" checkbox) that will not be mapped to the underlying object, you need to set the property_path option to false:
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('task');
$builder->add('dueDate', null, array('property_path' => false));
}
The field data can be accessed in a controller with:
$form->get('dueDate')->getData();

Resources