Symfony2 -- Creating a form that use's differents entities - php

I've been using Symfony a bit and i'm trying to figure out a way to create a form.
I need to use a MVC based solution.
My form needs to ask several information of different Entities and then i need to process that information extracting it in the database.
The database wont be a problem.
I was just figuring out how do i make a form with different types of entities ?
And how do i make a scrolldown menu with the data contained in the database for an entity ?

If the comment by #chalasr does not apply, i.e., the entities are not related, it is possible to do something like the following in a controller. Simply create a $form variable for each Entity{x}Type form as in:
$formA = $this->createForm(AppBundle\Entity\EntityAType($entityA));
$formB = $this->createForm(AppBundle\Entity\EntityBType($entityB));
...
return array(
'formA' => $formA->createView(),
'formB' => $formB->createView(),
...
);

You can simply combine the Forms while keeping each separate like so
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ReallyBigFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('foo', FooType::class, [
// label, required, ... as well as options of FooType
])
->add('bar', BarType::class, [
// label, required, ... as well as options of BarType
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([]);
}
}
and define FooType and BarType like a regular Form
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType
class FooType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, [
'label' => 'foo.name',
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\Foo',
]);
}
}

Related

manage field population in edit form

I'm using symfony 4 and I have this form:
<?php
namespace App\Form;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use App\Entity\TypeParking;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TypeParkingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('libelle')
->add('tempsmax')
->add('jourdebut')
->add('jourfin')
->add('Exception_Name', TextType::class, ['property_path' => 'exception[name]'])
->add('Starting_date', DateType::class, [
'property_path' => 'exception[datedebut]',
])
->add('Ending_date', DateType::class, [
'property_path' => 'exception[datefin]',
])
->add('Starting_time', TimeType::class, ['property_path' => 'exception[heuredebut]'])
->add('Ending_time', TimeType::class, ['property_path' => 'exception[heurefin]'])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => TypeParking::class,
]);
}
}
When I create a new form I usually take the values of Exception_Name,Starting_date,Ending_date,Starting_time and Ending_time manually and put them into a single json array in a single database field.
However when I go to edit the form, the data inside the json doesn't get divided to populate each of the fields.
let's say that I have this json array: {"Exceptions": {"name": "6fdfs", "StartDate": "2015-03-03", "StartHour": "00:00:00", "EndingDate": "2015-03-03", "EndingHour": "00:00:00"}}
I'm gonna take the name value and use it to populate the Exception_Name field, etc...
TL;DR: How can I control how I want to pre-populate each field of the edit form
Edit:
this is my datamapper
<?php
// src/Form/DataMapper/ColorMapper.php
namespace App\Form\DataMapper;
use App\Painting\Color;
use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\FormInterface;
final class TypeParkingMapper implements DataMapperInterface
{
/**
* #param TypeParking|null $data
*/
public function mapDataToForms($data, $forms)
{
// there is no data yet, so nothing to prepopulate
if (null === $data) {
return;
}
/** #var FormInterface[] $forms */
$forms = iterator_to_array($forms);
// initialize form field values
$Excep=$data->getException();
$forms['Exception_Name']->setData($Excep['Exceptions']['name']);
$forms['Starting_date']->setData($Excep['Exceptions']['StartDate']);
$forms['Ending_date']->setData($Excep['Exceptions']['EndingDate']);
$forms['Starting_time']->setData($Excep['Exceptions']['StartHour']);
$forms['Ending_time']->setData($Excep['Exceptions']['EndingHoure']);
}
public function mapFormsToData($forms, &$data)
{
/** #var FormInterface[] $forms */
$forms = iterator_to_array($forms);
// as data is passed by reference, overriding it will change it in
// the form object as well
// beware of type inconsistency, see caution below
$data = new TypeParking(
$forms['Exception_Name']->getData()
);
}
}
So it works fine when I edit an object (since getException() returns the json array from my database), but when I create a new object it throws an error since there's no data to get.
So is there any way I can turn the mapper off when I create a new form and only activate it when editing a form ?
the error is : "Notice: Undefined index: Exceptions"
You could write a DataMapper to fully control how your data is mapped to your form.

How to POST nested entities with FOSRest and Symfony Form

Using Symfony 3.2 and FOS REST Bundle I have created some REST endpoints for a resource and use a Symfony Form to define the fields for API DOC. Everything is working fine to this point.
Now I'm trying to improve my schema and added a sub entity (one-to-one) to my resource. I want the main resource to save the sub entity - there is no dedicated endpoint for the sub entity.
I followed the instruction on the Symfony documentation and removed all other fields to isolate any issues.
This is how my form type looks now:
<?php
namespace VendorName\MyBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CountryType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('mySubEntity', EntityType::class, array(
'multiple' => false,
'expanded' => false,
'property' => 'name',
'class' => 'MyBundle\Entity\mySubEntity'));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'VendorName\MyBundle\Entity\Country',
'csrf_protection' => false
));
}
}
Now when I load my api-docs, I receive the error message The option "property" does not exist. Defined options are: "action", "allow_extra_fields", [...].
To be honest, I don't even know if adding the Entity to the form is the right approach to make it show up in the API Docs. Any help in resolving the above issue and / or best practices to achieve this would be appreciated.
EDIT: Thanks to #miikes this error is now resolved and I can see the api doc showing up correctly with the fields of the nested form. However, now my issue is that the form does not populate the sub entity on the parent entity. This seems to be related to the way I modelled the parent-child relationship and I have posted a new question for this issue.
To resolve your error try to use choice_label, instead of property option.
'choice_label' => 'name'
But referring to the documentation, EntityType is a kind of ChoiceType, so using this type, you can only select existing entity, not persist new one.
The easiest and most clear way for creating new entity instance is creating another type class, designed for your entity, and adding the type as the field to your CountryType.
class MyEntityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('some-field', TextType::class)
->add('another-field', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => MyEntity::class
]);
}
}
class CountryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('mySubEntity', MyEntityType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Country::class,
'csrf_protection' => false
));
}
}
So you should pass form data as
['mySubEntity']['some-field'] = 'foo'
['mySubEntity']['another-field'] = 'bar'
Another tip is to use Country::class instead of string 'VendorName\MyBundle\Entity\Country', because in case of renaming class, IDE refactoring should affect on your type.

Passing Arguments into Symfony 3 Form configureOptions

I have the following form:
class TestFormType extends AbstractType
{
protected $testArgument;
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (isset($options['testArgument'])) {
$this->testArgument = $options['testArgument'];
}
$builder->add('textField', 'Symfony\Component\Form\Extension\Core\Type\TextType');
}
public function configureOptions(OptionsResolver $optionsResolver)
{
$optionsResolver->setRequired('testArgument');
$optionsResolver->setDefaults(
array(
'data_class' => get_class($this->testArgument)
)
);
}
}
I am passing the value for the testArgument attribute via form options (Symfony 3 modifications), but when is comes to get the class name of the attribute to set the 'data_class' inside configureOptions method, it is always null. Basically I need to depend on the form type class attribute inside the configureOptions method.Can someone please help me out here to the right direction ?
I had to pass the dependency in configureOptions method from the form factory create method itself:
$form = $this->factory->create(
'app\TestBundle\Form\Type\TestFormType',
$this->testArgument,
array(
'data_class' => get_class($this->testArgument)
)
);
as it would not be set by the default settings in the form type and had to refactor the form type class as follows:
class TestFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('textField', 'Symfony\Component\Form\Extension\Core\Type\TextType');
}
}
You should pass the *Type __constructor for use to
use App\Entity\Blog;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class BlogType extends AbstractType {
private $someDependency;
public function __construct($someDependency)
{
$this->someDependency = $someDependency;
}
// ...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'empty_data' => new Blog($this->someDependency),
]);
} }

Symfony 2.8/3.0 - Pass array to another formType from a collectionType

I could get this to work prior to v2.8 but as symfony now uses fully qualified class names name i'm unsure sure how to proceed.
I can pass an array (to populate a choice field) to a form without issue but if there is an another formType added via a collectionType how can a pass the array?
BTW - the array is gathered from data from a custom annotations - NOT an entity
Heres my code:
PageType.php
<?php
namespace Prototype\PageBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
const ActiveComponentsType = 'Prototype\PageBundle\Form\ActiveComponentsType';
const collectionType = 'Symfony\Component\Form\Extension\Core\Type\CollectionType';
class PageType extends AbstractType
{
private $cmsComponentArray;
public function __construct($cmsComponentArray = null)
{
$this->cmsComponentArray = $cmsComponentArray;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$cmsComponentArray = $options['cmsComponentArray'];
$componentChoices = array();
foreach($cmsComponentArray as $cmsComponent){
$componentChoices[$cmsComponent['name']] = $cmsComponent['route'];
}
//correct values are shown here
//print_r($componentChoices);
$builder
->add('title')
->add('parent')
->add('template')
->add('active')
->add('content')
->add('components', collectionType, array(
'entry_type' => ActiveComponentsType, // i want to pass $cmsComponentArray to ActiveComponentsType
'allow_add' => true,
'allow_delete' => true
))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Prototype\PageBundle\Entity\Page',
'cmsComponentArray' => null
));
}
}
The ActiveComponentsType embeded form does work - except I'm unsure how to pass the $componentChoices array to it.
Any ideas?
The collection type defines the entry_options option which is used to configure the options that are passed to the embedded form type.

Embedding a dynamically generated form into another

I use Symfony2.1's FormBuilder to create a dynamic form in the controller:
$form = $this->createFormBuilder($defaultData);
$form->add('field','text');
I would like to embed another form in the same way and embed it with the main form.
$subForm = $this->createFormBuilder();
$subForm->add('subfield','text');
// Does not work
$form->add('subform', $subForm);
Unfortunately this setup does not work properly. I could not find any way how to add a dynamically generated subform into a dynamically generated form, like the example above.
What's the function call I am missing?
The exception thrown by the example code above is
UnexpectedTypeException: Expected argument of type "string or
Symfony\Component\Form\FormTypeInterface",
"Symfony\Component\Form\Form" given.
I would create two Form Clases in two separate fields:
<?php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class FirstFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field', 'text')
->add('subform', new SecondFormType());
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\DemoBundle\Entity\FirstEntity'
));
}
public function getName()
{
return 'first';
}
}
<?php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SecondFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('subfield', 'text');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\DemoBundle\Entity\SecondEntity',
));
}
public function getName()
{
return 'second';
}
}
After this creation you can create your form from any Controller with this line for creation forms:
$form = $this->createForm(new FirstFormType());
or for edition forms:
$form = $this->createForm(new FirstFormType(), $first);
In your example code you are trying to assig to a field an entire form instead of a Form Type.
Hope it helps
Recommended symfony-way is to create separate FormType classes, as mentioned above. But if you really want to..
You can add dynamic subform into form builder, by calling
$formBuilder->add($subform /* FormBuilder */);
This subform will have name "form" if created with $this->createFormBuilder(); , that means that you can not add two or more subforms created this way - newer one will overwrite previous with same name.
If you need to add multiple sub-forms, you have to create their builders with
$this->get('form.factory')->createNamedBuilder($uniqName)
Without making classes... inside a Controller Action:
$subFormBuilder = $this->createFormBuilder(
null /* default data */,
['label' => 'Sub Form'] /* options */
)
->add('name');
$form = $this->createFormBuilder()
->add($subFormBuilder)
->add('number')
->getForm();

Categories