validating dynamic forms in symfony2 returns "this value is not valid" - forms

I have 3 entities: Country, State and City with the following relationships, and i have another entity named Location which have all of theme for setting user location,
i have a form too, for getting user's location data. which should be generated dynamically, because the entities are related to each other, and for example if the state changed, the cities must be changed too.
i used the code below in my listener for doing this:
/**
* #param FormFactoryInterface $factory
* #param EntityManager $om
*/
public function __construct($factory, $om)
{
$this->factory = $factory;
$this->om = $om;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_BIND => 'preBind',
FormEvents::PRE_SET_DATA => 'preSetData',
);
}
/**
* #param event FormEvent
*/
public function preSetData(FormEvent $event)
{
$company = $event->getData();
if (null === $company)
{
return;
}
// company has country => edit action
if($company->getCountry()){
//retrieve location
$country = $company->getCountry();
$state = $company->getState();
$city = $company->getCity();
if(is_object($state) && is_object($city) && is_object($country))
{
$states = $country->getStates();
$cities = $state->getCities();
}
else
{
$cities = array();
$states = array();
}
}else{
// new location action
$states = array();
$cities = array();
}
$form = $event->getForm();
$this->customizeForm($form, $cities,$states);
}
public function preBind(FormEvent $event)
{
$dataForm = $event->getData();
$countryId = $dataForm['country'];
$stateId = $dataForm['state'];
$cityId = $dataForm['city'];
$country = $this->om
->getRepository('GMBundle:Country')
->find($countryId);
$state = $this->om
->getRepository('GMBundle:State')
->find($stateId);
$city = $this->om
->getRepository('GMBundle:City')
->find($cityId);
if(is_object($state) && is_object($city) && is_object($country))
{
$states = $country->getStates();
$cities = $state->getCities();
}
else
{
$cities = array();
$states = array();
}
$form = $event->getForm();
$this->customizeForm($form, $cities, $states);
}
protected function customizeForm($form, $cities, $states)
{
$form
->add($this->factory->createNamed('state', 'entity', null, array(
'choices' => $states,
'label' => 'state',
'required' => true,
'empty_value' => 'choose state',
'class' => 'GMBundle:State',
)))
->add($this->factory->createNamed('city', 'entity', null, array(
'choices' => $cities,
'label' => 'city',
'required' => true,
'empty_value' => 'choose city',
'class' => 'GMBundle:City',
)))
;
}
when i want to edit my form, i got the error This value is not valid.
it seems that symfony doesn't changed the data, for example if user had selected X as Stated and now wants to select Y for his state, symfony said the chosen city is not valid and must be selected from X's cities, which was his previous choice.
any idea how i can resolve it? or where i'm doing something wrong!?
-thanks

Related

Taxonomy list dependent on choice from another taxonomy list, drupal 8

I have a taxonomy option list make where I choose say Toyota.
I want the second taxonomy option list with the models of Toyota only (Eg. Corolla, hilux etc...).
When I choose Benz the second list will then contains C-Class, ML, etc...
I have created the entity vehicle from google examples on xampp localhost, windows 10.
In my vehicle form I'm able to populate the first list. But the second appears empty.
Here is my code. Please help:
public function buildForm(array $form, FormStateInterface $form_state, $params = NULL) {
$options = array();
$tax = "make";
$terms = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($tax, $parent = 0, $max_depth = NULL, $load_entities = FALSE);
foreach ($terms as $term) {
$options[] = $term->name;
}
$form['make'] = array(
'#type' => 'select',
'#title' => t('Make'),
'weight' => 0,
'#options' => $options,
'#ajax' => array(
'callback' => [$this, 'changeOptionsAjax'],
'wrapper' => 'model_wrapper',
),
);
$form['model'] = array(
'#type' => 'select',
'#title' => t('Model'),
'weight' => 1,
'#options' => $this->getOptions($form_state),
'#prefix' => '<div id="model_wrapper">',
'#suffix' => '</div>',
);
return $form;
}
public function getOptions(FormStateInterface $form_state) {
$options = array();
if ($form_state->getValue('make') == "Benz") {
$tax="benz";
}
elseif ($form_state->getValue('make') == "BMW") {
$tax="bmw";
}
elseif ($form_state->getValue('make') == "Toyota") {
$tax="toyota";
}
else {
$tax="title";
// title is just another taxonomy list I'm using as default if make is not found
}
$terms = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($tax, $parent = 0, $max_depth = NULL, $load_entities = FALSE);
foreach ($terms as $term) {
$options[] = $term->name;
}
return $options;
}
public function changeOptionsAjax(array &$form, FormStateInterface $form_state) {
return $form['model'];
}
Here I give you a working sample based on your example VehiculesForm.php:
I took the liberty to rename some variable for better readability.
<?php
namespace Drupal\example\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
/**
* VehiculesForm.
*/
class VehiculesForm extends FormBase {
/**
* The term Storage.
*
* #var \Drupal\taxonomy\TermStorageInterface
*/
protected $termStorage;
/**
* {#inheritdoc}
*/
public function __construct(EntityTypeManagerInterface $entity) {
$this->termStorage = $entity->getStorage('taxonomy_term');
}
/**
* {#inheritdoc}
*/
public static function create(ContainerInterface $container) {
// Instantiates this form class.
return new static(
// Load the service required to construct this class.
$container->get('entity_type.manager')
);
}
/**
* {#inheritdoc}
*/
public function getFormId() {
return 'vehicules_form';
}
/**
* {#inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $params = NULL) {
$brands = $this->termStorage->loadTree('make', 0, NULL, TRUE);
$options = [];
if ($brands) {
foreach ($brands as $brand) {
$options[$brand->getName()] = $brand->getName();
}
}
$form['brand'] = array(
'#type' => 'select',
'#title' => $this->t('brand'),
'#options' => $options,
'#ajax' => array(
'callback' => [$this, 'selectModelsAjax'],
'wrapper' => 'model_wrapper',
),
);
$form['model'] = array(
'#type' => 'select',
'#title' => $this->t('Model'),
'#options' => ['_none' => $this->t('- Select a brand before -')],
'#prefix' => '<div id="model_wrapper">',
'#suffix' => '</div>',
'#validated' => TRUE,
);
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Send'),
];
return $form;
}
/**
* {#inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
}
/**
* {#inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
/**
* Called via Ajax to populate the Model field according brand.
*
* #param array $form
* An associative array containing the structure of the form.
* #param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* #return array
* The form model field structure.
*/
public function selectModelsAjax(array &$form, FormStateInterface $form_state) {
$options = [];
$vocabulary = 'title';
switch ($form_state->getValue('brand')) {
case 'Benz':
$vocabulary = 'benz';
break;
case 'BMW':
$vocabulary = 'bmw';
break;
case 'Toyota':
$vocabulary = 'toyota';
break;
}
$models = $this->termStorage->loadTree($vocabulary, 0, NULL, TRUE);
if ($models) {
foreach ($models as $model) {
$options[$model->id()] = $model->getName();
}
}
$form['model']['#options'] = $options;
return $form['model'];
}
}
Also, I suggest you to make some improvments on you code such:
Don't use a switch but link your taxonomies with a reference fields.
Add validation to ensure security (check we don't spoof your field for example) !!
Don't use the brandname but the ID. Avoid $options[$brand->getName()] = $brand->getName(); and use something like $options[$brand->id()] = $brand->getName();.
Hope it will help you !

Symfony2 : Save image form field in session

I want to save the form data in session in case of an error occured then I can display valid form data in my form view.
I've got a file field for an image but I've got this error when I submit my form with an image : Serialization of 'Symfony\Component\HttpFoundation\File\UploadedFile' is not allowed
My Form Type :
class ContenuType extends AbstractType
{
private $session;
public function __construct($session)
{
$this->session = $session;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$session = $this->session;
$formSession = $session->get('dataCreateContenu');
if (!$formSession) {
$formSession = new Contenu();
}
$builder
->add('description', 'textarea', array(
'label' => 'Description',
'data' => $formSession->getDescription(),
'attr' => array('maxlength' => 560),
)
)
->add('file', 'file', array(
'label' => 'Image',
'data' => $formSession->getImage(),
'required' => false,
)
)
;
}
}
My Form Controller :
/**
* New Contenu
*
* #Route("/", name="contenu_create")
* #Method("POST")
* #Template("MyOwnBundle:Contenu:new.html.twig")
*/
public function createAction(Request $request)
{
$entity = new Contenu();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
$session = $this->get('session');
$session->set('dataCreateContenu', $form->getData());
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$formAll = $form->all();
foreach ($formAll as $formField) {
if(empty($formField->getData())) {
$this->get('session')->getFlashBag()->add('error', 'Field « '.$formField->getConfig()->getOption("label").' » is empty.');
return $this->redirect($this->generateUrl('contenu_new'));
}
}
$image = $form->get('file')->getData();
if ($image) {
$entity->upload();// Upload
}
$em->persist($entity);
$em->flush();
$session->clear();
return $this->redirect($this->generateUrl('contenu_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
What can I do ?

ManyToMany, duplicate entry, table empty

Soo :
I have my two tables/entities texts and groups which are linked by a manytomany relationship.
I have a form where the users fill checkboxes to assign a text into one or several groups.
I have a problem though it's getting me this error
An exception occurred while executing 'INSERT INTO texte_groupe
(texte_id, groupe_id) VALUES (?, ?)' with params [2, 1]:
Here is the core of the problem ( i think?)
public function AcceptMediaAction($id) {
$em = $this->getDoctrine()->getManager();
$texte = new Texte();
$securityContext = $this->container->get('security.context');
$texte = $em->getRepository('EVeilleurDefuntBundle:Texte')->find($id);
$form = $this->createForm( new ChooseGroupeType($securityContext), $texte );
$request = $this->get('request');
$form->bind($request);
if ($request->getMethod() == 'POST') {
$groupes = $texte->getGroupes();
$statut = $texte->getStatut();
foreach ($groupes->toArray() as $groupe)
{
$texte->addGroupe($groupe);
}
$em->persist($groupe);
$em->flush();
return $this->redirect($this->generateUrl('e_veilleur_defunt_gerer_medias',
Groupe.php
/**
* #ORM\ManyToMany(targetEntity="EVeilleur\DefuntBundle\Entity\Texte",mappedBy="groupes")
*/
private $textes;
Texte.php
/**
* #ORM\ManyToMany(targetEntity="EVeilleur\DefuntBundle\Entity\Groupe",inversedBy="textes")
*/
private $groupes;
formtype
public function buildForm(FormBuilderInterface $builder, array
$options)
{
// $builder->add('statut','choice', array('choices'=> array('nonValide'=>'non Valide',
// 'Valilde'=>'suchValidation'),
// 'required'=>'true'
// )
// ) ; // , array("attr" => array("multiple" => "multiple", ))
$user = $this->securityContext->getToken()->getUser();
if (!$user) {
throw new \LogicException(
'This cannot be used without an authenticated user!'
);
}
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($user) {
$form = $event->getForm();
$formOptions = array(
'multiple' => true, //several choices
'expanded' => true, // activate checkbox instead of list
'by_reference' => false,
'required' => true|false,
'class' => 'EVeilleur\DefuntBundle\Entity\Groupe',
'query_builder' => function (EntityRepository $er) use ($user) {
// build a custom query
return $er->createQueryBuilder('u')->add('select', 'u')
->add('from', 'EVeilleurDefuntBundle:Groupe u');
// ->add('where', 'u.id = ?1')
// ->add('orderBy', 'u.name ASC');
},
);
// create the field, = $builder->add()
// field name, field type, data, options
$form->add('groupes', 'entity', $formOptions);
}
);
}
Thanks
Try with:
foreach ($groupes->toArray() as $groupe) {
if (!in_array($groupe, $texte->getGroupe()->toArray()) {
$texte->addGroupe($groupe);
}
}

Symfony2 - Form validation with query builder - value never valid

I have two entities "meeting" and "Slot" with a one2many relation.
I have a form to update a meeting (just to change the time slot), i want to display only free slot so i've made a query_ builder. It works for the creation, but for the update, there is always a validation error (this value is not valid).
This is my code :
controller
public function editAction($id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('NfidBusinessMeetingBundle:Meeting')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Meeting entity.');
}
$editForm = $this->createForm(new UpdateMeetingType($entity->getUserA(),$entity->getUserB()), $entity);
if ($this->getRequest()->isMethod('POST')) {
$editForm->bind($this->getRequest());
// var_dump($editForm->getData() );
if ($editForm->isValid()) {
$entity->setDeleted(0);
$entity->setPending(1);
$entity->setValidated(0);
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('meeting_front'));
}
}
return $this->render('NfidBusinessMeetingBundle:MeetingFront:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
));
}
form builder
public function __construct($userA = null, $userB = null)
{
$this->userA = $userA;
$this->userB = $userB;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// die(var_dump($options));
$users[] = $this->userA;
$users[] = $this->userB;
$builder
->add('slot', 'entity',
array(
'class' => 'NfidBusinessMeetingBundle:Slot',
'query_builder' => function(SlotRepository $cr) use ($users) {
return $cr->findAllFreeSlotByUsers($users);
}
)
);
}
Repository
public function findAllFreeSlotByUsers(array $users){
$subquery = $this->createQueryBuilder('query1');
$subquery->select('s')
->from('NfidBusinessMeetingBundle:Meeting', 'm')
->leftJoin('m.slot', 's')
->leftJoin('m.userA', 'uA')
->leftJoin('m.userB', 'uB')
->where('m.userA = :userA')
->orWhere('m.userA = :userB')
->orWhere('m.userB = :userA')
->orWhere('m.userB = :userB')
->andWhere('m.validated = 1');
$query = $this->createQueryBuilder('query');
$query->select('s2')
->from('NfidBusinessMeetingBundle:Slot', 's2')
->where($query->expr()->notIn('s2.id', $subquery->getDQL()))
->setParameters(array('userA' => $users[0]->getId(), 'userB' => $users[1]->getId()));
return $query;
}
Someone can help me ?
EDIT : If made this it works
public function buildForm(FormBuilderInterface $builder, array $options)
{
// die(var_dump($options));
$users[] = $this->userA;
$users[] = $this->userB;
$builder
->add('slot');
}
=========================== EDIT ====================================
I've try this
public function findAllFreeSlotByUsers(array $users){
$subquery = $this->createQueryBuilder('query1');
$subquery->select('s')
->from('NfidBusinessMeetingBundle:Meeting', 'm')
->leftJoin('m.slot', 's')
->leftJoin('m.userA', 'uA')
->leftJoin('m.userB', 'uB')
->where('m.userA = :userA')
->orWhere('m.userA = :userB')
->orWhere('m.userB = :userA')
->orWhere('m.userB = :userB')
->andWhere('m.validated = 1');
$query = $this->createQueryBuilder('query');
$query->select('s2')
->from('NfidBusinessMeetingBundle:Slot', 's2')
->where(
$query->expr()->orx(
$query->expr()->notIn('s2.id', $subquery->getDQL()),
$query->expr()->eq('s2.id', $users[2]->getId())
)
)
->setParameters(array('userA' => $users[0]->getId(), 'userB' => $users[1]->getId()));
return $query;
}
and
public function buildForm(FormBuilderInterface $builder, array $options)
{
// die(var_dump($options));
$users[] = $this->userA;
$users[] = $this->userB;
$users[] = $builder->getData();
if ($this->userConnected == User::TYPE_RESSOURCE ) {
$builder
->add('slot', 'entity',
array(
'class' => 'NfidBusinessMeetingBundle:Slot',
'query_builder' => function(SlotRepository $cr) use ($users) {
return $cr->findAllFreeSlotByUsers($users);
}
)
)
And it's always the same error
string 'slot:
ERROR: Cette valeur n'est pas valide.
UPDATED:
Insert this code and output the result:
editAction:
var_dump($entity->getSlot()->getId());
$editForm = $this->createForm(new UpdateMeetingType($entity->getUserA(),$entity->getUserB()), $entity);
buildForm:
'query_builder' => function(SlotRepository $cr) use ($users) {
$query = $cr->findAllFreeSlotByUsers($users);
$results = $query->getQuery()->getResult();
foreach ($results as $result) {
var_dump($result->getId());
}
die;
}
Try this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$data = $builder->getData();
$users[] = $this->userA;
$users[] = $this->userB;
$builder
->add('slot', 'entity',
array(
'class' => 'NfidBusinessMeetingBundle:Slot',
'query_builder' => function(SlotRepository $cr) use ($users) {
return $cr->findAllFreeSlotByUsers($users, $data->getSlot());
}
)
);
}
SlotRepository: slot_id is the second parameters you pass from the entity type
$query = $this->createQueryBuilder('query');
$query->select('s2')
->from('NfidBusinessMeetingBundle:Slot', 's2')
->where(
$query->expr()->orx(
$query->expr()->notIn('s2.id', $subquery->getDQL()),
$query->expr()->eq('s2.id', $slot_id)
)
)
->setParameters(array('userA' => $users[0]->getId(), 'userB' => users[1]->getId()));
Of course, you may need to make some tests if the slot is null, etc.
What may be your issue:
Let's assume NEW meeting. This a the list of free slots:
Slot 1,
Slot 2,
Slot 3,
You select Slot 2. At edit, because it is used this is the list of free slots:
Slot 1,
Slot 3
Since Slot 2 is not on the list you get invalid choice.

Symfony2: How to add form constraint for a field in bind PRE_SET_DATA depending on the data

I have a form in Symfony 2 with basically two fields:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('contactType', 'select', array( 'choices' => $contactTypes ))
->add('value', 'text');
}
Then I added an EventSubscriber that listens to the FormEvents::PRE_SET_DATA event. What I actually want to do, is to change the way of validation depending on the value of contactType (numeric values from 1 to 4, which stand for email, mobile, fixed line and fax).
I followed this tutorial http://symfony.com/doc/current/cookbook/form/dynamic_form_generation.html
but I can't figure out, how to add a constraint to the value field.
Can anyone help me? Thanks a lot in advance.
Instead of adding validation constraints dynamically in event subscriber (not sure if this is even possible), you can set groups to field's validation constraints and determine validation groups based on submitted data.
A function to create the form from the controller :
<?php
// ...
class DefaultController extends Controller
{
/**
*
* #param \Clicproxy\DeltadocCabBundle\Entity\Mark $mark
* #param \Clicproxy\DeltadocCabBundle\Entity\Tag $tag
* #return Form
*/
private function createTagForm(Mark $mark, Tag $tag)
{
$form = $this->createForm(new TagType(), $tag, array(
'action' => $this->generateUrl('tag_new', array('slug' => $this->slugify($mark->getName()))),
'method' => 'POST',
));
foreach ($mark->getFields() as $field)
{
$form->add($this->slugify($field->getName()), $field->getFormType(), $field->getOptions());
}
$form->add('submit', 'submit', array('label' => 'crud.default.save'));
return $form;
}
// ...
The code in the entity (type, constraints, ...) :
<?php
// ...
/**
* Field
*
* #ORM\Table()
* #ORM\Entity
* #UniqueEntity({"name", "mark"})
*/
class Field
{
// ...
/**
*
* #return array
*/
public function getOptions()
{
$options = array('label' => $this->getName(), 'mapped' => FALSE);
$options['required'] = $this->getType() != 'checkbox';
if ('date' == $this->getType())
{
$options['attr']['class'] = 'datepicker'; // 'input-group date datepicker';
$options['attr']['data-date-format'] = 'dd/mm/yyyy';
$options['attr']['data-date-autoclose'] = true;
}
if ('choice' == $this->getType())
{
$choices = array();
foreach ($this->getChoices() as $choice)
{
$choices[$choice->getValue()] = $choice->getName();
}
asort($choices);
$options['choices'] = $choices;
}
$options['constraints'] = $this->getValidationConstraint();
return $options;
}
public function getValidationConstraint ()
{
$validation_constraint = array();
if ('number' == $this->getType()) {
if (0 < $this->getMaximum()) {
$validation_constraint[] = new LessThanOrEqual (array(
'message' => 'entity.field.number.lessthanorequal', // {{ compared_value }}
'value' => $this->getMaximum()
));
}
if (0 < $this->getMinimum()) {
$validation_constraint[] = new GreaterThanOrEqual(array(
'message' => 'entity.field.number.greaterthanorequal', // {{ compared_value }}
'value' => $this->getMinimum()
));
}
} elseif ('text' == $this->getType ()) {
if (0 < $this->getMaximum()) {
$validation_constraint[] = new Length(array(
'min' => $this->getMinimum() > 0 ? $this->getMinimum() : 0,
'max' => $this->getMaximum() > 0 ? $this->getMaximum() : 0,
'minMessage' => 'entity.field.text.minMessage', // {{ limit }}
'maxMessage' => 'entity.field.text.maxMessage',
'exactMessage' => 'entity.field.text.exactMessage',
));
}
} elseif ('date' == $this->getType()) {
}
return $validation_constraint;
}
// ...
All this code work actually.
With this you have a solution to generate a form on the fly with constraints.