0

First, best wishes to all for this 2016 new year !

I'm facing a problem I did not manage to resolve on my own.

I'm working on a Silex (~1.3) application. I coded simple CRUDs on my domain classes. I created as well some Type forms to be able to modify my basic domain classes. In this case, I have to manage the notion of State within a Country. Each is a specific class, and a State has one Country attribute.

In my form, I declared some text fields, and a Choice field to be able to select the country (the form class is copied below).

My problem is that when I try to modify an existing State with the following controller, the text fields name, code, unloc are filled with the data from the database, but not the choices country nor hub (the controller class is copied below).

Please note that i'm NOT using Doctrine, but a home-made (and quite basic) DAO.

Here is my code and some information :

  1. The view is done using twig, with the 'standard' bootstrap example which can be found here : Form Customization, using Bootstrap layout and Form layout :

    {% extends 'layout.html.twig' %} {% block title %}{{ title }}{% endblock %}
    {% block content %}
        {% if form and is_granted('IS_AUTHENTICATED_FULLY') and is_granted('ROLE_ADMIN')%}
            {% form_theme form 'bootstrap_3_layout.html.twig' %} {{ form_start(form) }}
                <div class="form-group">
                    {{ form_errors(form) }}
                    {{ form_widget(form) }}
                    <input type="submit" class="btn btn-primary" value={% if button is not defined %} "Save"{% else %}"{{ button }}"{% endif %} />
                </div>
            {{ form_end(form) }}
        {% else %}
            <div>
                <p>Ask an admin to add/modify information.</p>
            </div>
        {% endif %}
    {% endblock %}
    
  2. composer.json content :

    {
        "require": {
            "silex/silex": "~1.3",
            "doctrine/dbal": "2.5.*",
            "symfony/security": "2.7.*",
            "twig/twig": "1.21.*",
            "symfony/twig-bridge": "2.7.*",
            "symfony/form": "2.7.*",
            "symfony/translation": "2.7.*",
            "symfony/config": "2.7.*",
            "jasongrimes/silex-simpleuser": "*",
            "twig/extensions": "1.3.*",
            "symfony/validator": "2.*",
            "phpoffice/phpexcel": "1.*"
        },
        "require-dev": {
            "phpunit/phpunit": "*",
            "symfony/browser-kit": "*",
            "symfony/css-selector": "*",
            "silex/web-profiler": "*",
            "symfony/monolog-bridge": "*"
        },
        "autoload":{
            "psr-4":{"Easytrip2\\": "src"}
        }
    }
    
  3. Form code :

    <?php
    
    namespace Easytrip2\Form\Type;
    
    use Easytrip2\DAO\CountryDAO;
    use Easytrip2\DAO\GeopointDAO;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    
    class StateType extends AbstractType {
        /**
         * @CountryDAO
         * \Easytrip2\DAO\CountryDAO
         * allow to find the Country for the form.
         */
        private $countryDAO;
        /**
         * @GeopointDAO
         * \Easytrip2\DAO\GeopointDAO
         * allow to find the Country for the form.
         */
        private $geopointDAO;
        /**
         *
         * {@inheritDoc}
         *
         * @see \Symfony\Component\Form\AbstractType::buildForm()
         */
        public function buildForm(FormBuilderInterface $builder, array     $options) {
            $builder->add ( 'name', 'text', array (
                    'label' => 'State name'
            ) );
            $builder->add ( 'code', 'text', array (
                    'label' => 'State code'
            ) );
            $builder->add ( 'unloc', 'text', array (
                    'label' => 'State code'
            ) );
            $countries = $this->countryDAO->findAll ();
            $choices = array ();
            $labels = array ();
            foreach ( $countries as $value ) {
                $choices [] = $value;
                $labels [] = $value->getName ();
            }
            $builder->add ( 'country', 'choice', array (
                    'choice_list' => new ChoiceList ( $choices, $labels )
            ) );
    
            $hubs = $this->geopointDAO->findAllHubs ();
            $choices = array ();
            $labels = array ();
            foreach ( $hubs as $value ) {
                $choices [] = $value;
                $labels [] = $value->getName ();
            }
            $builder->add ( 'hub', 'choice', array (
                    'choice_list' => new ChoiceList ( $choices, $labels ),
                    'required' => false
            ) );
        }
    
        /**
         *
         * {@inheritDoc}
         *
         * @see \Symfony\Component\Form\AbstractType::configureOptions()
         */
        public function configureOptions(OptionsResolver $resolver) {
            $resolver->setDefaults ( array (
                    'data_class' => 'Easytrip2\Domain\State'
            ) );
        }
    
        /**
         *
         * {@inheritDoc}
         *
         * @see \Symfony\Component\Form\FormTypeInterface::getName()
         */
        public function getName() {
            return 'state';
        }
        /**
         * allow use of a CountryDAO
         */
        public function __construct(CountryDAO $countryDAO, GeopointDAO     $geopointDAO) {
            $this->geopointDAO = $geopointDAO;
            $this->countryDAO = $countryDAO;
        }
    }
    
  4. Controller content :

    public function stateUpdateByIdAction($id, Request $request, Application $app) {
            if ($app ['security.authorization_checker']->isGranted ( 'IS_AUTHENTICATED_FULLY' ) and $app ['security.authorization_checker']->isGranted ( 'ROLE_ADMIN' )) {
                $obj = $app ['dao.state']->findById ( $id );
                $form = $app ['form.factory']->create ( new StateType ( $app ['dao.country'], $app ['dao.geopoint'] ), $obj );
                $form->handleRequest ( $request );
                if ($form->isSubmitted () && $form->isValid ()) {
                    if ($app ['dao.state']->save ( $obj )) {
                        $app ['session']->getFlashBag ()->add ( 'success', 'The state was succesfully updated.' );
                        return $app->redirect ( $app ['url_generator']->generate ( 'state' ) );
                    } else {
                        $app ['session']->getFlashBag ()->add ( 'error', 'Something went wrong...' );
                    }
                }
                return $app ['twig']->render ( 'form.html.twig', array (
                        'form' => $form->createView (),
                        'title' => 'Edit states'
                ) );
            } else {
                $app ['session']->getFlashBag ()->add ( 'error', 'Don\'t have the rights...' );
                return $app->redirect ( $app ['url_generator']->generate ( 'home' ) );
            }
        }
    
  • I saw that post, but the problem is slightly different (it works for me for text fields, and not for choice lists, as it does not work at all in the post) https://stackoverflow.com/questions/20500115/symfony2-form-pre-fill-fields-with-data. I tried anyway, but the problem still exists. – Matthieu Borgraeve Jan 05 '16 at 15:45

3 Answers3

0

Suppose that choices are not filled with data from DB because country and hub id is not transmitted to ChoiceList

$choices = array();
foreach ($countries as $value) {
    $choices[$value->getId()] = $value->getName();
}
$builder->add('country', 'choice', array(
    'choices' => $choices
));
Max P.
  • 5,579
  • 2
  • 13
  • 32
  • Good idea ! I'm using the form factory though, which get the object I'm constructing. The form does not get the object directly. Do you know how I could pass the linked objects to lower levels of my form ? Am I wrongly using forms here ? Thx ! – Matthieu Borgraeve Jan 08 '16 at 13:23
  • Suppose that that your code is correct, but you have a mistake. You transmit to choices only country names without ids, so form can't select option according to object country property. Look at my example in answer. – Max P. Jan 08 '16 at 13:56
  • Ah you're right, i did not notice the change. I just tried, and now i get Silex telling me that `The option "choice" does not exists`, and tell me that is is supposed to be one of a very long list, containing `"choice_attr", "choice_label", "choice_list", "choice_loader", "choice_name", "choice_translation_domain", "choice_value", "choices", "choices_as_values"`. If I modify it to match `choices`, i get another error that tells me that silex could not load type `choices`. – Matthieu Borgraeve Jan 11 '16 at 14:52
  • I'm a bit lost, I'm starting to feel that i have a wrong setup, as I do not have the same behavior as the one described by the doc :( http://symfony.com/doc/current/reference/forms/types/choice.html. – Matthieu Borgraeve Jan 11 '16 at 15:21
  • option - choices, type - choice. Try `$builder->add('country', Symfony\Component\Form\Extension\Core\Type\ChoiceType::class, array('choices' => $choices));` – Max P. Jan 11 '16 at 15:25
  • I get a `Could not load type` :( – Matthieu Borgraeve Jan 11 '16 at 15:59
0

I managed to find a solution (probably not the best, but it works).

I understand this as : we give the object as choices, use it as value, and then use closure to get the ids and labels, instead of doing the work ourselves and giving 'ready-to-use' data to the form.

Is there a cleaner way to do it ?

$obj = $this->countryDAO->findAll ();
$list = array ();
foreach ( $obj as $value ) {
    $list [$value->getId ()] = $value;
}
$builder->add ( 'country', 'choice', array (
    'choices' => $list,
    'choices_as_values' => true,
    'choice_label' => function ($value) {
        return $value->getName ();
    },
    'choice_value' => function ($value) {
    // you mightwant to check for null here, is your form concern
    // a attribute that can be null, as the closure appear to be called
    // on the attribute, and not only on the $obj contents;
        return $value->getId ();
    },
    'placeholder' => 'Select a country'
) );
0

Add this line to your choice options: 'choices_as_values' => true,

It is essential to activate the new choice type API http://symfony.com/doc/current/reference/forms/types/choice.html#example-usage

NightFox
  • 57
  • 1
  • 6
  • Hello, I tried that before, and it does work (it display properly), but does not fill in the object data in the form in the case a attribute is an object. I guess that there is a comparison object that takes place when no closure is given, and this fail for some reason. – Matthieu Borgraeve Jan 11 '16 at 16:57