Symfony2 - NotBlank constraint not working on EntityType - forms

I have two EntityType fields in my form and both has NotBlank constraint assigned to them.
Now, I have this issue that NotBlank constraint is not working only on one field with multiple => true set on.
$builder
->add('preferredCountries', EntityType::class, array(
'required' => false,
'class' => 'IndexBundle:Country',
'property' => 'name',
'empty_value' => 'Choose',
'multiple' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.name != :name')
->orderBy('c.name', 'ASC')
->setParameter('name', 'Other');
},
'constraints' => array(
new NotBlank(array(
'message' => 'blank!!!',
)),
)
))
->add('internshipProgram', EntityType::class, array(
'required' => false,
'class' => 'IndexBundle:InternshipProgram',
'property' => 'name',
'empty_value' => 'Choose',
'constraints' => array(
new NotBlank(array(
'message' => 'blank!!!',
)),
)
))
In this case when I submit empty values, field internshipProgram get an error, and prefferedCountries not.
Form display:
<div class="form-group col-xs-12">
{{ form_label(current_internship_form.preferredCountries, 'Preferred countries', { 'label_attr': {'class': 'label-text'} }) }}
{{ form_widget(current_internship_form.preferredCountries) }}
<span class="error text-danger small">{{ form_errors(current_internship_form.preferredCountries) }}</span>
</div>
<div class="form-group col-xs-12">
{{ form_label(current_internship_form.internshipProgram, 'What type of training agreement will you have?', { 'label_attr': {'class': 'label-text'} }) }}
{{ form_widget(current_internship_form.internshipProgram, { 'id': 'internship_program', 'attr': {'class': 'form-control '}}) }}
<span class="error text-danger small">{{ form_errors(current_internship_form.internshipProgram) }}</span>
</div>
Is there a mistake in my code or is it somehow related to multiple choice selection? Has anyone had similar issue and know how to solve it?

You can't use NotBlank constraint on EntityType with multiple set to true. As the array will never be null. You should try using the count constraint like this:
$builder
->add('preferredCountries', EntityType::class, array(
'required' => false,
'class' => 'IndexBundle:Country',
'property' => 'name',
'empty_value' => 'Choose',
'multiple' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.name != :name')
->orderBy('c.name', 'ASC')
->setParameter('name', 'Other');
},
'constraints' => array(
new Count(array(
'min' => 1,
'minMessage' => "Should not be blank"
))
)
))
...

Also you can specify at Entity level,
/**
* #Count(min = 1, minMessage = "At least one branch must be selected")
*/
protected $multiCheckBox;

Related

Symfony give class to option entitytype element

i have an entity type with field (nome, id,id_Categoria,id_tipo)
id_tipo has value 1,2,or 3
$builder->add('idTipologiaEsame', EntityType::class, array(
'placeholder' => 'tipologia esame',
'label' => false,
'mapped' => false,
'required' => false,
'class' => 'AppBundle:Nome_esame',
'choice_label' => 'nome',
'group_by' => 'idCategoriaEsame.tipo',
))
My goal is give a class to option value in select if id_tipo is 1 or 2.
like this:
<select>
<option class="ruminanti">val 1</option> ->id_tipo=1
<option class="pippo">val 2</option> ->id_tipo=2
<option class="ruminanti">val 3</option> ->id_tipo=1
</select>
it is possible??
thank's to Ramazan Apaydın i add this
$builder->add('idTipologiaEsame', EntityType::class, array(
'placeholder' => 'tipologia esame',
'label' => false,
'mapped' => false,
'required' => false,
'class' => 'AppBundle:Nome_esame',
'choice_label' => 'nome',
'group_by' => 'idCategoriaEsame.tipo',
'choice_attr' => function($val, $key, $index) {
// adds a class like attending_yes, attending_no, etc
if($index){ ----> i want id_tipo =1
return ['class' => '.ruminati';}else{
return ['class' => '.suini';
}
},
but i want add condition id_tipo=1
EntityType is a ChoiceType element. An example is available in the Symfony documentation.
https://symfony.com/doc/current/reference/forms/types/choice.html#choice-attr

Form Collection with choice field

I have a form to modify an entity which has some children entities. I use a form collection to do so. When I edit this entity, the children collection should appear but it doesn't. The children collection is composed of 2 choice fields and 1 integer field. The integer field is well rendered with the right data but choice fields ask to select an option whereas it should show Matiere and Colle of children entities.
In the code, Colle entity is a child of Matiere Entity. I'm using MaterializeCSS as a framework.
Here's my code :
Child Form :
$builder
->add('matiere', EntityType::class, [
'class' => 'PACESColleBundle:Matiere',
'attr' => ['class'=> 'matiere'],
'choice_label' => 'name',
'label' => false,
'required' => false,
'placeholder' => 'Choisissez une matière',
'mapped' => false])
->add('colleEnfant', EntityType::class, [
'class' => 'PACESColleBundle:Colle',
'attr' => ['class' => 'colles'],
'choice_label' => 'nom',
'label' => false,
'group_by' => 'matiere',
'required' => true,
'placeholder' => 'choose.colle'])
->add('ordre', IntegerType::class,[
'attr'=>['class'=>'ordre'],
'required' => true,
'label' => false]);
Parent Form :
$builder->add('nom', TextType::class,['label' => 'Nom de la colle'])
->add('collesEnfants', CollectionType::class,
['label' => false,
'entry_type' => SousColleFormType::class,
'required' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false]);
View :
<table id="tableau" class="creneaux"
data-prototype="{{ form_widget(form.collesEnfants.vars.prototype)|e }}">
<thead>
<tr>
<th>Matière</th>
<th>Colle</th>
<th>Ordre</th>
<th>Supprimer</th>
</tr>
</thead>
<tbody>
{% for colle in form.collesEnfants %}
<tr>
<td>{{ form_row(colle.matiere) }}</td>
<td>{{ form_row(colle.colleEnfant) }}</td>
<td>{{ form_row(colle.ordre) }}</td>
<td><i class="material-icons">delete</i></td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
$(document).ready(function() {
$('.matiere').material_select();
$('.colles').material_select()
});
</script>
You could add a query in your entity type and check if the field 'nom' exist in your entity.
$builder->add('colleEnfant', EntityType::class, [
'class' => 'PACESColleBundle:Colle',
'attr' => ['class' => 'colles'],
'choice_label' => 'nom',
'label' => false,
'required' => true,
'placeholder' => 'choose.colle',
'query_builder' => function(ColleRepository $repository) {
$qb = $repository->createQueryBuilder('c');
// the function returns a QueryBuilder object
return $qb->groupBy('c.matiere')->orderBy('c.nom', 'ASC');
}
])
After you need to add an event listener who detect the select modification.
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$matiere = $this->em->getRepository('PACESColleBundle:Matiere')->find($data['matiere']);
$form = $event->getForm();
$form->add('colleEnfant', EntityType::class, [
'class' => 'PACESColleBundle:Colle',
'attr' => ['class' => 'colles'],
'choice_label' => 'nom',
'label' => false,
'required' => true,
'placeholder' => 'choose.colle',
'query_builder' => function(ColleRepository $repository) use ($matiere) {
$qb = $repository->createQueryBuilder('c');
// the function returns a QueryBuilder object
return $qb
->where('c.matiere = :matiere')
->setParameter('matiere', $matiere)
->orderBy('c.nom', 'ASC');
}
]);
});
I hope this going to help you.
I ended up doing it with JQuery.
Script :
$(document).ready(function() {
{% for colleEnfant in collesEnfants %}
$('#paces_colle_colle_ajoutsupercolle_collesEnfants_{{ loop.index0 }}_matiere option[value="{{ colleEnfant.matiere }}"]').prop('selected', true);
$('#paces_colle_colle_ajoutsupercolle_collesEnfants_{{ loop.index0 }}_colleEnfant option[value="{{ colleEnfant.colle }}"]').prop('selected', true);
{% endfor %}
$('.matiere').material_select();
$('.colles').material_select();
}
Controller :
$collesEnfantsForm = [];
foreach ($colle->getCollesEnfants() as $colleEnfant) {
$collesEnfantsForm[] = ['matiere' => $colleEnfant->getMatiere()->getId(), 'colle' => $colleEnfant->getId()];
}
it is because ArrayChoiceList::getValuesForChoices() does comparison for objects by reference. As result if you have not same instance of object in data and in list of choices that it won't be selected. Solution is to use "choice_value" options for ChoiceType field. eg.
$builder->add('type', ChoiceType::class, [
'choices' => FeeType::getTypes(),
'choice_label' => 'name',
'required' => true,
'choice_value' => static function (?FeeType $feeType) {
return $feeType ? $feeType->getId() : '';
}
])

How to customize EntityType option text?

I would like to display selection with options from DB. In this case, I am using EntityType with query_builder:
$builder
->add('internships', EntityType::class, array(
'class' => 'IndexBundle\Entity\Internship',
'property' => 'id',
'empty_value' => 'Choose',
'query_builder' => function (EntityRepository $er) use ($trainee) {
return $er->createQueryBuilder('i')
->where('i.trainee = :trainee')
->andWhere('i.state = :state')
->setParameter('trainee', $trainee)
->setParameter('state', 'unverified');
},
'constraints' => array(
new NotBlank(array(
'message' => 'choice.not.blank'
))
)
))
Now all is fine. I get select element with necessary options within with text of id value.
<select>
<option value="id">id</option>
...
</select>
How do I customize it?
For example I would like it to be combination of id and type table columns:
<select>
<option value="id">#id (type)</option>
...
</select>
You can use the choice_label option to customize your options.
You can either pass a function to retrieve the text you want, or you can add a getter to your entity if you reuse it at another place.
With a function:
$builder
->add('internships', EntityType::class, array(
'class' => 'IndexBundle\Entity\Internship',
'property' => 'id',
'empty_value' => 'Choose',
'query_builder' => function (EntityRepository $er) use ($trainee) {
return $er->createQueryBuilder('i')
->where('i.trainee = :trainee')
->andWhere('i.state = :state')
->setParameter('trainee', $trainee)
->setParameter('state', 'unverified');
},
'choice_label' => function ($internship) {
return '#'.$internship->getId().' ('.$internship->getType().')';
},
'constraints' => array(
new NotBlank(array(
'message' => 'choice.not.blank'
))
)
))
With a getter:
$builder
->add('internships', EntityType::class, array(
'class' => 'IndexBundle\Entity\Internship',
'property' => 'id',
'empty_value' => 'Choose',
'query_builder' => function (EntityRepository $er) use ($trainee) {
return $er->createQueryBuilder('i')
->where('i.trainee = :trainee')
->andWhere('i.state = :state')
->setParameter('trainee', $trainee)
->setParameter('state', 'unverified');
},
'choice_label' => 'idAndType',
'constraints' => array(
new NotBlank(array(
'message' => 'choice.not.blank'
))
)
))
Internship.php:
Class Internship
{
//...
public function getIdAndType()
{
return '#'.$this->id.' ('.$this->type.')';
}
}
Note:
For older Symfony versions (<= 2.6), this option was named property and use something supported by the PropertyAccessor component, so you can't use a function, only a getter for it.

Collection of form embedded is showing the whole form insetead of a single field

I'm following the official documentation example but I can't make it to work properly.
Summarizing, I have material and items_budget table. Both of them are related as oneToMany and manyToOne, by the id and material fields:
Material.orm.yml:
oneToMany:
itemsBudget:
targetEntity: ItemsBudget
mappedBy: material
ItemsBudget.orm.yml:
material:
targetEntity: Material
inversedBy: itemsBudget
joinColumn:
name: material
referencedColumnName: id
Here, the ItemsBudgetType where I set the material field as a collection:
$builder->add('budget', 'entity', array(
'class' => 'PanelBundle:Budget',
'attr' => array(
'class' => 'form-control',
),
))
->add('material', 'collection', array(
'type' => new MaterialType(),
'allow_add' => true
))
->add('quantity', 'number', array(
'attr' => array(
'class' => 'form-control',
),
))
->add('price', 'money', array(
'attr' => array(
'class' => 'form-control',
),
));
Just for information, the MaterialType:
$builder->add('name', 'text', array(
'attr' => array(
'class' => 'form-control',
),
))
->add('description', 'text', array(
'attr' => array(
'class' => 'form-control',
),
))
->add('current_quantity', 'text', array(
'attr' => array(
'class' => 'form-control',
'required' => false
),
))
->add('price', 'money', array(
'attr' => array(
'class' => 'form-control',
),
));
Here the index.html.twig of my ItemsBudget view:
<strong>Materiais</strong>
<div class="form-group">
<ul class="materials" data-prototype="{{ form_widget(form.material.vars.prototype)|e }}">
{% for material in form.material %}
<li>{{ form_widget(form.material.vars.prototype.name) }}</li>
{% endfor %}
</ul>
</div>
In the view, I have tried also as it is in the example: {{ form_row(material.name) }}, but it still shows the whole Material form.
And where I'm calling them, in the ItemsBudgetController:
public function addAction(Request $request)
{
$form = $this->createForm(new ItemsBudgetType(), new ItemsBudget());
$manager = $this->getDoctrine()->getManager();
if ($request->getMethod() == 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$ItemsBudgetEntity = $form->getData();
$manager->persist($ItemsBudgetEntity);
$manager->flush();
$BudgetEntity = $form->get('budget')->getData();
$BudgetEntity->addItemsBudget($ItemsBudgetEntity);
$manager->persist($BudgetEntity);
$manager->flush();
$this->addFlash('success', 'Materiais para o orçamento adicionados');
return $this->redirect($this->generateUrl('panel_budgets'));
}
}
return $this->render('PanelBundle:ItemsBudget:index.html.twig', array(
'form' => $form->createView(),
'budgets' => $manager->getRepository('PanelBundle:Budget')->findAll()
));
}
The JavaScript is the same from the example too:
function addMaterial($collectionHolder, $newLinkLi) {
var prototype = $collectionHolder.data('prototype');
var index = $collectionHolder.data('index');
var newForm = prototype.replace(/__name__/g, index);
$collectionHolder.data('index', index + 1);
var $newFormLi = $('<li></li>').append(newForm);
$newLinkLi.before($newFormLi);
}
var $collectionHolder;
var addMaterialLink = $('Mais');
var $newLinkLi = $('<li></li>').append(addMaterialLink);
jQuery(document).ready(function () {
$collectionHolder = $('ul.materials');
$collectionHolder.append($newLinkLi);
$collectionHolder.data('index', $collectionHolder.find(':input').length);
addMaterialLink.on('click', function (e) {
e.preventDefault();
addMaterial($collectionHolder, $newLinkLi);
});
});
This is the issue: instead of showing only the name field from Material form, it is showing the whole form every time I click the "Mais". Am I missing something?
The javascript takes the content of the data-prototype attribute and appends it to the html of the form. The attribute in your template contains {{ form_widget(form.material.vars.prototype)|e }} which is the html prototype of the form.
Try:
<ul class="materials" data-prototype="{{ form_widget(form.material.vars.prototype.name) }}">

Symfony form output

I render a form in my application:
{{ form_widget(form.weeks) }}
The options come from a entity query builder.
I want to check in view if($entitiy->getBooked()){ echo disabled } on each option. (so they can not select booked options).
But how can I do that?
if I do that manual the {{ form_rest(form) }} will put a new select on the bottom.
formBuilder:
$builder->add( 'weeks', 'entity', array(
'class' => 'Mitch\NameBundle\Entity\CaravanRow',
'property' => 'line',
'query_builder' => function(EntityRepository $er ) use ( $caravan ) {
return $er->createQueryBuilder('w')
->orderBy('w.dateFrom', 'ASC')
->where('w.caravan = :caravan' )
->andWhere('w.visible = 1')
->setParameter( 'caravan', $caravan );
},
'attr' => array(
'multiple' => true,
'size' => 5,
'style' => 'width: 415px;'
),
));