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.
Related
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',
]);
}
}
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.
I need to embed one form into another form and I'm doing as follow:
use Symfony\Component\Form\AbstractType,
Symfony\Component\Form\FormBuilderInterface,
Symfony\Component\OptionsResolver\OptionsResolverInterface,
Common\CommonBundle\Form\AddressExtraInfoType;
class StandardAddressType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('country', 'entity', array( ... ))
->add('state', 'entity', array( ... ))
->add('city', 'entity', array( ... ))
->add('extra_info', new AddressExtraInfoType());
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Common\CommonBundle\Entity\StandardAddress'
));
}
public function getName() {
return 'common_commonbundle_standard_address';
}
}
Since the main form need to be attached to the 'data_class' => 'Common\CommonBundle\Entity\StandardAddress' then when I try to get the form this error come up:
Neither the property "extra_info" nor one of the methods "getExtraInfo()", "isExtraInfo()", "hasExtraInfo()", "__get()" exist and have public access in class "Common\CommonBundle\Entity\StandardAddress"
How I can fix this? How I can embed the second form into the first one without get this eror?
try this:
$builder->add('extra_info', new AddressExtraInfoType(), array('mapped' => false));
You don't have field extra_info in class Common\CommonBundle\Entity\StandardAddress so you must use non mapped field in form type
Finally and after read my code once again I found where the error was. In my AddressExtraInfoentity I had address_extra_info and of course changing that to extra_info fix the issue, anyway thanks for your reply
I've to pass a custom option to a symfony form. I followed the documentation setp by step but my options will not pass.
The FormType
class AdvertType extends AbstractType
{
/**
* {#inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$entityManager = $options['em'];
}
/**
* {#inheritDoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'em',
));
$resolver->setAllowedTypes(array(
'em' => 'Doctrine\Common\Persistence\ObjectManager',
));
}
/**
* {#inheritDoc}
*/
public function getName()
{
return 'advert';
}
}
Here is the Code from the controller.
$form = $this->createForm(new AdvertType(), new Advert(), array(
'em' => $this->getDoctrine()->getManager(),
));
Symfony will throw an exception that my option em is missing.
Exception: The required option "em" is missing.
I followed the tutorial to add a data transformers: http://symfony.com/doc/current/cookbook/form/data_transformers.html
I cleared my cache and restarted my web server but nothing will work. What did I wrong? Did I missed a configuration to pass my $options? It looks like the $options array from the controller I passed will never reach the buildForm method.
I'm using Symfony v2.3.5. For testing I updated to the latest (2.3.6) but the problem still exists.
Cheers.
I've copied your code and used it successfully (Symfony 2.3.6). It worked! You shouldn't have to clear your cache. So I'm not sure what's wrong. You should also consider adding the data_class option in your resolver if you want to constrain the form to your Advert object, e.g.
$resolver
->setDefaults(array(
'data_class' => 'Your\Bundle\Entity\Advert',
))
;
I used my form twice (in different ways) so the error results from the wrong usage. I looked on the wrong place for the error. The code in general is correct. Sorry for that.
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();