Nested form: passing values - symfony

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!

Related

Symfony 5 / Easy Admin 3 - FormBuilder added field not displaying appropiate input

I am building a form using Easy Admin's FormBuilder. My goal is to have an AssociationField which represents a OneToMany relationship, for example, to assign multiple products to a shop. Additionally, I only want some filtered products to be listed, so I overrode the createEditFormBuilder method in the CrudController, I used this question as reference, and this is the code for the overridden function :
public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createEditFormBuilder($entityDto, $formOptions, $context);
$filteredProducts = $context->getEntity()->getInstance()->getFilteredProducts();
$formBuilder->add('products', EntityType::class, ['class' => 'App\Entity\Product', 'choices' => $filteredProducts, 'multiple' => true]);
return $formBuilder;
}
I expected an Association field as the ones configured in the configureFields() function, however, the displayed field doesn't allow text search or autocomplete features, plus has incorrect height.
Expected:
Actual:
I tried to change the second argument in the $formBuilder->Add() function, but all specific EasyAdmin types threw errors.
UPDATE: I also tried using EasyAdmin's CrudFormType instead of EntityType, which doesn't support the 'choice' parameter. Still, the result was the same.
There is setQueryBuilder on the field, you can use it for filtering entities like this
<?php
// ...
public function configureFields(string $pageName): iterable
{
// ...
yield new AssociationField::new('products')->setQueryBuilder(function($queryBuilder) {
$queryBuilder
->andWhere('entity.id IN (1,2,3)')
;
})
;
// ...
}

Symfony, how show normal name of object not enetity number

I have problem with name of objects. In symfony 4, sonata admin.
When create field autocomplete with result from other enitity i got number of this object enitity, why i dont get normal name witch is saved in database ?
->add('child', ModelAutocompleteType::class, [
'property' => 'name',
'multiple' => 'true',
])
And there : Edite App\Entity\Invest..... want there normal name. Can anyone help ?
Try implementing "magic" method __toString() in you object class:
http://php.net/manual/en/language.oop5.magic.php#object.tostring
and inside it generate name of you object. So, add it like:
public function __toString()
{
return $this->foo;
}
Your function just has to return a string. Use variables you have in your object to generate that string.

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;
}
}

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

Symfony2 Form Entity to String Transformer Issue

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( ....

Resources