Symfony2 Choice Form Data from ORM - symfony

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

Related

Symfony 5 custom formType: set `data` option based on the value of 2 others options

I am coding a reusable form type called LdapAppGroupUserType that take 2 options : appDn, userName.
The choices and the data options are calculated based on this 2 option values.
This form type is consummed as follow:
$form->add('groups', LdapAppGroupUserType::class, [
'appDn' => $appDn,
'userName' => $userName,
]);
Since this form must be reusable, I would like to move the code present in the buildForm method to configureOptions method.
This way, the form that will consume this field can be mapped more properly.
Instead of doing $form->getData()->get('groups')['app_user_groups'], I will be able to do: $form->getData()->get('groups')
My question is, how to pass from:
<?php
namespace App\Form;
use App\Service\LdapClient;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class LdapAppGroupUserType extends AbstractType
{
private LdapClient $ldapClient;
public function __construct(LdapClient $ldapClient)
{
$this->ldapClient = $ldapClient;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$appDn = $options['appDn'];
$userName = $options['userName'];
$groups = $this->ldapClient->getGroupsForApp($appDn);
$choices = array_combine($groups, $groups);
$userGroups = $this->ldapClient->getUserGroupsForApp($userName, $appDn);
dump($userName, $appDn, $userGroups, $choices);
$builder
->add('app_user_groups', ChoiceType::class, [
'expanded' => true,
'multiple' => true,
'choices' => $choices,
"data" => $userGroups,
"empty_data" => $userGroups,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'appDn' => null,
'userName' => null,
]);
}
to something like that:
<?php
namespace App\Form;
use App\Service\LdapClient;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class LdapAppGroupUserType extends AbstractType
{
private LdapClient $ldapClient;
public function __construct(LdapClient $ldapClient)
{
$this->ldapClient = $ldapClient;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'appDn' => null,
'userName' => null,
'expanded' => true,
'multiple' => true,
]);
$resolver->setNormalizer('data', function (OptionsResolver $resolver) {
$appDn = $resolver->offsetGet('appDn');
$userName = $resolver->offsetGet('userName');
$userGroups = $this->ldapClient->getUserGroupsForApp($userName, $appDn);
return $userGroups; //this has no effect! checkboxes are empty!
});
$resolver->setNormalizer('choices', function (OptionsResolver $resolver) {
$appDn = $resolver->offsetGet('appDn');
$groups = $this->ldapClient->getGroupsForApp($appDn);
$choices = array_combine($groups, $groups);
return $choices;
});
}
public function getParent()
{
return ChoiceType::class;
}
}
The choices option is well populated but I can't alter the data option this way.
I am running symfony 5.4.
Late answer, but I had a similar problem today and solved it by modifying configureOptions like this, based on the OptionsResolver documentation here:
https://symfony.com/doc/current/components/options_resolver.html#default-values-that-depend-on-another-option
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'appDn' => null,
'userName' => null,
'expanded' => true,
'multiple' => true,
]);
$resolver->setDefault('data', function (Options $options) {
$appDn = $options['appDn'];
$userName = $options['userName'];
$userGroups = $this->ldapClient->getUserGroupsForApp($userName, $appDn);
return $userGroups;
});
$resolver->setDefault('choices', function (Options $options) {
$appDn = $options['appDn'];
$groups = $this->ldapClient->getGroupsForApp($appDn);
$choices = array_combine($groups, $groups);
return $choices;
});
}
And don't forget the use statement:
use Symfony\Component\OptionsResolver\Options;

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) {
// ...
})

Unable to use Callback assert with a form without data_class

I'm creating a custom FormType called IntervalType. My IntervalType will have two fields, start and end and will be of type integer. This custom FormType will always be used without data_class.
I want to add a constraint to guarantee that start is lower than end.
How do I use the Symfony\Component\Validator\Constraints\Callback directly in a FormType without data_class?
Here is my IntervalType, just for reference:
// src/AppBundle/Form/Type/IntervalType.php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Validator\Constraints\NotBlank;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('start', IntegerType::class, array(
'constraints' => array(
new NotBlank(),
),
))
->add('end', IntegerType::class, array(
'constraints' => array(
new NotBlank(),
),
))
);
}
}
When the form won't be using any data_class the only option seems to be the Callback constraint.
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
class IntervalType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('start', IntegerType::class, array(
'constraints' => array(
new NotBlank(),
),
))
->add('end', IntegerType::class, array(
'constraints' => array(
new NotBlank(),
new Callback(array($this, 'validateInterval')),
),
))
->add('submit', SubmitType::class);
}
public function validateInterval($value, ExecutionContextInterface $context)
{
$form = $context->getRoot();
$data = $form->getData();
if ($data['start'] >= $value) {
$context
->buildViolation('The end value has to be higher than the start value')
->addViolation();
}
}
}

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'.

Symfony2 collection form problems

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?

Resources