Symfony3 Dynamically Modifies Forms Using Events

The problem is that nothing is loaded in the municipality field, it goes undefined. In AJAX code, I understand the meaning of the province well. But in the addMunicipioField.php class does not take the value of $ province, it is always nul

enter image description here

I am trying to make a registration form where part of the usual fields ( name, nick, password, ... ) I also add two dependent fields Municipality and Province .

Codec Controller:

 class UserController extends Controller { private $session; public function __construct() { $this->session = new Session(); } public function registerAction(Request $request) { if (is_object($this->getUser())) { return $this->redirect('home'); } $user = new DbUsuario(); $form = $this->createForm(RegistreUserType::class, $user); $form->handleRequest($request); if ($form->isSubmitted()) { if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); $query = $em->createQuery('SELECT u FROM BackendBundle:DbUsuario u WHERE u.email = :email OR u.nick = :nick') ->setParameter('email', $form->get("email")->getData()) ->setParameter('nick', $form->get("nick")->getData()); $user_isset = $query->getResult(); if (count($user_isset) == 0) { $factory = $this->get("security.encoder_factory"); $encoder = $factory->getEncoder($user); $password = $encoder->encodePassword($form->get("password")->getData(), $user->getSalt()); $user->setPassword($password); $user->setRole("ROLE_USER"); $user->setImagen(null); $em->persist($user); $flush = $em->flush(); if ($flush == null) { $status = "Te has registrado correctamente"; $this->session->getFlashBag()->add("status", $status); return $this->redirect("login"); } else { $status = "No te has registrado correctamente"; } } else { $status = "Usuario ya esta registrado."; } } else { $status = "No te has registrado correctamente."; } $this->session->getFlashBag()->add("status", $status); } return $this->render('AppBundle:User:register.html.twig', array( "form" => $form->createView() # Genera el html del formulario. )); } 

The entity that creates the form is DbUsuario , which has an idMunicipio field.

 /** @var \BackendBundle\Entity\DbMunicipios */ private $idMunicipio; /** * Set idMunicipio * @param \BackendBundle\Entity\DbMunicipio $idMunicipio * @return DbUsuario */ public function setIdMunicipio(\BackendBundle\Entity\DbMunicipio $idMunicipio = null) { $this->idMunicipio = $idMunicipio; return $this; } /** * Get idMunicipio * @return \BackendBundle\Entity\DbMunicipio */ public function getIdMunicipio() { return $this->idMunicipio; } 

Then the Entity Of DbMunicipio , which connects to the 'province' with

 /** @var \BackendBundle\Entity\DbProvincia */ private $provincia; /**@param \BackendBundle\Entity\DbProvincia $provincia * @return DbMunicipio */ public function setProvincia(\BackendBundle\Entity\DbProvincia $provincia = null){ $this->provincia = $provincia; return $this; } // And implement this function. public function __toString(){ return $this->getMunicipio(); } /**@return \BackendBundle\Entity\DbProvincia */ public function getProvincia(){ return $this->provincia; } 

And the DbProvincia Entity , which has only fields ( id (integer), slug (String) and province (String) ).

I define the form as follows:

 namespace AppBundle\Form; use .... class RegistreUserType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $factory = $builder->getFormFactory(); $builder->add('nombre', TextType::class, array('label' => 'Nombre', 'required' => 'required', 'attr' => array('class' => 'form-nombre form-control') )); $builder->add('apellido', TextType::class, array('label' => 'Apellido', 'required' => 'required', 'attr' => array('class' => 'form-apellido form-control') )); $builder->add('nick', TextType::class, array('label' => 'Nick', 'required' => 'required', 'attr' => array('class' => 'form-nick form-control nick-input') )); $provinSubscriber = new AddProvinciaField($factory); $builder->addEventSubscriber($provinSubscriber); $muniSubscriber = new AddMunicipioField($factory); $builder->addEventSubscriber($muniSubscriber); $builder->add('email', EmailType::class, array('label' => 'Correo electrónico', 'required' => 'required', 'attr' => array('class' => 'form-email form-control') )); $builder->add('password', PasswordType::class, array('label' => 'Password', 'required' => 'required', 'attr' => array('class' => 'form-password form-control') )); $builder->add('Registrarse', SubmitType::class, array("attr" => array("class" => "form-submit btn btn-success"))); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'data_class' => 'BackendBundle\Entity\DbUsuario' )); } public function getBlockPrefix() { return 'backendbundle_dbusuario'; } } 

I define in AppBundle \ Form \ eventListener \ AddProvinciaField the classes called on the form:

 namespace AppBundle\Form\EventListener; use .... use BackendBundle\Entity\DbProvincia; class AddProvinciaField implements EventSubscriberInterface { private $factory; public function __construct(FormFactoryInterface $factory) { $this->factory = $factory; } public static function getSubscribedEvents() { return array( FormEvents::PRE_SET_DATA => 'preSetData', FormEvents::PRE_SUBMIT => 'preSubmit' ); } private function addProvinciaForm($form, $provincia) { $form -> add('provincia', EntityType::class, array( 'class' => 'BackendBundle:DbProvincia', 'label' => 'Provincia', 'placeholder' => '_ Elegir _', 'auto_initialize' => false, 'mapped' => false, 'attr'=> array('class' => 'form-provincia form-control provincia-input'), 'query_builder' => function (EntityRepository $repository) { $qb = $repository->createQueryBuilder('provincia'); return $qb; } )); } public function preSetData(FormEvent $event){ $data = $event->getData(); $form = $event->getForm(); if (null === $data) {return;} $provincia = ($data->getIdMunicipio()) ? $data->getIdMunicipio()->getProvincia() : null ; $this->addProvinciaForm($form, $provincia); } public function preSubmit(FormEvent $event) { $data = $event->getData(); $form = $event->getForm(); if (null === $data) { return;} $provincia = array_key_exists('provincia-input', $data) ? $data['provincia-input'] : null; $this->addProvinciaForm($form, $provincia); } } 

And later I define AddMunicipioField .php:

 namespace AppBundle\Form\EventListener; use .... use BackendBundle\Entity\DbProvincia; class AddMunicipioField implements EventSubscriberInterface { private $factory; public function _construct(FormFactoryInterface $factory) { $this->factory = $factory; } public static function getSubscribedEvents() { return array( FormEvents::PRE_SET_DATA => 'preSetData', FormEvents::PRE_SUBMIT => 'preSubmit' ); } private function addMunicipioForm($form, $provincia) { $form->add('idMunicipio', EntityType::class, array( 'class' => 'BackendBundle:DbMunicipio', 'label' => 'Municipio', 'placeholder' => '_ Elegir _', 'auto_initialize' => false, 'attr'=> array('class' => 'form-municipio form-control municipio-input'), 'query_builder' => function (EntityRepository $repository) use ($provincia) { $qb = $repository->createQueryBuilder('idMunicipio') ->innerJoin('idMunicipio.provincia', 'provincia'); if ($provincia instanceof DbProvincia) { $qb->where('idMunicipio.provincia = :provincia') ->setParameter('provincia', $provincia); } elseif (is_numeric($provincia)) { $qb->where('provincia.id = :provincia') ->setParameter('provincia', $provincia); } else { $qb->where('provincia.provincia = :provincia') ->setParameter('provincia', null); } return $qb; } )); } public function preSetData(FormEvent $event){ $data = $event->getData(); $form = $event->getForm(); if (null === $data) { return; } $provincia = ($data->getIdMunicipio()) ? $data->getIdMunicipio()->getProvincia() : null ; $this->addMunicipioForm($form, $provincia); } public function preSubmit(FormEvent $event){ $data = $event->getData(); $form = $event->getForm(); if (null === $data) { return; } $provincia = array_key_exists('provincia_input', $data) ? $data['provincia_input'] : null; $this->addMunicipioForm($form, $provincia); } 

}

And finally, the AJAX request:

 $(document).ready(function(){ var $form = $(this).closest('form'); $(".provincia-input").change(function(){ var data = { idMunicipio: $(this).val() }; $.ajax({ type: 'POST', url: $form.attr('action'), data: data, success: function(data) { for (var i=0, total = data.length; i < total; i++) { $('.municipio-input').append('<option value="' + data[i].id + '">' + data[i].municipio + '</option>'); } } }); }); }); 

I added var_dump and alert () to the code. This is the output method.

In this case, the value of the region is always null .

  addMunicipioField.php public function preSetData(FormEvent $event){ $data = $event->getData(); $form = $event->getForm(); if (null === $data) { return; } $provincia = ($data->getIdMunicipio()) ? $data->getIdMunicipio()->getProvincia() : null ; var_dump('presetdata'); var_dump($provincia); $this->addMunicipioForm($form, $provincia); } 

AJAX:

 $(document).ready(function(){ var $form = $(this).closest('form'); $(".provincia-input").change(function(){ alert($('.provincia-input').val()); // THIS IS CORRECT VALUE, INTEGER. var data = { idMunicipio: $(this).val() }; $.ajax({ type: 'POST', url: $form.attr('action'), data: data, success: function(data) { alert(data); alert(data.length); // THIS IS INCORRECT. for (var i=0, total = data.length; i < total; i++) { $('.municipio-input').append('<option value="' + data[i].id + '">' + data[i].municipio + '</option>'); } } }); }); }); 

enter image description here

Another point of view Objects are the same. In this case, it works, but I have to click the submit button. How could I do this without pressing a button, that it was an automatic change?

The RegistreUserType class extends AbstractType. I am adding the following lines.

 $builder -> add('provincia', EntityType::class, array( 'class' => 'BackendBundle:DbProvincia', 'label' => 'Provincia', 'placeholder' => '_ Elegir _', 'auto_initialize' => false, 'mapped' => false, 'attr'=> array('class' => 'form-provincia form-control provincia-input'), 'query_builder' => function (EntityRepository $repository) { $qb = $repository->createQueryBuilder('provincia'); return $qb; } )); $builder->add('idMunicipio', EntityType::class, array( 'class' => 'BackendBundle:DbMunicipio', 'label' => 'Municipio', 'placeholder' => '_ Elegir _', 'auto_initialize' => false, 'mapped' => false, 'attr'=> array('class' => 'form-municipio form-control municipio-input') )); $builder->addEventSubscriber(new AddMunicipioField()); 

New AddMunicpioField () class:

 class AddMunicipioField implements EventSubscriberInterface { public static function getSubscribedEvents() { return array( FormEvents::PRE_SUBMIT => 'preSubmit', FormEvents::PRE_SET_DATA => 'preSetData', ); } public function preSubmit(FormEvent $event){ $data = $event->getData(); $this->addField($event->getForm(), $data['provincia']); } protected function addField(Form $form, $provincia){ $form->add('idMunicipio', EntityType::class, array( 'class' => 'BackendBundle:DbMunicipio', 'label' => 'Municipio', 'placeholder' => '_ Elegir _', 'auto_initialize' => false, 'mapped' => false, 'attr'=> array('class' => 'form-municipio form-control municipio-input'), 'query_builder' => function(EntityRepository $er) use ($provincia){ $qb = $er->createQueryBuilder('idMunicipio') ->where('idMunicipio.provincia = :provincia') ->setParameter('provincia', $provincia); return $qb; } )); } 

Ajax Codec:

 $(document).ready(function () { $('.provincia-input').change(function () { var $form = $(this).closest('form'); var data = $('.provincia-input').serialize(); $.ajax({ url: $form.attr('action'), type: 'POST', data: data, success: function (data) { $('.municipio-input').replaceWith($(html).find('.municipio-input')); } }); }); 

});

+8
php events symfony symfony-forms
source share
2 answers

solvable !!

In my form, I added a call to two new classes:

 $builder -> addEventSubscriber(new AddMunicipioFieldSubscriber('idMunicipio')); $builder -> addEventSubscriber(new AddProvinceFieldSubscriber('idMunicipio')); 

Choosing firth is a province, this is a class:

 class AddProvinceFieldSubscriber implements EventSubscriberInterface { private $propertyPathToMunicipio; public function __construct($propertyPathToMunicipio) { $this->propertyPathToMunicipio = $propertyPathToMunicipio; } public static function getSubscribedEvents() { return array( FormEvents::PRE_SET_DATA => 'preSetData', FormEvents::PRE_SUBMIT => 'preSubmit' ); } private function addProvinceForm($form, $Province = null) { $formOptions = array( 'class' => 'BackendBundle:DbProvincia', 'mapped' => false, 'label' => 'Provincia', 'attr' => array( 'class' => 'class_select_provincia', ), ); if ($Province) { $formOptions['data'] = $Province; } $form->add('provincia', EntityType::class, $formOptions); } public function preSetData(FormEvent $event){ $data = $event->getData(); $form = $event->getForm(); if (null === $data) { return; } $accessor = PropertyAccess::createPropertyAccessor(); $municipio = $accessor->getValue($data, $this->propertyPathToMunicipio); $provincia = ($municipio) ? $municipio->getIdMunicipio()->getProvincia() : null; $this->addProvinceForm($form, $provincia); } public function preSubmit(FormEvent $event){ $form = $event->getForm(); $this->addProvinceForm($form); } } 

Second class - Municipi:

 class AddMunicipioFieldSubscriber implements EventSubscriberInterface { //put your code here private $propertyPathToMunicipio; public function __construct($propertyPathToMunicipio){ $this->propertyPathToMunicipio = $propertyPathToMunicipio; } public static function getSubscribedEvents(){ return array( FormEvents::PRE_SET_DATA => 'preSetData', FormEvents::PRE_SUBMIT => 'preSubmit' ); } private function addCityForm($form, $province_id){ $formOptions = array( 'class' => 'BackendBundle:DbMunicipio', 'label' => 'Municipio', 'attr' => array( 'class' => 'class_select_municipio', ), 'query_builder' => function (EntityRepository $repository) use ($province_id) { $qb = $repository->createQueryBuilder('municipio') ->innerJoin('municipio.provincia', 'provincia') ->where('provincia.id = :provincia') ->setParameter('provincia', $province_id) ; return $qb; } ); $form->add($this->propertyPathToMunicipio, EntityType::class, $formOptions); } public function preSetData(FormEvent $event){ $data = $event->getData(); $form = $event->getForm(); if (null === $data) { return; } $accessor = PropertyAccess::createPropertyAccessor(); $municipio = $accessor->getValue($data, $this->propertyPathToMunicipio); $province_id = ($municipio) ? $municipio->getIdMunicipio()->getProvincia()->getId() : null; $this->addCityForm($form, $province_id); } public function preSubmit(FormEvent $event){ $data = $event->getData(); $form = $event->getForm(); $province_id = array_key_exists('provincia', $data) ? $data['provincia'] : null; $this->addCityForm($form, $province_id); } } 

Managed adds this function:

  public function municipioTestAction(Request $request){ $provincia_id = $request->get('provincia_id'); $em = $this->getDoctrine()->getManager(); $provincia = $em->getRepository('BackendBundle:DbMunicipio')->findByProvinceId($provincia_id); return new JsonResponse($provincia); } 

If the findByProvinceId function, I create it as a repository for the DbMunicipio object.

 class DbMunicipioRepository extends EntityRepository{ public function findByProvinceId($provincia_id){ $query = $this->getEntityManager()->createQuery(" SELECT muni FROM BackendBundle:DbMunicipio muni LEFT JOIN muni.provincia provin WHERE provin.id = :provincia_id ")->setParameter('provincia_id', $provincia_id); return $query->getArrayResult(); } } 

And the AJAX codec.

 $(document).ready(function () { $(".class_select_provincia").change(function(){ var data = { provincia_id: $(this).val() }; $.ajax({ type: 'POST', url: URL+'/municipio-test', data: data, success: function(data) { var $muni_selector = $('.class_select_municipio'); alert(data); $muni_selector.html('<option>Ciudad</option>'); for (var i=0, total = data.length; i < total; i++) { $muni_selector.append('<option value="' + data[i].id + '">' + data[i].municipio + '</option>'); } } }); }); }); 
0
source share

I didn’t notice either the field or the property called "select_provincia" neither in your essence nor in the main form, so I’ll try to guess that it should probably be called simply “province”, since it is a name for both property in the municipality and in form of subscription to the municipality. Also in AddMunicipioField.php you should change this code:

 if ($provincia instanceof DbProvincia) { $qb->where('idMunicipio.provincia = :provincia') >setParameter('provincia', $provincia); } 

:

 if ($provincia instanceof DbProvincia) { $qb->where('idMunicipio.provincia = :provincia') >setParameter('provincia', $provincia->getId()); } 

since upon request you will compare the province with the province identifier.

Also, make sure that you implement the __toString () method in the municipality entity, so that symfony knows how to convert this object to a string to show it in the select list.

Hope this helps :)


When I see that you have added new information, I will update my answer:

Firstly, in AddMunicipioField.php you still have the same error: the array key will be called the same as you name your field, in this case not “provincia_input”, but “province”. You can view the data that was sent to you by calling "dump ($ data); die;" before checking for the presence of an array key (check the province key name, as you can see that the name matches what you specified when adding the field to the form (AddProvinciaField.php):

 $form -> add('provincia', EntityType::class 

Another thing that I noticed in the first js snippet you posted is that in this part of the code:

 $(".provincia-input").change(function(){ var data = { idMunicipio: $(this).val() }; $.ajax({ type: 'POST', url: $form.attr('action'), data: data, success: function(data) { for (var i=0, total = data.length; i < total; i++) { $('.municipio-input').append('<option value="' + data[i].id + '">' + data[i].municipio + '</option>'); } } }); }); 

you take the input file from $ (". provincia-input") and send it as a value for the field called "idMunicipio", which, in your opinion, does not make sense.


Finally, I will talk about the errors that were made in the last JS snippet you posted:

  $(document).ready(function () { $('.provincia-input').change(function () { var $form = $(this).closest('form'); var data = $('.provincia-input').serialize(); $.ajax({ url: $form.attr('action'), type: 'POST', data: data, success: function (data) { $('.municipio-input').replaceWith($(html).find('.municipio-input')); } }); }); }); 

First of all, class names should not be used to identify the fields that you use. By definition, they should be used several times in the document and describe only the style, which can lead to unexpected behavior as your code base grows. Please assign the correct identifier values ​​to the inputs you intend to request, and especially replace them so that you can identify them correctly.

Secondly, please refer to the JS code published in the official symfony tutorial following this link . As you can see, the correct way to send data back to the server is not by sending a single property, as you are trying to do in this line:

 var data = $('.provincia-input').serialize(); 

but rather by sending a property as part of the given forms. Since in the tutorial I posted, first create an empty data object:

 var data = {}; 

then add the province value to it:

 data[$(this).attr('name')] = $(this).val(); 

Thirdly, this part of the code is clearly incorrect:

 success: function (data) { $('.municipio-input').replaceWith($(html).find('.municipio-input')); } 

As you can see, the html variable is undefined in this part of the code. This, of course, is because the variable that you should use in this case is called data (the response you received from the server). Therefore, please change this to the following:

 success: function (data) { $('.municipio-input').replaceWith($(data).find('.municipio-input')); } 

Finally, if you are still studying SF and web programming, I would suggest using a bottom-up approach to advance your programming knowledge, as this case is quite complex, and the problems that hampered your code still require a deeper understanding understanding the technologies that you use. I personally would suggest reading about using HTML attributes, handling Symfony forms, reading what data is available to you during each Symfony form event, and maybe trying to use the symfony dumper component to debug your code, since var_dump is a really very inefficient way to debug SF code (would solve a lot of problems for you).

+3
source share

All Articles