I wish to understand how to display errors that come from forms. For now, when I validate any form on my project, I don't have anything appearing.
How to display form errors in Symfony ?
Thank you for your help.
You can see my code from :
register.html.twig file where my userform helping users to register is shown.
UserController.php file where you can see what happens when userform is validated.
Also User.php and UserType.php.
register.html.twig
{% extends 'base.html.twig' %}
{% block title %}Incris-toi !{% endblock %}
{% block main %}
{{ form_start(userform) }}
<div class="alert alert-danger text-center" role="alert">
{% set formErrors = userform.vars.errors.form.getErrors(true) %}
{% if formErrors|length %}
<div class="alert alert-danger text-center" role="alert">
{% if userform.vars.value.email == null or userform.vars.value.email != 'email' or userform.vars.value.email != 'unique' %}
{{ form_errors(userform.email) }}
{% elseif userform.vars.value.password|length < 6 %}
{{ form_errors(userform.password) }}
{% elseif userform.vars.value.gender == null or (userform.vars.value.gender != 'male' and userform.vars.value.gender != 'female' and userform.vars.value.gender != 'non-binary') %}
{{ form_errors(userform.gender) }}
{% elseif userform.vars.value.firstname|length < 2 %}
{{ form_errors(userform.firstname) }}
{% elseif userform.vars.value.lastname|length < 2 %}
{{ form_errors(userform.lastname) }}
{% elseif userform.vars.value.birthdate == null %}
{{ form_errors(userform.birthdate) }}
{% elseif userform.vars.value.occupation|length < 2 %}
{{ form_errors(userform.occupation) }}
{% elseif userform.vars.value.nationality == null %}
{{ form_errors(userform.nationality) }}
{% elseif userform.vars.value.nativelanguage == null %}
{{ form_errors(userform.nativelanguage) }}
{% endif %}
</div>
{% endif %}
</div>
{{ form_widget(userform.email) }}
{{ form_widget(userform.password) }}
{{ form_widget(userform.gender) }}
{{ form_widget(userform.firstname) }}
{{ form_widget(userform.lastname) }}
{{ form_widget(userform.birthdate) }}
{{ form_widget(userform.occupation) }}
{{ form_widget(userform.nationality) }}
{{ form_widget(userform.nativelanguage) }}
{{ form_widget(userform.save) }}
{{ form_end(userform) }}
UserController.php
<?php
namespace App\Controller\Front;
use App\Entity\User;
use App\Form\UserType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserController extends AbstractController
{
#[Route('/register', name: 'register', methods: ['GET', 'POST'])]
public function createUser(
Request $request,
EntityManagerInterface $entityManagerInterface,
UserPasswordHasherInterface $userPasswordHasherInterface
){
$user = new User();
$userform = $this->createForm(UserType::class, $user);
$userform->handleRequest($request);
if ($userform->isSubmitted() && $userform->isValid()) {
$user->setRoles(["ROLE_USER"]);
$plainPassword = $userform->get('password')->getData();
$hashedPassword = $userPasswordHasherInterface->hashPassword($user, $plainPassword);
$user->setPassword($hashedPassword);
$entityManagerInterface->persist($user);
$entityManagerInterface->flush();
return $this->redirectToRoute('home');
}
return $this->renderForm('front/register.html.twig', [
'userform' => $userform,
]);
}
User.php
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id;
#[ORM\Column(length: 180, unique: true)]
#[Assert\NotBlank(message:'Tu as oublié d\'entrer ton adresse e-mail.')]
#[Assert\Email(message: 'Entre une adresse e-mail valide.')]
private ?string $email = null;
/**
* #var string The hashed password
*/
#[ORM\Column]
#[Assert\NotBlank(message:'Tu as oublié de créer un mot de passe.')]
#[Assert\Length(min: 6, minMessage: 'Crée un mot de passe de 6 caractères minimum.')]
private ?string $password = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank(message:'Tu as oublié de sélectionner ton genre.')]
private ?string $gender = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank(message:'Tu as oublié d\'entrer ton prénom.')]
#[Assert\Length(min: 2, minMessage: 'Écris un prénom valide.')]
private ?string $firstname = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank(message:'Tu as oublié d\'entrer ton nom de famille.')]
#[Assert\Length(min: 2, minMessage: 'Écris un nom de famille valide.')]
private ?string $lastname = null;
#[ORM\Column(type: Types::DATE_MUTABLE)]
#[Assert\NotBlank(message:'Tu as oublié de sélectionner ta date de naissance.')]
private ?\DateTimeInterface $birthdate = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank(message:'Tu as oublié de nous dire ce que tu fais.')]
#[Assert\Length(min: 2, minMessage: 'Écris une occupation valide.')]
private ?string $occupation = null;
#[ORM\ManyToOne(inversedBy: 'users')]
#[Assert\NotBlank(message:'Tu as oublié de nous sélectionner le pays d\'où tu viens.')]
private ?Country $nationality = null;
#[ORM\ManyToOne(inversedBy: 'users')]
#[Assert\NotBlank(message:'Tu as oublié de nous sélectionner ta langue maternelle.')]
private ?Language $nativelanguage = null;
public function __construct()
{
$this->events = new ArrayCollection();
$this->participations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* #see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getGender(): ?string
{
return $this->gender;
}
public function setGender(string $gender): self
{
$this->gender = $gender;
return $this;
}
public function getFirstname(): ?string
{
return $this->firstname;
}
public function setFirstname(string $firstname): self
{
$this->firstname = $firstname;
return $this;
}
public function getLastname(): ?string
{
return $this->lastname;
}
public function setLastname(string $lastname): self
{
$this->lastname = $lastname;
return $this;
}
public function getBirthdate(): ?\DateTimeInterface
{
return $this->birthdate;
}
public function setBirthdate(?\DateTimeInterface $birthdate): self
{
$this->birthdate = $birthdate;
return $this;
}
public function getOccupation(): ?string
{
return $this->occupation;
}
public function setOccupation(string $occupation): self
{
$this->occupation = $occupation;
return $this;
}
public function getNationality(): ?Country
{
return $this->nationality;
}
public function setNationality(?Country $nationality): self
{
$this->nationality = $nationality;
return $this;
}
public function getNativelanguage(): ?Language
{
return $this->nativelanguage;
}
public function setNativelanguage(?Language $nativelanguage): self
{
$this->nativelanguage = $nativelanguage;
return $this;
}
}
UserType.php
<?php
namespace App\Form;
use App\Entity\User;
use App\Entity\Country;
use App\Entity\Language;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('gender', ChoiceType::class, [
'choices' => [
'Je suis ...' => '',
'un homme' => 'male',
'une femme' =>'female',
'non-binaire' => 'non-binary'
]
])
->add('lastname')
->add('firstname')
->add('birthdate', BirthdayType::class, [
'placeholder' => [
'year' => 'Année', 'month' => 'Mois', 'day' => 'Jour',
],
'choice_translation_domain' => true
])
->add('occupation')
->add('nationality', EntityType::class, [
'class' => Country::class,
'choice_label' => 'name',
'placeholder' => 'Je choisis un pays'
])
->add('nativelanguage', EntityType::class, [
'class' => Language::class,
'choice_label' => 'name',
'placeholder' => 'Je sélectionne ma langue maternelle'
])
->add('email')
->add('password', PasswordType::class, [
'mapped' => false
])
->add('password', RepeatedType::class, [
'type' => PasswordType::class,
'invalid_message' => 'Les deux mots de passe doivent être identiques.',
'options' => ['attr' => ['class' => 'password-field']],
'required' => true,
'first_options' => ['label' => 'Password'],
'second_options' => ['label' => 'Repeat Password']
])
->add('save', SubmitType::class, [
'attr' => ['class' => 'save'],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
'translation_domain' => 'forms'
]);
}
}
-- This question is still open. --
EDIT : I have found the way to display error messages. But this problem persists : password doesn't show its error message. I don't understand why.
Try to test "firstname" for example with min length 3 by typing only 2 chars in the form, the form error appears or no ?
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
#[Assert\Length(min: 3)]
private ?string $firstname = null;
/**
* #Assert\NotBlank(
* message="Mot de passe ne doit pas être vide."
* )
* #Assert\Length(
* min="6",
* max="32",
* minMessage="Mot de passe doit avoir au minimum ({{ limit }}) caractères.",
* maxMessage="Mot de passe doit avoir au maximum ({{ limit }}) caractères."
* )
* #Assert\Length(max=4096)
*/
private $plainPassword;
public function getPlainPassword(): ?string
{
return $this->plainPassword;
}
public function setPlainPassword(?string $password): self
{
$this->plainPassword = $password;
return $this;
}
FormType:
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'first_options' => array(
'label' => 'Mot de passe (6 caractères au minimum)',
'constraints' => [
new NotBlank([
'message' => 'Mot de passe ne doit pas être vide',
]),
new Length([
'min' => 6,
'minMessage' => 'Mot de passe doit avoir au minimum {{ limit }} caractères',
'max' => 4096,
]),
],
),
'second_options' => array('label' => 'Confirmation'),
'invalid_message' => 'Les deux mots de passe ne sont pas identiques'
))
The controller:
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
private UserPasswordHasherInterface $passwordHasher;
public function __construct(UserPasswordHasherInterface $passwordHasher)
{
$this->passwordHasher = $passwordHasher;
}
if ($form->isSubmitted() && $form->isValid()) {
$user->addRole('ROLE_AAAAAA');
$user->setPassword(
$this->passwordHasher->hashPassword($user, $form->get('plainPassword')->getData()));
// ...........
}
Twig form:
{% if not userform.vars.valid %}
<div class="alert alert-danger">
{{ form_errors(userform) }}
{% for children in userform.children %}
{% if not children.vars.valid %}
{{ form_errors(children) }}
{% endif %}
{% for child in children %}
{% if not child.vars.valid %}
{{ form_errors(child) }}
{% endif %}
{% endfor %}
{% endfor %}
</div>
{% endif %}
<div class="col-md-6">
{{ form_row(form.plainPassword.first) }}
</div>
<div class="col-md-6">
{{ form_row(form.plainPassword.second) }}
</div>
_ I think there is a problem with your User class, you didn't set any validations for your form : https://symfony.com/doc/current/forms.html#validating-forms
Validation is done by adding a set of rules, called (validation) constraints, to a class. You can add them either to the entity class or to the form class.
Try to add some rules (#[Assert\NotBlank]) to your class and your form will throw errors if it's not fit.
_ Also, you need te remove your default value on your fields in your User class ( = null) to the variable you need to be filled (at least you need to remove the ID one, who can't be null).
_ In your form on each fields, you set 'error_bubbling' to true : https://symfony.com/doc/current/reference/forms/types/text.html#error-bubbling
If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.
If you want to have the control on every field, i think you need to remove this option, then symfony will attach errors on each fields.
Or you can let this option but then you need to update your code in the Alert div, to not render each fields, but the parent field or form.
Related
I tried to display a simply form that I built in SearchType.php. In my controller, the form is supposed to be displayed with the route /search. But we we go there, I have just one input while I defined two input in SearchType.php. Note : this input is not even a selection, I only can enter text.
search.html.twig
{% extends 'base.html.twig' %}
{% block title %}Liste des sorties et des activités{% endblock %}
{% block main %}
{{ form(form) }}
{% endblock %}
SearchType.php
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class SearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('bigcity', ChoiceType::class, [
'choices' => [
'Je sélectionne une ville' => '',
'Paris, France' => 'paris'
]
])
->add('category', ChoiceType::class, [
'choices' => [
"Je sélectionne un type d'activité" => '',
'Eat Zpeak !' => 'eatzpeak',
'Party Zpeak !' => 'partyzpeak',
'Run Zpeak !' => 'runzpeak',
'Art Zpeak !' => 'artzpeak',
]
])
->add('save', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => null
]);
}
}
EventsController.php
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class EventsController extends AbstractController
{
#[Route('/search', name: 'search')]
public function search(Request $request)
{
$form = $this->createForm(SearchType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
return $this->render('front/events.html.twig', $data);
}
return $this->render('front/search.html.twig', [
'form' => $form->createView()
]);
}
}
You are usinig a bad class import in your controller
use Symfony\Component\Form\Extension\Core\Type\SearchType;
You should use the namespace of your formType
App\Form\SearchType
I tried to display a simply form that I built in SearchType.php. In my controller, the form is supposed to be displayed with the route /search. But we we go there, I have just one input while I defined two input in SearchType.php. Note : this input is not even a selection, I only can enter text.
search.html.twig
{% extends 'base.html.twig' %}
{% block title %}Liste des sorties et des activités{% endblock %}
{% block main %}
{{ form(form) }}
{% endblock %}
SearchType.php
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class SearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('bigcity', ChoiceType::class, [
'choices' => [
'Je sélectionne une ville' => '',
'Paris, France' => 'paris'
]
])
->add('category', ChoiceType::class, [
'choices' => [
"Je sélectionne un type d'activité" => '',
'Eat Zpeak !' => 'eatzpeak',
'Party Zpeak !' => 'partyzpeak',
'Run Zpeak !' => 'runzpeak',
'Art Zpeak !' => 'artzpeak',
]
])
->add('save', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => null
]);
}
}
EventsController.php
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\Extension\Core\Type\SearchType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class EventsController extends AbstractController
{
#[Route('/search', name: 'search')]
public function search(Request $request)
{
$form = $this->createForm(SearchType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
return $this->render('front/events.html.twig', $data);
}
return $this->render('front/search.html.twig', [
'form' => $form->createView()
]);
}
}
You are usinig a bad class import in your controller
use Symfony\Component\Form\Extension\Core\Type\SearchType;
You should use the namespace of your formType
App\Form\SearchType
I try to make a search bar but my form dont return value.
I am starting, I have already made symfony forms but there I really do not see :(
I use Symfony 4.12.10.
I always have the result in symfony profiler:
App \ Entity \ PropertySearch {# 707 ▼
-searchbar: null
}
html :
<form class="form-inline mr-auto">
{{ form_start(searchForm) }}
{{ form_errors(searchForm) }}
{{ form_row(searchForm.searchbar) }}
<button type="submit" class="btn btn-secondary my-4 ml-1 my-sm-0">🔍</button>
{{ form_end((searchForm)) }}
</form>
repository :
class TextesRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Textes::class);
}
/**
* #return Query
*/
public function findAllVisible(PropertySearch $search)
{
$query = $this->createQueryBuilder('p');
if ($search->getSearchbar()) { //Si l'utilisateur recherche
$query = $query->andwhere('p.title = :searchbar');
$query->setParameter('searchbar', $search->getSearchbar());
}
return $query->getQuery()->getResult();
}
form:
class SearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('searchbar', TextType::class, [
'label' => false,
'required' => false,
'attr' => [
'placeholder' => 'recherche...',
'style' => 'width: 50vw',
],]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => PropertySearch::class,
'csrf_protection' => false,
]);
}
}
entity for the form:
class PropertySearch{
private $searchbar;
/**
* #return mixed
*/
public function getSearchbar()
{
return $this->searchbar;
}
/**
* #param mixed $searchbar
* #return PropertySearch
*/
public function setSearchbar($searchbar)
{
$this->searchbar = $searchbar;
return $this;
}
}
controller :
class TextController extends AbstractController
{
/**
* #Route("/liste", name="list")
*/
public function index(TextesRepository $repo, Request $request)
{
$search = new PropertySearch();
$searchform=$this->createForm(SearchType::class, $search);
$searchform->handleRequest($request);
return $this->render('text/index.html.twig', [
'controller_name' => 'TextController.php',
'articles' => $repo->findAllVisible($search),
'searchForm' => $searchform->createView()
]);
}
You don't need to use the form tag beacuse using form_start and form_end already creates this tag.
Due to this you have a form inside a form, what may cause some troubles in html forms.
{{ form_start(searchForm) }}
{{ form_errors(searchForm) }}
{{ form_row(searchForm.searchbar) }}
<button type="submit" class="btn btn-secondary my-4 ml-1 my-sm-0">🔍</button>
{{ form_end((searchForm)) }}
Documentation
Also I recommend you to add the submit button to the form. Link
Is it possible for to display in a column instead of side by each?
The Radio options are selected in FormElement
namespace Main\Form\Element;
use Doctrine\ORM\EntityManager;
use Zend\Form\Element\Radio;
/**
* Class SurveyAnswerRadio
*
* #package Main\Form\Element
*/
class SurveyAnswerRadio extends Radio
{
/**
* #var EntityManager $entityManager
*/
protected $entityManager;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* Get Value Options
*
* #return array
*
* #throws \Exception
*/
public function getValueOptions()
{
$array = [];
$survey_question_reference = 1;
$result = $this->entityManager
->getRepository('AMDatabase\Entity\TheVerse\SA')
->findBy(
[
'sqReference' => $survey_question_reference
],
[
'surveyAnswer' => 'ASC'
]
);
if (is_array($result) && count($result) > '0') {
/**
* #var \AMDatabase\Entity\TheVerse\SA $val
*/
foreach ($result as $val) {
$array[$val->getReference()] = $val->getSurveyAnswer();
}
}
return $array;
}
}
This is added to the Form
/**
* Survey Answer
*/
$this->add(
[
'type' => 'Main\Form\Element\SurveyAnswerRadio',
'name' => 'survey_answer',
'options' => [
'label' => 'survey_answer'
],
'attributes' => [
'id' => 'survey_answer'
]
]
);
}
Then it is displayed within a view Twig
<div class="field">
<span>{{ formLabel(form.get('survey_answer')) }}</span>
{{ formRadio(form.get('survey_answer')) }}
</div>
The output encloses each within a . I am wanting the output to be displayed in this fashion:
<ul>
<li><label><input type="radio" name="survey_answer" id="survey_answer" value="1">option 1</label><li>
<li><label><input type="radio" name="survey_answer" id="survey_answer" value="2">option 2</label></li>
<li><label><input type="radio" name="survey_answer" id="survey_answer" value="3">option 3</label></li>
</ul>
use this command in view
$this->formRadio()->setSeparator('</li><li class="yourclass">');
this command set separator between your radio options.
then you show your element like this
<li class="yourclass" >
<?php echo $this->formRadio($form->get('survey_answer')); ?>
</li>
I'm trying to add a Select2 input using the GenemuFormBundle as described in the "Use jQuery Select2 with Ajax" doc. Adding a jQuery Select2 Field following the jQuery Select2 Field documentation works just fine.
But the documentation on how to implement an Ajax-loading Select2 form is very inconclusive. If I unterstand the doc correctly, it aims to create the same as mentioned in the Select2 documentation. This is exactly the thing I'd like to create. I added hidden field as well as the required JavaScript, but the only thing that I get is a Variable "id" does not exist in xBundle:x:new.html.twig at line x.
Form builder (taken directly form the mentioned doc):
...
->add('field_name', 'genemu_jqueryselect2_hidden', array(
'configs' => array(
'multiple' => true // Wether or not multiple values are allowed (default to false)
)
))
->add('field_name', 'genemu_jqueryselect2_entity', array(
'class' => 'xBundle:Entity',
'property' => 'foo',
))
View (also taken directly form the doc):
{% block stylesheets %}
{{ form_stylesheet(form) }}
{% endblock %}
{% block javascript %}
{{ form_javascript(form) }}
{% endblock %}
{% block genemu_jqueryselect2_javascript %}
<script type="text/javascript">
$field = $('#{{ id }}');
var $configs = {{ configs|json_encode|raw }};
// custom configs
$configs = $.extend($configs, {
query: function (query) {
var data = {results: []}, i, j, s;
for (i = 1; i < 5; i++) {
s = "";
for (j = 0; j < i; j++) {s = s + query.term;}
data.results.push({id: query.term + i, text: s});
}
query.callback(data);
}
});
// end of custom configs
$field.select2($configs);
</script>
{% endblock %}
I just struggled with this exact issue and thought I would throw in my own findings for anyone that happens to stumble across this. I did find a solution, but I would probably still recommend just going with the ZenStruckFormBundle recommended by Doug because it seems to actually be designed to work as a solution for a select2 field type that loads via ajax and relates back to an entity.
The real reason this doesn't work is that the the genemu_jqueryselect2_* types for the GenemuFormBundle do not implement a data transformer that will work with an entity when you need an ajax-loading select2 field. It doesn't seem like it was ever designed to work this way. When you use the genemu_jqueryselect2_hidden type and have the “multiple’ config option set to true, then it adds an ArrayToStringTransformer. This will not work with an entity.
To fix this you need to make a new form field type and define that its parent is genemu_jqueryselect2_hidden then make a few customizations. It would look something like this…
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Acme\DemoBundle\Form\DataTransformer\EntityCollectionToIdTransformer;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class EntityCollectionSelectorType extends AbstractType
{
/**
* #var ObjectManager
*/
protected $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new EntityCollectionToIdTransformer($this->om, $options['configs']['entity']);
$builder->resetViewTransformers();
$builder->addModelTransformer($transformer);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'invalid_message' => 'The selected entity does not exist',
'required' => true,
'auto_initialize' => false,
'configs' => array('multiple' => true),
'error_bubbling' => false,
));
}
public function getParent()
{
return 'genemu_jqueryselect2_hidden';
}
public function getName()
{
return 'entity_collection_selector';
}
}
Then you will also need to add the new data transformer used in the above form field type, so that it can translate the values between the form field and the entity…
namespace Acme\DemoBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Collections\ArrayCollection;
class EntityCollectionToIdTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $om;
/**
* #var string The Doctrine entity type to use
*/
private $entityType;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om, $entityType)
{
$this->om = $om;
$this->entityType = $entityType;
}
/**
* Transforms a collection of entities to a comma separated string
*
* #param ArrayCollection $entities
* #return string
*/
public function transform($entities)
{
if (null == $entities || empty($entities)) {
return '';
}
$results = '';
foreach ($entities as $entity) {
$results .= $entity->getId() . ',';
}
$results = trim($results, ' ,');
return $results;
}
/**
* Transforms a string of comma separated IDs to a PersistentCollection for Doctrine
*
* #param string $values
* #return PersistentCollection|ArrayCollection
* #throws TransformationFailedException if entity is not found.
*/
public function reverseTransform($values)
{
if (!$values) {
return new ArrayCollection();
}
$values = explode(',', $values);
$collection = array();
foreach ($values as $id) {
$item = $this->om->getRepository($this->entityType)->findOneById($id);
if (!is_null($item)) {
$collection[] = $item;
}
else {
throw new TransformationFailedException(sprintf(
'An entity with ID "%s" does not exist!',
$value
));
}
}
return new PersistentCollection($this->om, $this->entityType, new ArrayCollection($collection));
}
}
Now make sure you define your new field type in your config for your services…
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
...
<parameter key="acme_demo.form.type.entity_collection_selector.class">Acme\DemoBundle\Form\Type\EntityCollectionSelectorType</parameter>
...
</parameters>
<services>
...
<service id="acme_demo.form.type.entity_collection_selector"
class="%acme_demo.form.type.entity_collection_selector.class%">
<argument type="service" id="doctrine.orm.default_entity_manager" />
<tag name="form.type" alias="entity_collection_selector" />
</service>
...
</services>
</container>
Now you can use it as such…
$builder->add('customers', 'entity_collection_selector', array(
'configs' => array('entity' => 'AcmeDemoBundle:Customer')
));