Symfony2 - adding form for oneToMany strange rendering with a "0" - forms

I have a form which works fine. It persists data to two entities (News and Illustration). In fact, it will be possible, in a near future, that the user can add multiple illustrations to the news. BUT, not now :)...
So i've the backend ready for this and when I run the action which calls the formbuilder which builds the form with two FormTypes:
class NewsType extends AbstractType
{
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add('date', 'date')
->add('titre', 'text')
->add('contenu', 'textarea')
->add('publication', 'checkbox', array('required' => false))
->add('illustrations', 'collection', array(
'type' => new IllustrationType(),
'allow_add' => false,
'required' => false
));
}
public function getName()
{
return 'News';
}
}
class IllustrationType extends AbstractType
{
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add('file', 'file');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Fcbg\NewsBundle\Entity\Illustration',
'cascade_validation' => true,
));
}
public function getName()
{
return 'Illustration';
}
}
The form output renders something like that:
Some idea why I have the "illustrations: 0" ? XD
Thx a lot

You see this because you didn't say how many Illustration you want to display (1? 2? 0? 100?). In doubt, Symfony chose to display none.
Either you allow the user to create one or several new Illustration by setting 'allow_add' => true, (what you don't want to do yet, I understood), or you manually create one (or even several) Illustration in your NewsController.php:
public function addAction()
{
$news = new News();
$Illustration = new Illustration();
$news->addIllustration($illustration);
// create your form and display it
}

Related

How does symfony know what is the type of this object got from form?

I'm using symfony 2.8 with doctrine (and still learning as you will see).
I have this controller in which I create some form and render it.
I don't understand how symfony could possibly know what is the type of the $contract object below.
However those $contract objects end up in my database in the contract table so there must be something (magic?) I don't understand going on.
class MyContractsController extends BaseController
{
public function addAction(Request $request)
{
$form = $this->createForm(MultiNewType::class)->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
foreach ($form->getData()['contracts'] as $contract) {
$em->persist($contract);
}
$em->flush();
return $this->validResponse();
}
return $this->renderModalFormResponse($form, array(
'formTemplate' => 'MyBundle:Form:Contract/multiNew.html.twig'
));
}
Below the MultiNewType file in which I don't see anything related to contract.
class MultiNewType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('contracts', 'collection', array(
'type' => new NewInlineType(),
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'label' => false
));
}
public function getName()
{
return 'contractMultiNew';
}
}
And the NewInlineType in which I see all the fields of the contract table, but can't see anywhere the contract :
class NewInlineType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('foo', 'bar', array(
'label' => false,
'class' => 'MyBundle:MyClass',
))
->add('format', FormatType::class, array(
'label' => false,
))
->add('version', VersionType::class, array(
'label' => false,
))
->add('debut', 'datePicker', array(
'label' => false,
'placeholder' => 'Début'
))
->add('fin', 'datePicker', array(
'label' => false,
'placeholder' => 'Fin'
));
}
public function getName()
{
return 'contractNew';
}
public function getParent()
{
return 'contractEdit';
}
}
Thanks
Your NewInlineType has a parent form as indicated by the getParent() method.
The parent form in this case will return 'contractEdit' from it's getName() method.
In the parent (or possibly grand-parent) form you should find something like the following:
use AppBundle\Entity\Contract;
use Symfony\Component\OptionsResolver\OptionsResolver;
// ...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Contract::class,
));
}
That pretty much does what it says; setting the default data_class option to your Contract class name.

Symfony 5 How to create a form using paramconverter as value for one of the field

I made a form that works with two tables joined (OneToMany, a strategy can have many strenghts), but I could only make it work with 'choice_label' for the 'strategy' field, as shown below.
Instead of the 'choice_label' which gives the user a list of strategies to choose from by titles, I want my code to retrieve the value of the paramconverter {title} used in my controller functions to identify which strategy is wanted, so that when the form is filled and sent, it automatically sets the strenght to the right strategy.
I looked into the many FormType Field of the documentation, and tried option like 'data' instead of 'choice_label' but couldn't make it work. Thank you for your help !
My FormType file :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('strenght')
->add('strategy', EntityType::class,[
'class' => Strategy::class,
'choice_label' => 'title'
])
;
}
My Controller file :
public function createUpdate(DiagnosticForce $diagnosticforce = null, Request $request, EntityManagerInterface $em)
{
if(!$diagnosticforce){
$diagnosticforce = new DiagnosticForce();
}
$form = $this->createForm(DiagnosticForceType::class, $diagnosticforce);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em->persist($diagnosticforce);
$em->flush();
return $this->redirectToRoute("frContentStrategy");
}
return $this->render('content_strategy/createForce.html.twig', [
"diagnosticforce" => $diagnosticforce,
"form" => $form->createView()
]);
}
The answer is : use the array $options inside your FormType to get the data you need, by sending it from your controller as the third argument of your createForm function, here in my controller : ['title' => $id] ; and then in your FormType you will be able to get that data just before $builder, and use it in your ->add (in this example, as the value for 'choices')
Here are my files and the solution :
FormType file :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->title = $options['title'];
$builder
->add('strenght')
->add('strategy', EntityType::class,[
'class' => Strategy::class,
'choices' => [$this->title]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => DiagnosticForce::class,
'title' => null
]);
}
Controller File :
public function createUpdate(Strategy $id, DiagnosticForce $diagnosticforce = null, Request $request, EntityManagerInterface $em)
{
if(!$diagnosticforce){
$diagnosticforce = new DiagnosticForce();
}
$form = $this->createForm(DiagnosticForceType::class, $diagnosticforce, [
'title' => $id
]);

Passing Entity object value in to nested CollectionTypes in a Symfony4 form

Overview: Foreach VariantGroup in a Products VariantSets I need to create a dropdown containg that VariantGroups Variants in the Symfony form
Details:
I have a Product entity which is associated as a ManyToMany relationship to multiple VariantSet objects. The VariantSetobjects contain multiple VariantGroup objects which need to generate a dropdown each to display their Variant choices.
For the nested CollectionTypes I need to only make accessible the options related to the parent CollectionType.
So the only Variants available should be related to the VariantGroups which are only the ones related to the available VariantSets which are associated to the initially parsed Product.
There is some info out there that points toward using the query builder to grab the relevant items only but I'm wondering if this is best practice. Also - how do I pass the previous forms (so getting the top level Product in the nested CollectionType for VariantGroup as VariantSets sits between these two).
Is this even possible to achieve using best practices in Symfony forms?
Example of the desired output here
Yep. The answer was nested CollectionType forms and a custom query builder in the final form (for some reason it was calling all of the Variant objects in the DB, rather than using the ones associated to the parsed VariantGroup object:
Main product form
class ComplexProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Render the variant sets based on the parsed product
$builder
->add('variantSets', CollectionType::class, [
'entry_type' => VariantSetComplexProductType::class,
'label'=> false,
'by_reference' => true,
] );
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
"data_class" => Product::class,
));
}
}
Render the products Variant Sets: (to get the correct VariantGroups associated to the main products VarianSet objects)
class VariantSetComplexProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Render the groups based on the Variant sets
$builder
->add( 'label' )
->add( 'variantGroups', CollectionType::class, [
'entry_type' => VariantGroupComplexProductType::class,
'label'=> false,
'by_reference' => true
] )
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults( [
'data_class' => VariantSet::class,
] )
;
}
}
Render the Variant Sets VariantGroups with its Variants in a dropdown:
The drop down of Variant objects needs to be done in a FromEvent using the query_builder options, as otherwise I had an issue where all the Variant objects were being called in the DB.
There need to be a check to make sure only the correct Variant objects were called based on the parsed VariantGroup.
class VariantGroupComplexProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Render the drop downs based on the Parsed variant group
$builder
->add( 'label' )
// Rendering of the drop down must be done after the previous form data is available
->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'preSetData']);
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults( [
'data_class' => VariantGroup::class,
] );
}
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
/** #var VariantGroup $child */
$child = $event->getData();
$form
->add('variants', EntityType::class, array(
'class' => Variant::class,
'by_reference' => true,
'query_builder' => function (EntityRepository $er) use ( $child ) {
return $er->createQueryBuilder('u')
->where('u.variant_group = :variant_group')
->setParameter('variant_group', $child )
->orderBy('u.label', 'DESC');
},
'choice_label' => 'label',
));
}
}

Empty collection field type in a form

I've defined a Form that only have one 'collection' type field:
<?php
namespace GMC\AccesoSistemaBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use GMC\AccesoSistemaBundle\Form\FuncionType;
class FuncionesType Extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('funciones', 'collection', array(
'type' => new FuncionType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => null
));
}
public function getName() {
return 'gmc_accesosistemabundle_funcionestype';
}
Then I create a form in my controller:
public function mostrarFuncionesAction() {
$em = $this->getDoctrine()->getManager();
$funciones = $em->getRepository('AccesoSistemaBundle:Funcion')->findAll();
$formulario = $this->createForm(new FuncionesType(), $funciones);
return $this->render(
'AccesoSistemaBundle:Default:funciones.html.twig',
array('formulario' => $formulario->createView())
);
}
But even when $funciones has two records, the 'funciones' collection of the form is empty, why? Am I missing anything?
As you can see I'm a complete newbbie with Symfony2 so please be patient with me.
Thanks in advance for your help!
What Symfony is doing with your current code is :
take the $functiones object (instance of Function class)
look for an attribute called functiones in Function class
hydrate your form with data found in this attribute
If you want to use an entity mappin, you have to name your form field (with $builder->add('<name_here>')) as an attribute of your Function class, which is the collection of Function.
Else, you can try to hydrate your form with an array, giving :
$formulario = $this->createForm(new FuncionesType(), array('functiones' => $funciones));

symfony2 using validation groups in form

I have a Entity with a property:
/**
* #var string $name
*
* #Assert\NotBlank(groups={"foobar"})
* #ORM\Column(name="name", type="string", length=225, nullable=false)
*/
private $name;
The Form:
class MyType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('name');
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => '...',
'validation_group' => array('foobar'),
);
}
public function getName()
{
...
}
}
In the Controller I bind the Request and call $form->isValid()
But how to define the validation_group?
From inside your FormType class you can define the validation groups associated to that type by setting your default options:
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\MyBundle\Entity\MyEntity',
'validation_groups' => array('group1', 'group2'),
);
}
I had exactly the same problem. I solved it that way ...
// Entity
$employee = new Employee();
// Form creation
$form = $this->createForm(new EmployeeForm(), $employee, array('validation_groups'=>'registration'));
I hope that helps!
When building the form in the controller, add a 'validation_groups' item to the options array:
$form = $this->createFormBuilder($users, array(
'validation_groups' => array('foobar'),
))->add(...)
;
It is described in the forms page of the symfony2 book: http://symfony.com/doc/current/book/forms.html#validation-groups
For me, on symfony 2.1, i solved it by adding 'Default' in validation_groups like:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\MyBundle\Entity\MyEntity',
'validation_groups' => array('Default', 'registration')
));
}
I made a little blog post related to this problem: http://marcjuch.li/blog/2013/04/21/how-to-use-validation-groups-in-symfony/
In this post I’m going to show how to use validation groups in symfony with the example of an order form which should offer the possibility to use separate billing and shipping adresses. This includes 3 steps:
Group validation contstraints for the shipping related form fields together
Determine which validation contraints are applied, depending on the
checkbox value in the submitted form
Copy data from non-shipping fields to shipping fields if checkbox is
not selected
You can also define validation groups dynamically:
// MyCustomType.php
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
if (Client::TYPE_PERSON == $data->getType()) {
return array('person');
}
return array('company');
},
));
}