Symfony2 forms - structured data to flat entity - php

Is it possible to map this data by SF2 form
[
'name' => 'XL',
'dimensions' => [
'width' => 50,
'height' => 20,
'length' => 20,
]
]
to the entity
Box[name, width, height, length]
Something like:
$builder->add('dimensions.width', 'text', [
'property_path' => 'width'
])
Thanks!

Short version no. The dimensions array key above would have to be an embedded form, however you could set the data class to be the same object which would (probably) work. E.g.
class DimensionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('width')->add('height')->add('length');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([ 'data_class' => 'MyClass' ]);
}
}
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name')->add('dimension', new DimensionType(), [ 'mapped' => false, 'data' => $options['data'] ]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([ 'data_class' => 'MyClass' ]);
}
}
So you have two form types, one of which is embedded in the other. For MyFormType, the dimension field isn't mapped to MyClass, however it has the same data_class option which means when the form resolves it should set the width, height and length properties on the object.
I haven't tested this but in theory this should work. Ideally you'd change the submitted data to match your object (as Symfony forms are only views of your objects) but this might be a decent patch.

Related

Symfony: How to disable input TextType for name attribute inside CollectionType with own condition

(Symfony 5.2, doctrine, PHP 8.0)
Take a look at this sample code:
more described here: https://symfony.com/doc/current/form/form_collections.html
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('description');
$builder->add('tags', CollectionType::class, [
'entry_type' => TagType::class,
'entry_options' => ['label' => false],
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Task::class,
]);
}
}
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('name');
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$tag = $event->getData();
$form = $event->getForm();
dd($tag); // empty because of prototype: true (as I said need it for adding new t
});
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Tag::class,
]);
}
}
TABLES:
TASK: id, name
TAG: id, task_id, name
my problem:
I would like to set - HTML attribute disabled for input name that meets the condition.
I need to get Task ID inside TagType so I can check if this id is used in another table and if so - disable changing tag name.
I tried multiple things but nothing worked for me.
The main problem is its a prototype (need it for adding/removing) and I don't receive tag id inside $tag = $event->getData(); in TagType so I can't get task id.
any idea? I didn't found the same problem on the internet and I'm not able to figure it out alone.
Something you can try is to use the PRE_SET_DATA event on the task form side to get the ID, and pass that ID to your tag form with the entry_options. Something like (not tested, but you'll get the idea) :
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('description');
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$task = $event->getData();
$form = $event->getForm();
$form->add('tags', CollectionType::class, [
'entry_type' => TagType::class,
'entry_options' => [
'label' => false,
'task_id' => !empty($task) ? $task->getId() : null,
],
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
]);
});
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Task::class,
]);
}
}
class TagType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// $options['task_id']
// Use your taskId here. The value can be null, on the task creation form.
$builder->add('name');
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Tag::class,
'task_id' => null,
]);
}
}

Symfony3 pass form data to collection inside collection

I have some nested forms with CollectionType and seems that the data from the constructor is not passed to the 2nd nesting level.
I simplified my form classes, just with I think's important (if you want more info jut tell me in the comments).
The bottom level form is entirely generated depending on the Activity entity class:
class ActivityServiceCreationType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use ($options, $router) {
$activity = $event->getData();
dump($activity); //JUST TO TEST
$form = $event->getForm();
... //$form->add of all necessary fields
}
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Activity',
...
);
}
}
Over the ActivityServiceCreationType I have the next form that is just a collection of the previous one:
class ActivityServiceCreationMultipleType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('activities', CustomCollectionType::class, [
'entry_type' => ActivityServiceCreationType::class,
'entry_options' => $options,
'mapped' => true,
'allow_add' => true,
'show_add_link' => true,
])
;
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use ($options) {
$data = $event->getData();
dump($data); //To test the data arriving to this form
});
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => null,
...
));
}
}
Then I have the "main" form, with I create from the controller:
class ActivityServiceCreationCollectionType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('selectAll', CheckboxType::class, [...])
...
;
$builder->add('multipleActivities', CustomCollectionType::class, [
'entry_type' => ActivityServiceCreationMultipleType::class,
'entry_options' => [
"router" => $options["router"],
"em" => $options['em'],
"basePeriod" => $options['basePeriod'],
'fit' => $options['fit'],
'periods' => $options['periods'],
'activities' => $options['activities']
],
'mapped' => true
])
;
}
From the controller I want to set the Activity objects to the ActivityServiceCreationType form, so the fields can be created. And I'm doing it like this:
$form = $this->createForm(ActivityServiceCreationCollectionType::class,
["multipleActivities" => ["activities" => $activities]],
[
"router" => $this->get("router"),
"em" => $this->getEm(),
"periods" => $periods,
"basePeriod" => $basePeriod,
'fit' => $fit
]);
As you can see the data for the form is:
["multipleActivities" => ["activities" => $activities]]
The results for the dumps that I put in the code is the following:
For the first dump, in the ActivityServiceCreationMultipleType I get an ArrayCollection of Activities
witch is what is expected, no problem here,
But in the second dump, in the ActivityServiceCreationType, I'm getting null. Here what I expected is an Activity entity for each one of the forms in the collection, right?
Can you tell me where I'm wrong?
--
Edited to add more info:
I've been trying to know when the data is "lost" and added some code to the event in the collection type.
In the ActivityServiceCreationMultipleType changed the POST_SET_DATA this way:
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use ($options) {
$data = $event->getData();
dump($data);
$form = $event->getForm();
$form->get('activities')->setData($data);
});
Now the dump() in the ActivityServiceCreationMultipleType (that you see in the last code snippet) shows the array of activities. Directly the activities.
And the dump() in ActivityServiceCreationType is executed 36 times (one for each activity) with null...
Don't know why it seems to be passing the data to the last embedded form, but the event can not get it.
EDIT
The configurateOptions of ActivityServiceCreationCollectionType must be:
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => null,
'multipleActivities' => null
);
}
The configureOptions of ActivityServiceCreationMultipleType.
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => null,
'entry_options' => null,
'router" => null,
'em" => null,
'basePeriod" => null,
'fit' => null,
'periods'=>null,
'activities' => null
));
}
The configureOptions of ActivityServiceCreationType.
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Activity',
'entry_options' => null,
'router" => null,
'em" => null,
'basePeriod" => null,
'fit' => null,
'periods'=>null,
'activities' => null
));
}
In conclusion, you must always indicate every external property that you want to pass to the form in configureOptions

Symfony2 Form builder add entity 'Could not load type entity'

I've created a form type in Symfony that extends the Abstract type, and added the fields using the builder, but no matter what I do it won't work!
class MyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
$builder->add('other', 'entity', array(
'data_class' => 'My\App\DefaultBundle\Entity\Other'
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'runSpeeds' => 'My\App\DefaultBundle\Entity\My',
));
}
public function getName()
{
return 'my';
}
}
Could not load type "entity" 500 Internal Server Error -
InvalidArgumentException
"My" Entity has a column which references the id of "Other" using a foreign key constraint. I want my form to basically have a drop down in the form for "My" that displays all the values from the "name" column in the "Other" entity using the Other.id -> My.other_id as reference.
Update
I have an OtherType (Form type) and the following will work:
$builder>add('name', new OtherType(), array(
'data_class' => 'My\App\DefaultBundle\Entity\Other')
)
But this displays the entire entity in the form. I only want one field from the Other entity to display, and in a dropdown with the choices
You didn't provide the required option class.
As mentioned in the documentation of entity Field Type
EDIT:
Moreover you have two syntax issues (";" is missing)
$builder->add('name', 'text')
$builder->add('other', 'entity', array(
'data_class' => 'My\App\DefaultBundle\Entity\Other'
))
Use the required class attribute, as defined in the basic usage http://symfony.com/doc/current/reference/forms/types/entity.html#basic-usage
$builder->add('other', 'entity', array(
'class' => 'DefaultBundle:Other'
))
If your Other class implements a __toString() method you can use that to determine the label. You can also use property for that:
$builder->add('other', 'entity', array(
'class' => 'DefaultBundle:Other',
'property' => 'name',
))
You need to add this:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'My\App\DefaultBundle\Entity\My'
));
}
And change data_class to class in the following lines:
$builder->add('other', 'entity', array(
'data_class' => 'My\App\DefaultBundle\Entity\Other'
));

Issues with form inheritance

I have a PersonType form and then I have LegalPersonType and NaturalPersonType forms and both extends from PersonType since they have a common field on that form (mapped at Entity level). For example, this is the code for NaturalPersonType.php
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Tanane\FrontendBundle\DBAL\Types\CIType;
use Tanane\FrontendBundle\Form\Type\PersonType;
class NaturalPersonType extends PersonType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('identification_type', 'choice', array(
'label' => 'Número de Cédula',
'choices' => CIType::getChoices()
))
->add('ci', 'number', array(
'required' => true,
'label' => false,
'attr' => array(
'maxlength' => 8,
))
)
->add('lives_in_ccs', 'checkbox', array(
'label' => false,
'required' => false,
'value' => 1,
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Tanane\FrontendBundle\Entity\NaturalPerson'
));
}
public function getName()
{
return 'natural_person';
}
}
Then at SaveFormController/orderAction() I'm doing this:
$order = new Orders();
$orderForm = $this->createForm(new OrdersType(array($type)), $order, array('action' => $this->generateUrl('save_order')));
But any time I try to render the form I get this error:
Neither the property "nat" nor one of the methods "getNat()", "nat()",
"isNat()", "hasNat()", "__get()" exist and have public access in class
"Tanane\FrontendBundle\Entity\Orders".
Relationship are at Entity level, how I fix that error?
Thanks in advance
1st possible solution
Following suggestions from user here I change, in OrderType.php Form my code to this:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Tanane\FrontendBundle\DBAL\Types\SFType;
class OrdersType extends AbstractType {
/**
* #var string
*/
protected $register_type;
public function __construct($register_type)
{
$this->register_type = $register_type;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// here goes $builder with default options remove for see less code
if ($this->register_type[0] == "natural")
{
$builder->add('nat', new NaturalPersonType(), array(
'data_class' => 'Tanane\FrontendBundle\Entity\NaturalPerson'
));
}
elseif ($this->register_type[0] == "legal")
{
$builder->add('leg', new LegalPersonType(), array(
'data_class' => 'Tanane\FrontendBundle\Entity\LegalPerson'
));
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Tanane\FrontendBundle\Entity\Orders',
'render_fieldset' => FALSE,
'show_legend' => FALSE
));
}
public function getName()
{
return 'orders';
}
}
I've fixed by adding 'mapped' => FALSE on each new FormType I add in OrdersType but I don't know if this is the right. Also, if I'm defining the data_class here, and NaturalType will never be access directly just trough OrdersType should I remove the default options from that form or should I leave them there? How can I fix the problem now? What I'm missing?
This is not a direct answer to your question but maybe could solve some problem before that happens...
I don't remember to have seen it's possible to extend a form like this instead of extend AbstractType, but as explained in the docs, if you have common fields to share between different types of forms you should use the native framework modularity offered by inherit_data.
If you need something more specific (some special methods to execute on some field) you can create a new field type or extend an existing one using AbstractTypeExtension.
EDIT:
I don't know exactly why you are using this approach (that I never used in my projects) but IMO PersonType, NaturalPersonType and LegalPersonType should be only "FormType/FieldType" initialized with inherit_data (and not entities like in your code) that contains the fields related to their use, while OrdersType should be composed with the block of forms needed to the type of person who fills it and with data_class setted on the UNIQUE entity that store the data outputted by the form.

Symfony form many-to-many without collection

I have 2 entities, User and Account. An Account can be own by multiple users and a User can own multiple Account. So I have this entity UserHasAccount that is handling this relation, plus some few fields that have personalised values for each User - Account relation, such as position, showInMenu, showInSideBar.
In the settings of the account, when a user is logged in I want to display those options but only for the current logged user.
So I end up with 2 form type:
AccountEditType:
class AccountEditType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, array(
'label' => 'account.form.name',
'translation_domain' => 'AcmeAccountBundle',
))
->add('timezone', 'genemu_jqueryselect2_timezone', array(
'label' => 'account.form.timezone',
'translation_domain' => 'AcmeAccountBundle',
))
->add('accountUser', new UserAccountEditType($options['user']))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\AccountBundle\Entity\Account',
'user' => null,
'validation_groups' => array('AcmeUpdateAccount'),
'cascade_validation' => true,
));
}
public function getName()
{
return 'acme_account_edit_settings';
}
}
UserAccountEditType:
class UserAccountEditType extends AbstractType
{
protected $user;
public function __construct($user)
{
$this->user = $user;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('showInMenu', null, array(
'label' => 'account.user_account.form.show_in_menu',
'translation_domain' => 'AcmeUserBundle',
'required' => false,
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\UserBundle\Entity\UserHasAccount',
));
}
public function getName()
{
return 'acme_general_account_user';
}
}
The problem is that Account.accountUser is a collection of user and so I cannot use UserAccountEditType which is mapping only one relation (not the collection). If I leave it like this I have the following error:
The form's view data is expected to be an instance of class Acme\UserBundle\Entity\UserHasAccount, but is an instance of class Doctrine\ORM\PersistentCollection
How can I possibly select one of the relation in the form type?
Cheers,
Maxime

Categories