Symfony2 collection form problems - symfony

Im using symfony 2.3.2, and I'm trying to add subelements to collection by this way on my form type
<?php
namespace candgo\EventoBundle\Form\Ajax;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use candgo\EventoBundle\Entity\Entrada;
use candgo\EventoBundle\Form\EntradaOrderType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class EntradaAjaxType extends AbstractType
{
protected $em,$id_evento;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('abc','collection');
$builder->get('abc')->add('poc');
}
public function __construct($em,$id_evento)
{
$this->em=$em;
$this->id_evento=$id_evento;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'show_legend' => false,
));
}
public function getName()
{
return 'purchase_tickets2';
}
}
I also tryied the code example provided by symfony doc
$builder->add('favorite_cities', 'collection', array(
'type' => 'choice',
'options' => array(
'choices' => array(
'nashville' => 'Nashville',
'paris' => 'Paris',
'berlin' => 'Berlin',
'london' => 'London',
),
),
));
At both chases the rendered form is empty, Anybody knows will be the problem?

Related

Insertion problem with Collection Type in EasyAdmin

I got an issue with my CollectionField.
I explain my problem. I want to create a job, for which we can add one or more tasks. Each task can have several activities etc. When I insert 2+ tasks,
everythings works.
But I have 2 problems:
When I create a task T1 with an activity A1 AND a task T2 with an activity A2 and I save, I end up with task T1 and activity A2 and task T2 without activity.
When I create a task T1 with two activities A1 and A2, I have an SQL problem :
An exception occurred while executing a query: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
Here is my code inside JobCrudController :
<?php
namespace App\Controller\Admin;
use App\Entity\Job;
use App\Form\TaskType;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\CollectionField;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class JobCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Job::class;
}
public function configureFields(string $pageName): iterable
{
return [
AssociationField::new('sector', 'Secteur'),
TextField::new('name', 'Nom du métier'),
CollectionField::new('tasks', 'Tâche(s)')
->setEntryType(TaskType::class),
];
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setDefaultSort([
'sector.name' => 'ASC',
'name' => 'ASC',
])
->setEntityLabelInSingular('Métier')
->setEntityLabelInPlural('Métiers');
}
}
Inside TaskType
<?php
namespace App\Form;
use App\Entity\Task;
use App\Form\ActivityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, [
'label' => 'Nom de la tâche',
])
->add('activities', CollectionType::class, [
'label' => 'Activité(s)',
'entry_type' => ActivityType::class,
'allow_add' => true,
'allow_delete' => true,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Task::class,
]);
}
}
And ActivityType :
<?php
namespace App\Form;
use App\Entity\Activity;
use App\Form\UnderActivityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class ActivityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, [
'label' => 'Nom de l\'activité',
])
->add('underActivities', CollectionType::class, [
'label' => 'Sous-Activité(s)',
'entry_type' => UnderActivityType::class,
'allow_add' => true,
'allow_delete' => true,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Activity::class,
]);
}
}
I don't know how to solve this, do you have any solutions?
Noé

Symfony 5 why can't I use FormsEvents::POST_SUBMIT in my form event listener?

I'm trying to add an EventListener to my Symfony form but I have a problem with the first parameter $listener of $builder->addEventListener. I want to use FormEvents::POST_SUBMIT to generate a new field after the submit. Basically I want to display list of cities based on the postal code.
The error tells me that the object is of the wrong type but I don't see which object I could use instead because the documentation tells me to do so. I'm working on Symfony 5.2
Here is my form code and the error :
<?php
namespace App\Form;
use App\Entity\Advert;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Event\PostSubmitEvent;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CreateAdvertType extends AbstractType
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$repositoryCities=$this->entityManager->getRepository('App\Entity\Cities');
$cities = $repositoryCities->findByPostal("86330");
$repositoryMake=$this->entityManager->getRepository('App\Entity\Make');
$makes = $repositoryMake->findAll();
$builder
->add('car_make',EntityType::class, array(
'class' => 'App\Entity\Make',
'choices' => $makes,
))
->add('car_model')
->add('car_motorisation')
->add('car_fuel',ChoiceType::class, array(
'choices' => [
'Diesel' => 'diesel',
'Essence' => 'essence',
'Electrique' => 'electrique',
'Hybride' => 'hybride',
],
))
->add('price', IntegerType::class, array(
'attr' => array(
'min' => 0,
'max' => 20,
)
))
->add('code_postal')
->add('description')
->add('save', SubmitType::class, ['label' => 'Create Task'])
->addEventListener(FormEvents::POST_SUBMIT,function (FormEvents $event){
$repository=$this->getDoctrine()->getManager()->getRepository('App\Entity\Cities');
$form = $event->getForm();
$cities = $repository->findByPostal($form->getData()['code_postal']);
$form->add('city' ,EntityType::class, array(
'class' => 'App\Entity\Cities',
'choices' => $cities
));
})
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Advert::class,
]);
}
}
Argument 1 passed to App\Form\CreateAdvertType::App\Form\{closure}() must be an instance of Symfony\Component\Form\FormEvents, instance of Symfony\Component\Form\Event\PreSetDataEvent given, called in /var/www/html/trymycar/vendor/symfony/event-dispatcher/EventDispatcher.php on line 230
Should be FormEvent (singular not plural).
// plural HERE ---v singular HERE ---v
->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
// ...
})

Symfony2 Choice Form Data from ORM

Have this form:
class ScanType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('scantarget', 'entity', array(
'class' => 'AppBundle:Website',
'property' => 'url'
));
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'AppBundle\Entity\Website',
);
}
}
Need to populate it with only urls for the particular userid
In the controller:
/**
* #Route("/verification-ok", name="verifyurlok")
*/
public function verifyurlActionOK(Request $request)
{
$user = $this->getUser();
if($user)
{
$userid=$user->getId();
$websites = $this->getDoctrine()->getRepository('AppBundle:Website')->findByUser($userid);
$form = $this->createForm(ScanType::class, $websites);
However, the $websites is not passed properly to FormBuilder and in my Select box I see all entries :( All possible values for Website entity.
How to Display only passed $websites in the form (select box)? So only websites for a specific userid?
Thanks,
Update 1
Form
<?php
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\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class ScanType extends AbstractType
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$user = $this->tokenStorage->getToken()->getUser();
if (!$user) {
throw new \LogicException(
'The FriendMessageFormType cannot be used without an authenticated user!'
);
}
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($user) {
$form = $event->getForm();
$formOptions = array(
'class' => 'AppBundle\Entity\Website',
'property' => 'user',
'query_builder' => function (EntityRepository $er) use ($user) {
// build a custom query
return $er->createQueryBuilder('u')->addOrderBy('user', 'DESC');
// or call a method on your repository that returns the query builder
// the $er is an instance of your UserRepository
// return $er->createOrderByFullNameQueryBuilder();
},
);
// create the field, this is similar the $builder->add()
// field name, field type, data, options
$form->add('url', ScanType::class, $formOptions);
}
);
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'AppBundle\Entity\Website',
);
}
}
Controller:
$form = $this->createForm(ScanType::class);
config.yml
services:
app.form.scan:
class: AppBundle\Form\ScanType
arguments: ['#security.token_storage']
tags:
- { name: form.type }
Unfortunately, get this error:
request.CRITICAL: Uncaught PHP Exception Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException: "The options "class", "property", "query_builder" do not exist. Defined options are: "action", "allow_extra_fields", "attr", "auto_initialize", "block_name", "by_reference",
Solution:
Thanks #Medard
Form:
<?php
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\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ScanType extends AbstractType
{
private $websites;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->websites = $options['trait_choices'];
$builder->add('scantarget', 'entity', array(
'class' => 'AppBundle:Website',
'property' => 'url',
'choices' => $this->websites
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
'trait_choices' => null,
));
}
}
Controller:
$form = $this->createForm(ScanType::class, $websites, array(
'trait_choices' => $websites
));
Works!!!
Well, you are not setting the choices. Try this:
class ScanType extends AbstractType
{
private $websites;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->websites = $options['trait_choices'];
$builder->add('scantarget', 'entity', array(
'class' => 'AppBundle:Website',
'property' => 'url',
'choices' => $this->websites
));
}
public function setDefaultOptions(array $options) {
return array(
'data_class' => 'AppBundle\Entity\Website',
'trait_choices' => null,
);
}
}
In the controller pass through the websites:
$form = $this->createForm(ScanType::class, $websites, array(
'trait_choices' => $websites,
));
Your form displays all the Website entries because you're not restricting them when building the form, in the add() method.
You should read this section fo the cookbook : http://symfony.com/doc/current/form/dynamic_form_modification.html#form-events-user-data

Modifying Symfony Forms html attributes / overriding a single attribute

I am trying to figure out how to modify html attributes on the fly with Symfony2 forms.
The situation is a case where a default placeholder is used most of the time, but occasionally, the developer needs to write a custom message.
My Form type looks like this:
<?php
namespace My\AwesomeBundle\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use My\AwesomeBundle\Transformer\ProcedureCodeTransformer;
class ProcedureType extends AbstractType
{
private $em;
private $user;
public function __construct($em, $securityContext)
{
$this->em=$em;
$this->user=$securityContext->getToken()->getUser();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new ProcedureTransformer( $this->em );
$builder->addModelTransformer( $transformer );
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$user = $this->user;
$resolver->setDefaults(
array(
'class' => 'My\AwesomeBundle\Entity\Procedure',
'label' => 'Procedure',
'label_attr' => array( 'class'=> 'control-label' ),
'required' => false,
'empty_value' => '',
'attr' => array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
),
)
);
$resolver->setOptional( array( 'placeholder' ) );
}
public function getParent() {
return 'hidden';
}
public function getName() {
return 'procedure';
}
}
The default render then has "Select Procedure" for the data-select2-placeholder element and javascript is used to display it. This is then used in a more complex type:
<?php
namespace My\AwesomeBundle\FormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ProcedureType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('biller', 'billers', array( 'placeholder' => 'New Text'));
[...]
}
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults( array(
'data_class' => 'My\AwesomeBundle\Entity\Procedure'
) );
}
public function getName(){
return 'my_awesomebundle_proceduretype';
}
}
I would like 'New Text' to be placed into the data-select2-placeholder html attribute. However, if I call the builder like this:
$builder->add('procedure',
new ProcedureCodeType()),
array( 'attr' => array('data-select2-placeholder' => 'New Text') )
);
The entire html attribute array is replaced. this is not surprising. Is there a function in the form builder that has eluded me to add or modify a single html attribute?
Unfortunately, there is no way to modify a single attr the way you would want it. This is the closest and most simple solution I could come with:
What you have to do in your situation is to use callback functions in your options.
For each default attribute in your form types, instead of a normal value, you can use a callback function that accepts a single input representing your options (Symfony class Symfony\Component\OptionsResolver\Options). The function is called when building the form and the return value is then used as the value of the option.
That way, you can build the resulting option depending on the other provided options. An example:
use Symfony\Component\OptionsResolver\Options;
class ProcedureType extends AbstractType
{
...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$attr = function(Options $options) {
$default = array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
);
return array_replace($default, $options['new_attr']);
};
$user = $this->user;
$resolver->setDefaults(
array(
'class' => 'My\AwesomeBundle\Entity\Procedure',
'label' => 'Procedure',
'label_attr' => array( 'class'=> 'control-label' ),
'required' => false,
'empty_value' => '',
'attr' => $attr,
'new_attr' => array(),
)
);
$resolver->setOptional( array( 'placeholder' ) );
}
That way, what you have to modify will be new_attr.
You might want to create a new class that resolves the nested options for you.
<?php
namespace My\AwesomeBundle\Form\Attr;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Options;
class ProcedureCodeAttr
{
/**
* #var array
*/
protected $options;
/**
* #param array $options
*/
public function __construct(array $options = array())
{
$resolver = new OptionsResolver();
$this->setDefaultOptions($resolver);
$this->options = $resolver->resolve($options);
}
/**
* #param OptionsResolverInterface $resolver
*/
protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'class' => 's2',
'data-select2-placeholder' => 'Select Procedure',
));
}
/**
* #return array
*/
public function toArray()
{
return $this->options;
}
/**
* #param array $options
* #return array
*/
public static function resolve(array $options = array())
{
$attr = new static($options);
return $attr->toArray();
}
}
Then in your form type class you'd use it like so
$builder->add('procedure', new ProcedureCodeType()), array(
'attr' => ProcedureCodeAttr::resolve(array(
'data-select2-placeholder' => 'New Text'
))
));
Symfony 4.4 and above now support nested options https://symfony.com/doc/4.4/components/options_resolver.html#nested-options
Instead of defining a nested array, create a callback with OptionsResolver.
For example, a custom form type exists with the following defaults:
class CustomType extends AbstractType {
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('attr', function (OptionsResolver $attrResolver) {
$attrResolver->setDefaults([
'class' => 'form-control-lg',
'placeholder' => 'My Default Placeholder',
'autocomplete' => 'off',
]);
});
$resolver->setDefaults([
'label' => 'Name',
'help' => 'Enter a name',
]);
}
....
}
Attr is now a callback, not an array, now in any forms that use it just defined the attr properties like normal.
class MyFormType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', CustomType::class, [
'label' => 'My Form',
'help' => 'My Form Help',
'attr' => [
'placeholder' => 'My Form Placeholder',
],
])
....
}
The generated form will have the class: 'form-control-lg' and the label: 'My Form Placeholder'.

How to dynamically add's collections within collections in Symfony2 form types

I have 3 form types in symfony2
FaultType which is the parent of all next collections
<?php
namespace My\FaultBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class FaultType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('title')
->add('steps', 'collection', array(
'type' => new StepType(),
'allow_add' => true,
'prototype' => true,
'by_reference' => false,
))
->add('created')
->add('updated')
;
}
public function getDefaultOptions()
{
return array(
'data_class' => 'My\FaultBundle\Entity\Fault'
);
}
public function getName()
{
return 'my_fault_fault';
}
}
StepType:
<?php
namespace My\FaultBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class StepType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('body')
->add('photos', 'collection', array(
'type' => new PhotoType(),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'by_reference' => false,
))
;
}
public function getDefaultOptions()
{
return array(
'data_class' => 'My\FaultBundle\Entity\Step'
);
}
public function getName()
{
return 'my_fault_step';
}
}
and the last PhotoType:
<?php
namespace My\FaultBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class PhotoType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('name')
->add('description')
->add('filename')
;
}
public function getDefaultOptions()
{
return array(
'data_class' => 'My\FaultBundle\Entity\Photo'
);
}
public function getName()
{
return 'my_fault_photo';
}
}
I found excelent article link about prototype, and with one nested form type is very good, but I have a problem when a want to get this to work with third nest mean PhotoType... Photos are in collection of Steps, which is collection of Fault..., how can I achive dynamically add/remove photos for steps with this example...?
I made a JS snippet that can be of help here. you just have to add two buttons [add new, delete last].
https://gist.github.com/juanmf/10483041
it can handle recursive/nested prototypes.
It's coupled with a mediator (same as Symfony event Dispatcher) that allows you to bind generated controls to events. If you dont need the mediator delete these lines:
docdigital.mediatorInstance.send(
docdigital.constants.mediator.messages.clonePrototype_prototypeAdded,
$clone
);
You have to make you own prototype.
There are 2 solutions:
Find with regex all digit segments of a property_path, and replace them with placeholder
$segments_found = preg_match('/\[(\d+)\]/', $prototype, $matches);
Use recursion to find top collection parent and build path manually from there.
Did you try reordering items? This is total disaster;)

Resources