How to to an orderby on a ManyToMany relation field - symfony

i got an entity called "Recipes" that has a relation with another entity called "Ingredients".
/**
* #ORM\ManyToMany(targetEntity=Ingredients::class, inversedBy="recettes")
*/
private $ingredient;
What i need is to list all ingredients via my form builder, and it works well :
->add('ingredient', EntityType::class, [
// looks for choices from this entity
'class' => Ingredients::class,
'choice_label' => 'nom',
// used to render a select box, check boxes or radios
'multiple' => true,
'expanded' => true,
'label' => 'Ingrédients de la recette'
])
But i need to list them ordered by their name, so i've tried to add this line on my "Recipes ingredient field" :
* #OrderBy({"nom" = "ASC"})
But ingredients remains not ordered by their name.
Am i missing something ? :)

/**
* ...
* #ORM\OrderBy({"nom" = "ASC"})
*/
private $recettes;
or
->add('ingredient', EntityType::class, [
// looks for choices from this entity
'class' => Ingredients::class,
'choice_label' => 'nom',
// used to render a select box, check boxes or radios
'multiple' => true,
'expanded' => true,
'label' => 'Ingrédients de la recette',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')->orderBy('u.nom', 'ASC');
},
])

Related

Pass array of data to form

I'm using symfony and I need to pass two arrays so I can make a selectable list.
For now in form builder I have this:
->add('city', EntityType::class, [
'label' => 'Select city',
'class' => Cities::class,
'choice_label' => 'title',
'choice_value' => 'title'
])
It throws me all cities in the list. I want to filtrate them. I've done filtration on my controller like this:
$capitals = $cityRepository->findBy(['cityType' => CityType::capital()->id()]);
$villages = $cityRepository->findBy(['villageType' => CityType::village()->id()]);
this returns me two arrays: capitals and villages.
How can I pass them to the form ?
In your controller :
$form=$this->createForm(YourFormType::class, $yourEntity, array(
'capitals'=>$capitals,
'villages'=>$villages
));
In your form :
public function buildForm(FormBuilderInterface $builder, array $options) {
/** #var array $capitals */
$capitals=$options['capitals'];
/** #var array $villages */
$villages=$options['villages'];
$builder->add('city', EntityType::class, array(...)
->add('vallacap', ChoiceType::class, array(
'mapped'=>false, //Make sure it's not mapped to any entity
'choices'=>array(
'Capitals'=>$capitals,
'Villages'=>$villages
),
'required'=>false,
));
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class'=>Personnalisation::class,
'capitals'=>null, // Set default to null in case argument is not passed
'villages'=>null,
));
}
You need to pass the arrays to choices option.
https://symfony.com/doc/current/reference/forms/types/entity.html#using-choices
->add('city', EntityType::class, [
'label' => 'Select city',
'class' => Cities::class,
'choice_label' => 'title',
'choice_value' => 'title',
'choices' => $choices // ------------> This line
])
$choices = array_merge($capitals, $villages);

How can I change error message in Symfony

I create a form and then when I click on submit button, show this error message:
Please select an item in the list.
How can I change this message and style it ( with CSS )?
Entity:
...
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank(message="Hi user, Please select an item")
*/
private $name;
...
Controller:
...
public function index(Request $request)
{
$form = $this->createForm(MagListType::class);
$form->handleRequest($request);
return $this->render('index.html.twig', [
'form' => $form->createView()
]);
}
...
Form:
...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', EntityType::class,[
'class' => InsCompareList::class,
'label' => false,
'query_builder' => function(EntityRepository $rp){
return $rp->createQueryBuilder('u')
->orderBy('u.id', 'ASC');
},
'choice_label' => 'name',
'choice_value' => 'id',
'required' => true,
])
->add('submit', SubmitType::class, [
'label' => 'OK'
])
;
...
}
In order to use custom messages, you have to put 'novalidate' on your HTML form. For example:
With Twig:
{{ form_start(form, {attr: {novalidate: 'novalidate'}}) }}
{{ form_widget(form) }}
{{ form_end(form) }}
Or in your controller:
$form = $this->createForm(MagListType::class, $task, array( //where task is your entity instance
'attr' => array(
'novalidate' => 'novalidate'
)
));
This Stackoverflow answer has more info on how to use novalidate with Symfony. You can also check the docs for more info.
As for the styling, you can use JavaScript to trigger classes, which you can then style in your CSS, like in this example taken from Happier HTML5 Form Validation . You can also take a look at the documentation on MDN and play with the :valid and :invalid selectors.
const inputs = document.querySelectorAll("input, select, textarea");
inputs.forEach(input => {
input.addEventListener(
"invalid",
event => {
input.classList.add("error");
},
false
);
});
EDIT:
You are probably not seeing your custom message because it comes from server side, the message that you're currently seeing when submitting your form is client-side validation. So you can either place novalidate on your field, or you can override the validation messages on the client side by using setCustomValidity(), as described in this SO post which also contains many useful links. An example using your Form Builder:
$builder
->add('name', EntityType::class,[
'class' => InsCompareList::class,
'label' => false,
[
'attr' => [
'oninvalid' => "setCustomValidity('Hi user, Please select an item')"
]
],
'query_builder' => function(EntityRepository $rp){
return $rp->createQueryBuilder('u')
->orderBy('u.id', 'ASC');
},
'choice_label' => 'name',
'choice_value' => 'id',
'required' => true,
])
->add('submit', SubmitType::class, [
'label' => 'OK'
]);

How can I set a new value for field form depending what user selects in choice field? Symfony2

I have a form with 3 fields, "productoOc" which is a related field, "cantidad" and "precioCompra", I need to set value for "precioCompra" when user selects a "productoOc". This value comes from a thrid entity which have prices.
My code:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('ordenCompra')
->add('productoOc', 'entity', array(
'class' => 'NivalInventarioBundle:InProducto',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.nombre', 'ASC');
},
'choice_label' => function ($displayname) {
return $displayname->getDisplayName();},
'by_reference' => true,
'expanded' => false,
'placeholder' => 'Seleccione una opción',
'mapped' => true,
'multiple' => false,
))
->add('cantidad')
->add('precioCompra')
;
}

Dynamic Generation for Submitted Forms with Form events

I've a little problem with FormEvents, I want do 3 fields populated dynamically.
I explain, I've 3 fields: Project > Box > Cell, the user choose a Project, the Box list is updated, he choose a Box, the cell list is updated.
To do it, I use FormEvent like the documentation say (http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data)
But I've a problem, for just one field dynamically updated, it's work, but no for 2 fields... Actually a user can choose a project, and when he does it, the box field is updated. But, when he choose a box, the cell field wasn't updated...
But, I've find something, who permit it to work, just change something in a ->add() and inversed to ->add(). But I don't want it.
My code is:
$builder
->add('project', EntityType::class, array(
'class' => 'AppBundle\Entity\Project',
'choice_label' => 'name',
'placeholder' => '-- select a project --',
'mapped' => false,
))
->add('box', EntityType::class, array(
'class' => 'AppBundle\Entity\Box',
'choice_label' => 'name',
'placeholder' => '-- select a box --',
'choices' => [],
))
->add('cell', ChoiceType::class, array(
'placeholder' => '-- select a cell --',
))
;
And when I change it to:
builder
->add('box', EntityType::class, array(
'class' => 'AppBundle\Entity\Box',
'choice_label' => 'name',
'placeholder' => '-- select a box --',
// 'choices' => [],
))
->add('project', EntityType::class, array(
'class' => 'AppBundle\Entity\Project',
'choice_label' => 'name',
'placeholder' => '-- select a project --',
'mapped' => false,
))
->add('cell', ChoiceType::class, array(
'placeholder' => '-- select a cell --',
))
;
It's work... But I want an empty list for box at the start, and I want project before box...
A little precision, this form is embded in an other form as a CollectionType.
All the code of this Type:
<?php
namespace AppBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TubeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('project', EntityType::class, array(
'class' => 'AppBundle\Entity\Project',
'choice_label' => 'name',
'placeholder' => '-- select a project --',
'mapped' => false,
))
->add('box', EntityType::class, array(
'class' => 'AppBundle\Entity\Box',
'choice_label' => 'name',
'placeholder' => '-- select a box --',
'choices' => [],
))
->add('cell', ChoiceType::class, array(
'placeholder' => '-- select a cell --',
))
;
// MODIFIER
$boxModifier = function (FormInterface $form, $project) {
$boxes = (null === $project) ? [] : $project->getBoxes();
$form->add('box', EntityType::class, array(
'class' => 'AppBundle\Entity\Box',
'choice_label' => 'name',
'placeholder' => '-- select a box --',
'choices' => $boxes,
));
};
$cellModifier = function(FormInterface $form, $box) {
$cells = (null === $box) ? [] : $box->getEmptyCells();
$form->add('cell', ChoiceType::class, array(
'placeholder' => '-- select a cell --',
'choices' => $cells,
));
};
// FORM EVENT LISTENER
$builder->get('project')->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event) use ($boxModifier) {
$project = $event->getForm()->getData();
$boxModifier($event->getForm()->getParent(), $project);
}
);
$builder->get('box')->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event) use ($cellModifier) {
$box = $event->getForm()->getData();
$cellModifier($event->getForm()->getParent(), $box);
}
);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Tube'
));
}
}
Thanks a lot to your help :)
You should use $builder->addEventListener. For multiple fields all you need to do is to have dynamic fields inside FormEvents::PRE_SET_DATA event handler. Also, use parent field data, as explained in the doc to fetch child field choices.
I have used this approach, for generating Country, State and City Entities in hierarchical fields. Let me know if it helps or you need more information.
EDIT : For bigger logic, you can use eventSubscriber which will keep your code clean and you also get to re-use dynamic part of the form for somewhere else.
For multiple dependent hierarchical fields, just add them through conditions in the eventSubscriber class.
Update with code snippet :
Here is a walk through on code snippet that worked for me in Symfony 2.7
Note : I don't replace the dynamic html field as described in the document, instead via jQuery I simply collect child options as per selected parent option and fill in those. When submitted, The form recognises correct child options as per eventSubscriber context. So here is how you might do it :
In your parent Form type (where you have all 3 fields) call a eventSubscriber instead of defining those 3 fields :
$builder->add(); // all other fields..
$builder->addEventSubscriber(new DynamicFieldsSubscriber());
Create an eventSubscriber as defined in the doc, here the file name is DynamicFieldsSubscriber
<?php
namespace YourBundle\Form\EventListener;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\FormInterface;
class DynamicFieldsSubscriber implements EventSubscriberInterface
{
/**
* Define the events we need to subscribe
* #return type
*/
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'preSetData', // check preSetData method below
FormEvents::PRE_SUBMIT => 'preSubmitData', // check preSubmitData method below
);
}
/**
* Handling form fields before form renders.
* #param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
$location = $event->getData();
// Location is the main entity which is obviously form's (data_class)
$form = $event->getForm();
$country = "";
$state = "";
$district = "";
if ($location) {
// collect preliminary values for 3 fields.
$country = $location->getCountry();
$state = $location->getState();
$district = $location->getDistrict();
}
// Add country field as its static.
$form->add('country', 'entity', array(
'class' => 'YourBundle:Country',
'label' => 'Select Country',
'empty_value' => ' -- Select Country -- ',
'required' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.status = ?1')
->setParameter(1, 1);
}
));
// Now add all child fields.
$this->addStateField($form, $country);
$this->addDistrictField($form, $state);
}
/**
* Handling Form fields before form submits.
* #param FormEvent $event
*/
public function preSubmitData(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
// Here $data will be in array format.
// Add property field if parent entity data is available.
$country = isset($data['country']) ? $data['country'] : $data['country'];
$state = isset($data['state']) ? $data['state'] : $data['state'];
$district = isset($data['district']) ? $data['district'] : $data['district'];
// Call methods to add child fields.
$this->addStateField($form, $country);
$this->addDistrictField($form, $state);
}
/**
* Method to Add State Field. (first dynamic field.)
* #param FormInterface $form
* #param type $country
*/
private function addStateField(FormInterface $form, $country = null)
{
$countryCode = (is_object($country)) ? $country->getCountryCode() : $country;
// $countryCode is dynamic here, collected from the event based data flow.
$form->add('state', 'entity', array(
'class' => 'YourBundle:State',
'label' => 'Select State',
'empty_value' => ' -- Select State -- ',
'required' => true,
'attr' => array('class' => 'state'),
'query_builder' => function (EntityRepository $er) use($countryCode) {
return $er->createQueryBuilder('u')
->where('u.countryCode = :countrycode')
->setParameter('countrycode', $countryCode);
}
));
}
/**
* Method to Add District Field, (second dynamic field)
* #param FormInterface $form
* #param type $state
*/
private function addDistrictField(FormInterface $form, $state = null)
{
$stateCode = (is_object($state)) ? $state->getStatecode() : $state;
// $stateCode is dynamic in here collected from event based data flow.
$form->add('district', 'entity', array(
'class' => 'YourBundle:District',
'label' => 'Select District',
'empty_value' => ' -- Select District -- ',
'required' => true,
'attr' => array('class' => 'district'),
'query_builder' => function (EntityRepository $er) use($stateCode) {
return $er->createQueryBuilder('s')
->where('s.stateCode = :statecode')
->setParameter('statecode', $stateCode);
}
));
}
}
After this, you need to write jQuery events which should update child options on change of parent option explicitly, You shouldn't face any error on submission of the form.
Note : The code above is extracted and changed for publishing here. Take care of namespace and variables where ever required.

How validate a selection of at least one element (or N elements) in Symfony 2?

My form as field users (entity type). How can i add validation in order to specificity that at least one user should be selected? Actually i'm adding an event listener but i don't know if this is a legit solution or not:
public function buildForm(\Symfony\Component\Form\FormBuilder $builder,
array $options)
{
$builder
->add('title', 'text', array(
'label' => 'Titolo'
))
->add('content', 'textarea', array(
'label' => 'Contenuto'
))
->add('sender_text', 'text', array(
'label' => 'Mittente testuale',
))
->add('users', 'entity', array(
'label' => 'Destinatari',
'class' => 'DL\FidelityBundle\Entity\User',
'property' => 'select_label',
'multiple' => true
));
;
// Valida il numero di utenti selezionati
$builder->addEventListener(\Symfony\Component\Form\FormEvents::POST_BIND,
function($event) {
$form = $event->getForm();
$data = $event->getData();
if(!$data->users->isEmpty()) return;
$msg = 'Occorre specificare almeno un utente destinatario';
$form->get('users')->addError(new FormError($msg));
});
}
As of Symfony 2.1, you can use the Count constraint. If you are on 2.0, you can simply copy the constraint to your project and adapt its namespaces and its API (which was slightly changed between 2.0 and 2.1).
/**
* #Assert\Count(min = 1, minMessage = "Occorre specificare almeno un utente destinatario")
*/
private $users = new ArrayCollection();
Have you tried using Count constraint validator?
I suppose your code will look like this:
->add('users', 'entity', array(
'label' => 'Destinatari',
'class' => 'DL\FidelityBundle\Entity\User',
'property' => 'select_label',
'multiple' => true,
'constraints' => new Count(
array('min' => 1, 'minMessage' => "Please select at least one user')
),
));
Have a look at the validation component: http://symfony.com/doc/current/book/validation.html
You can write a callback constraint within the object you want to validate:
use Symfony\Component\Validator\ExecutionContext;
public function isUsersValid(ExecutionContext $context)
{
if ($this->users->isEmpty()) {
$propertyPath = $context->getPropertyPath() . '.users';
$context->setPropertyPath($propertyPath);
$context->addViolation('Occorre specificare almeno un utente destinatario', array(), null);
}
}
See the callback constraint page how to add this constraint to your entity (this depens if you are using annotations or yaml/xml).

Resources