Symfony2 : Sort / Order a translated entity form field? - forms

I am trying to order an entity form field witch is translated.
I am using the symfony translation tool, so i can't order values with a SQL statement.
Is there a way to sort values after there are loaded and translated ?
Maybe using a form event ?
$builder
->add('country', 'entity',
array(
'class' => 'MyBundle:Country',
'translation_domain' => 'countries',
'property' => 'name',
'empty_value' => '---',
)
)

I found the solution to sort my field values in my Form Type.
We have to use the finishView() method which is called when the form view is created :
<?php
namespace My\Namespace\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
class MyFormType extends AbstractType
{
protected $translator;
public function __construct(Translator $translator)
{
$this->translator = $translator;
}
public function finishView(FormView $view, FormInterface $form, array $options)
{
// Order translated countries
$collator = new \Collator($this->translator->getLocale());
usort(
$view->children['country']->vars['choices'],
function ($a, $b) use ($collator) {
return $collator->compare(
$this->translator->trans($a->label, array(), 'countries'),
$this->translator->trans($b->label, array(), 'countries')
);
}
);
}
// ...
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('country', 'entity',
array(
'class' => 'MyBundle:Country',
'translation_domain' => 'countries',
'property' => 'name',
'empty_value' => '---',
)
)
;
}
}
OLD ANSWER
I found a solution for my problem, I can sort them in my controller after creating the view :
$fview = $form->createView();
usort(
$fview->children['country']->vars['choices'],
function($a, $b) use ($translator){
return strcoll($translator->trans($a->label, array(), 'countries'), $translator->trans($b->label, array(), 'countries'));
}
);
Maybe I can do that in a better way ?
Originally I wished to do directly in my form builder instead of adding extra code in controllers where I use this form.

I think it's impossible. You need to use PHP sorting, but if you use Symfony Form Type, I would advise to sort it with JavaScript after page is loaded.

If your countries are in an array, just use the sort() function, with the SORT_STRING flag. You will do some gymnastic to have it in my opinion.
Check this doc : http://php.net/manual/fr/function.sort.php

Related

Can't get a way to read the property "user" in class "App\Entity\User"

I made a form so that when a user is selected, his role changes from ["ROLE_USER"] to ["ROLE_ADMIN"]. When form is submitted, I have the following error : Can't get a way to read the property "user" in class "App\Entity\User".
I understand it must come the fact there is no such field named user in the User class, but I don't know with which field I can replace user. I already tried name or roles, but it doesn't work either with them.
How can I select a user and simply change his role ?
AdminType.php
<?php
namespace App\Form\Admin;
use App\Entity\User;
use Doctrine\ORM\EntityRepository;
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\SubmitType;
class AdminType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('user', EntityType::class, [
'class' => User::class,
'choice_label' => 'name',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '["ROLE_USER"]')
->orderBy('u.firstname', 'ASC');
},
'placeholder' => 'J\'ajoute un administrateur',
])
->add('save', SubmitType::class, [
'attr' => ['class' => 'save'],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
'translation_domain' => 'forms'
]);
}
}
AdminController.php
<?php
namespace App\Controller\Admin;
use App\Form\Admin\AdminType;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class AdminController extends AbstractController
{
#[Route('/admin/list', name: 'admin')]
public function admin(
Request $request,
UserRepository $userRepository,
EntityManagerInterface $entityManagerInterface
){
$admins = $userRepository->admin();
$form = $this->createForm(AdminType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $form->get('user')->getData();
$user->setRoles(["ROLE_ADMIN"]);
$entityManagerInterface->persist($user);
$entityManagerInterface->flush();
return $this->redirectToRoute('admin');
}
return $this->renderForm('admin/user/admin.html.twig', [
'admins' => $admins,
'form' => $form
]);
}
Short answer
remove 'data_type' => User::class from configureOptions in your AdminType form.
Explanation
Setting the data_type of the form to the User entity will cause Symfony to try to create a User object and set its user Property to the value in the form's user field (which you get via $form->get('user')). That's why the error message tells you, that it can't find a way to read (to then overwrite) the property user on the User class.
Removing the data_type will then mean, that the form's data type is just an array (the default), you could also explicitly set null.
If the form's data type is an array, it'll just set the user key in that array.
Since your form's user field is of type EntityType, with a given class, it already ensures that the form's user field's value must be of that class (the User entity). And since you only want to select a user, to then add a role, I assume that the form doesn't need the User data_type.

Symfony change form collection dropdown with propel model

Is it possible to change the contents of a form dropdown that is part of a form collection that is populated using propel but the data is not mapped. Example of code to get the data below:
AddressType:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->
->add("addressOne", new addressOneType()),
->add("addressTwo", new addressTwoType(), array(
"required" => false,
)),
}
addressOneType:
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->
->setMethod('POST')
->add('Country', 'model', array(
'mapped' => 'bundle\nameBundle\Model\Countries',
'required' => true,
'multiple' => false,
'expanded' => false,
'property' => 'label',
'query' => CountryQuery::create()->find(),
))
->getForm();
}
This collection is used for a particular part of an application however in this part in need to call a service from the form itself. Would this be possible as I've tried to extend the ContainerInterface and declare this inside of the construct method however this just throws an error.
However, I beleive this to be due to the fact that the form builder is not declared as a service.
Is there an easier way of changing the data of the drop down menu by injecting a new model to override the original. For example:
$form = $this->createForm(new AddressType());
$newData = CountriesQuery::create()
->orderBy("different_field");
$form['collectionName']['fieldname']->setData($newData);
Doing the above doesn't change or override the original model that is changing the data. With or without the ->find() at the end of the $newData field.
Does anyone know of a way to overwrite the data set by the model?
A very simple way for pass specific options to form is in constructor ...
class addressOneType
{
protected $countryQuery;
public function __constructor( $countryQuery = null )
{
$this->countryQuery = $countryQuery;
}
public function buildForm(FormBuilderInterface $builder, array $options){
$query = $this->countryQuery ? $this->countryQuery :
CountryQuery::create();
$builder
->setMethod('POST')
->add('Country', 'model', array(
'mapped' => 'bundle\nameBundle\Model\Countries',
'required' => true,
'multiple' => false,
'expanded' => false,
'property' => 'label',
'query' => $query->find(),
))
->getForm();
}
}
... and you can call to form in this way ...
$cQuery = CountriesQuery::create()->orderBy("different_field");
$form = $this->createForm(new AddressType($cQuery));

bind error to embedded form field in symfony2's controller

Using Symfony2.3.4
As the title quite explains it I need to bind an error message to an embedded form field and preferably in the controller. I'm thinking maybe similar to how I do it with single forms:
use Symfony\Component\Form\FormError;
//....
public function createAction(){
//.....
$postData = current($request->request->all());
if ($postData['field_name'] == '') {
$error = new FormError("Write some stuff in here");
$form->get('field_name')->addError($error);
}
//.....
}
or maybe it's got to be done differently, either way I need help,
thank$
I see, that you are trying to display a message when a form field does not contain any value. You can do this easily in your form class, like this:
buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('field_name', 'text', array(
'label' => 'Field label',
'required' => true,
'constraints' => array(
new NotBlank(array('message' => 'Write some stuff in here.'))
),
));
}
If you need to inject some other kind of constraint to your form which is not part of Symfony2 framework, you can create your own validation constraint.
If you want to add some options to your form in controller, it can be done in the method where you create the form by setting your own options:
class YourController {
public function createForm(YourEntity $yourEntity){
$form = $this->createForm(new YourFormType(), $yourFormType, array(
'action' => $this->generateUrl('your_action_name',
array('your_custom_option_key' => 'Your custom option value')),
'method' => 'POST',
));
return $form;
}
// Rest of code omitted.
}
After that you need to add an option in setDefaultOptions(OptionsResolverInterface $resolver) method, in your form class, like this:
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults(array(
'your_custom_option_key' => '',
));
}
And then access it in buildForm(FormBuilderInterface $builder, array $options) method, like this:
buildForm(FormBuilderInterface $builder, array $options) {
$options['your_custom_option_key']; // Access content of your option
// The rest of code omitted.
}

Form validation - Require only one field to be filled in

I have the following form class:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('existingfolder', 'entity', array(
'class' => 'ImageBundle:Folder',
'required' => false,
))
->add('folder', 'text', array('required' => false))
->add('file', 'file');
}
How can I set up the validation so that either the existingfolder or folder field must be filled (but not both of them)?
Any advice appreciated.
Thanks.
Use the True or Callback validation assert, here an example to check if the user must give at least one of the folders:
<?php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Image
{
// ...properties, functions, etc...
/**
* #Assert\True(message = "You must give at least an existing folder or a new folder")
*/
public function isThereOneFieldFilled()
{
return ($this->existingfolder || $this->folder); // If false, display an error !
}
}
Here another example if the user must give ONLY one field but not both:
/**
* #Assert\True(message = "You must give an existing folder or a new folder, not both")
*/
public function isThereOnlyOneFieldFilled()
{
return (!$this->existingfolder && $this->folder || $this->existingfolder && !$this->folder);
}
EDIT:
Callback validation inside a form (I found an example here):
// use ...
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
// Inside the form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('existingfolder', 'entity', array(
'class' => 'ImageBundle:Folder',
'required' => false,
))
->add('folder', 'text', array('required' => false))
->add('file', 'file');
// Use the CallbackValidator like a TrueValidator behavior
$builder->addValidator(new CallbackValidator(function(FormInterface $form) {
if (!$form["existingfolder"]->getData() && !$form["folder"]->getData()) {
$form->addError(new FormError('You must give at least an existing folder or a new folder'));
}
}));
}

Symfony2 Setting a default choice field selection

I am creating a form in the following manner:
$form = $this->createFormBuilder($breed)
->add('species', 'entity', array(
'class' => 'BFPEduBundle:Item',
'property' => 'name',
'query_builder' => function(ItemRepository $er){
return $er->createQueryBuilder('i')
->where("i.type = 'species'")
->orderBy('i.name', 'ASC');
}))
->add('breed', 'text', array('required'=>true))
->add('size', 'textarea', array('required' => false))
->getForm()
How can I set a default value for the species listbox?
Thank you for your response, I apologise, I think I should rephrase my question. Once I have a value that I retrieve from the model, how do I set that value as SELECTED="yes" for the corresponding value in the species choice list?
So, that select option output from the TWIG view would appear like so:
<option value="174" selected="yes">Dog</option>
You can define the default value from the 'data' attribute. This is part of the Abstract "field" type (http://symfony.com/doc/2.0/reference/forms/types/field.html)
$form = $this->createFormBuilder()
->add('status', 'choice', array(
'choices' => array(
0 => 'Published',
1 => 'Draft'
),
'data' => 1
))
->getForm();
In this example, 'Draft' would be set as the default selected value.
If you use Cristian's solution, you'll need to inject the EntityManager into your FormType class. Here is a simplified example:
class EntityType extends AbstractType{
public function __construct($em) {
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->add('MyEntity', 'entity', array(
'class' => 'AcmeDemoBundle:Entity',
'property' => 'name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('e')
->orderBy('e.name', 'ASC');
},
'data' => $this->em->getReference("AcmeDemoBundle:Entity", 3)
));
}
}
And your controller:
// ...
$form = $this->createForm(new EntityType($this->getDoctrine()->getManager()), $entity);
// ...
From Doctrine Docs:
The method EntityManager#getReference($entityName, $identifier) lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier.
the solution: for type entity use option "data" but value is a object. ie:
$em = $this->getDoctrine()->getEntityManager();
->add('sucursal', 'entity', array
(
'class' => 'TestGeneralBundle:Sucursal',
'property'=>'descripcion',
'label' => 'Sucursal',
'required' => false,
'data'=>$em->getReference("TestGeneralBundle:Sucursal",3)
))
I think you should simply use $breed->setSpecies($species), for instance in my form I have:
$m = new Member();
$m->setBirthDate(new \DateTime);
$form = $this->createForm(new MemberType, $m);
and that sets my default selection to the current date. Should work the same way for external entities...
If you want to pass in an array of Doctrine entities, try something like this (Symfony 3.0+):
protected $entities;
protected $selectedEntities;
public function __construct($entities = null, $selectedEntities = null)
{
$this->entities = $entities;
$this->selectedEntities = $selectedEntities;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('entities', 'entity', [
'class' => 'MyBundle:MyEntity',
'choices' => $this->entities,
'property' => 'id',
'multiple' => true,
'expanded' => true,
'data' => $this->selectedEntities,
]);
}
I don't think you should use the data option, because this does more than just setting a default value.
You're also overriding any data that's being passed to the form during creation. So basically, you're breaking
support for that feature. - Which might not matter when you're letting the user create data, but does matter when you
want to (someday) use the form for updating data.
See http://symfony.com/doc/current/reference/forms/types/choice.html#data
I believe it would be better to pass any default data during form creation. In the controller.
For example, you can pass in a class and define the default value in your class itself.
(when using the default Symfony\Bundle\FrameworkBundle\Controller\Controller)
$form = $this->createForm(AnimalType::class, [
'species' => 174 // this id might be substituted by an entity
]);
Or when using objects:
$dog = new Dog();
$dog->setSpecies(174); // this id might be substituted by an entity
$form = $this->createForm(AnimalType::class, $dog);
Even better when using a factory:
(where dog probably extends from animal)
$form = $this->createForm(AnimalType::class, DogFactory::create());
This will enable you to separate form structure and content from each other and make
your form reusable in more situations.
Or, use the preferred_choices option, but this has the side effect of moving the default option to the top of your form.
See: http://symfony.com/doc/current/reference/forms/types/choice.html#preferred-choices
$builder->add(
'species',
'entity',
[
'class' => 'BFPEduBundle:Item',
'property' => 'name',
'query_builder' => ...,
'preferred_choices' => [174] // this id might be substituted by an entity
]
);
I'm not sure what you are doing wrong here, when I build a form using form classes Symfony takes care of selecting the correct option in the list. Here's an example of one of my forms that works.
In the controller for the edit action:
$entity = $em->getRepository('FooBarBundle:CampaignEntity')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find CampaignEntity entity.');
}
$editForm = $this->createForm(new CampaignEntityType(), $entity);
$deleteForm = $this->createDeleteForm($id);
return $this->render('FooBarBundle:CampaignEntity:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
The campaign entity type class (src: Foo\BarBundle\Form\CampaignEntityType.php):
namespace Foo\BarBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Doctrine\ORM\EntityRepository;
class CampaignEntityType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('store', 'entity', array('class'=>'FooBarBundle:Store', 'property'=>'name', 'em'=>'my_non_default_em','required' => true, 'query_builder' => function(EntityRepository $er) {return $er->createQueryBuilder('s')->orderBy('s.name', 'ASC');}))
->add('reward');
}
public function getName()
{
return 'foo_barbundle_campaignentitytype';
}
}
From the docs:
public Form createNamed(string|FormTypeInterface $type, string $name, mixed $data = null, array $options = array())
mixed $data = null is the default options. So for example I have a field called status and I implemented it as so:
$default = array('Status' => 'pending');
$filter_form = $this->get('form.factory')->createNamedBuilder('filter', 'form', $default)
->add('Status', 'choice', array(
'choices' => array(
'' => 'Please Select...',
'rejected' => 'Rejected',
'incomplete' => 'Incomplete',
'pending' => 'Pending',
'approved' => 'Approved',
'validated' => 'Validated',
'processed' => 'Processed'
)
))->getForm();
Setting default choice for symfony2 radio button
$builder->add('range_options', 'choice', array(
'choices' => array('day'=>'Day', 'week'=>'Week', 'month'=>'Month'),
'data'=>'day', //set default value
'required'=>true,
'empty_data'=>null,
'multiple'=>false,
'expanded'=> true
))
The form should map the species->id value automatically to the selected entity select field. For example if your have a Breed entity that has a OnetoOne relationship with a Species entity in a join table called 'breed_species':
class Breed{
private $species;
/**
* #ORM\OneToOne(targetEntity="BreedSpecies", mappedBy="breed")
*/
private $breedSpecies;
public function getSpecies(){
return $breedSpecies->getSpecies();
}
private function getBreedSpecies(){
return $this->$breedSpecies;
}
}
The field 'species' in the form class should pick up the species->id value from the 'species' attribute object in the Breed class passed to the form.
Alternatively, you can explicitly set the value by explicitly passing the species entity into the form using SetData():
$breedForm = $this->createForm( new BreedForm(), $breed );
$species = $breed->getBreedSpecies()->getSpecies();
$breedForm->get('species')->setData( $species );
return $this->render( 'AcmeBundle:Computer:edit.html.twig'
, array( 'breed' => $breed
, 'breedForm' => $breedForm->createView()
)
);
You can either define the right default value into the model you want to edit with this form or you can specify an empty_data option so your code become:
$form = $this
->createFormBuilder($breed)
->add(
'species',
'entity',
array(
'class' => 'BFPEduBundle:Item',
'property' => 'name',
'empty_data' => 123,
'query_builder' => function(ItemRepository $er) {
return $er
->createQueryBuilder('i')
->where("i.type = 'species'")
->orderBy('i.name', 'ASC')
;
}
)
)
->add('breed', 'text', array('required'=>true))
->add('size', 'textarea', array('required' => false))
->getForm()
;
You can use "preferred_choices" and "push" the name you want to select to the top of the list. Then it will be selected by default.
'preferred_choices' => array(1), //1 is item number
entity Field Type