2

Back when I was doing most of my code by hand I had a form that consisted of a variable number of div elements that I handled as an array via post upon submission, but I cannot for the life of me figure out how to do anything similar in Symfony and I'm starting to get really frustrated. Here's my original implementation of a template class for an object (with my own simple templating engine):

<div class="img_transfer">
    <img class="toggle" src="{{src}}">
    <input class="title" type="text" name="title[{{i}}]" placeholder="Title"><br>
    Upload to Sta.sh:
    <input type="radio" name="stash[{{i}}]" value="0" checked="checked"> No
    <input type="radio" name="stash[{{i}}]" value="1"> Yes<br>
    Showcase:
    <input type="radio" name="showcase[{{i}}]" value="0" checked="checked"> No
    <input type="radio" name="showcase[{{i}}]" value="1"> Yes<br>
    <input class="save" type="hidden" name="save[{{i}}]" value="0">
    <input type="hidden" name="old_path[{{i}}]" value="{{old_path}}">
</div>

The {{i}} was a placeholder for a loop iteration, and this was working fine, but here's how I've been trying to do it in Symfony:

$img_tmp = glob('img_tmp/*.*');
$data = array();
foreach ($img_tmp as $src) {
    //$inter = end();
    $parts = explode('/', $src);
    $file = end($parts);
    //$name = explode($inter)[0];
    $name = explode('.', $file)[0];
    $data[$name] = new Art();
}

$builder = $this->createFormBuilder($data);
foreach ($data as $key => $value) {
    //$builder->add($key, (new ArtType())->setSrc($key));
    $builder->add($key, ArtType::class, array('img_src' => $key));
}

$form = $builder->getForm();

return $this->render('img_transfer.html.twig', array('form' => $form->createView()));

This is mostly based on another suggestion on SO for how to have a form of objects, but as is it will not let me send the custom option to the form to be built, which I think might be my final barrier to getting this to do what I had easily implemented by hand. I'm wholly welcome to suggestions and would prefer a less hack-y way of doing this, and also would love to know whether I'm just overlooking a simple way of doing this or if this is as horribly un-intuitive as I perceive it to be. I'll edit and restructure this to be a better question when I have time.

Edit: Formatting and the following additional information: This particular part of the application is supposed to take images of arbitrary name and extension dumped in the "web/img_tmp" directory, and move them into the "web/img" directory while renaming them with PHP's uniqid() and entering them into a database; I want something that generates a grouped set of fields for every "/img_tmp/." that allows me to set attributes for each one before persisting them in the DB. All resources I've yet found on how to submit multiple objects at once have been either incomplete or woefully over-complicated.

1 Answer 1

2

As it turns out, I was looking at solutions involving deprecated code, namely the setDefaultOptions(OptionsResolverInterface $resolver) method of my custom type, which apparently was removed in Symfony 3.0. For reference, here's the complete code of how you can submit multiple objects in a form and pass custom options to them:

Controller code

public function someAction()
{
    $vals = array('customVal1', 'customVal2', 'customVal3');
    foreach ($vals as $v) {
        $data[$v] = new YourObject();
    }

    $builder = $this->createFormBuilder($data);
    //I put this here so I don't have to scroll all the way to the bottom to submit;
    //this will render it at the top by default (at least on mine)
    $builder->add('submit', SubmitType::class, array('label' => 'Submit'));

    foreach ($vals as $v) {
        $builder->add($v, YourObjectType::class, array(
            'custom_val' => $v
        ));
    }

    $form = $builder->getForm();

    return $this->render('the_page.html.twig', array('form' => $form->createView()));
}

Custom form template

{% block yourobject_widget %}
    <div>
        <img src="{{ custom_val }}"> //This is set by the call to setAttribute() in YourObjectType
        {% for child in form %}
            {{ form_row(child) }}
        {% endfor %}
    </div>
{% endblock %}

app/config/config.yml

twig:
    debug:            "%kernel.debug%"
    strict_variables: "%kernel.debug%"
    form_themes:
        - 'form/fields.html.twig'

Finally, YourObjectType

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;

use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class YourObjectType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver)
    {
        parent::configureOptions($resolver);

        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\YourObject',
            'custom_val' => ''  //This is important. You have to set defaults for
                                //options for Symfony to allow you to pass them
        ));
    }

    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars['custom_val'] = $options['custom_val'];
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('some_text_field', TextType::class)
            ->add('some_choice_field', ChoiceType::class, array(
                'choices' => array('Yes' => true, 'No' => false),
                'expanded' => true
            ))
            ->add('some_hidden_field', HiddenType::class, array('mapped' => false))
            ->setAttribute('custom_val', $options['custom_val']);
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.