duannan3959 2018-03-07 07:24
浏览 39

Symfony CollectionType of Entities

I'm working on a project on Symfony 3.4 without Doctrine (we use a homemade ORM similar to Eloquent) and have been stucked trying to make a CollectionType of an Entity I use : IntroductionSlide.

The form looks like this :

What the form looks like when rendered

You can add or delete IntroductionSlides and each IntroductionSlide is defined only by its image. As the image depends on the locale, it's handled by a TranslatableImageType which creates a field per locale. Clicking on the little flag allows you to choose target locale's input field.

Here's what my code looks like :

The creation of the CollectionType :

case 'introduction_slides':
    $options['label'] = "Pages de présentation";
    $options['entry_type'] = IntroductionSlideType::class;
    $options['allow_add'] = true;
    $options['allow_delete'] = true;
    $options['attr']['class'] = "introduction-slides";
    $options['required'] = false;
    $this->form->add($field, CollectionType::class, $options);
    break;

The IntroductionSlideType :

<?php

namespace AppBundle\Form\Type;

use AppBundle\Model\IntroductionSlide;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;

class IntroductionSlideType extends AbstractType
{
    public function buildView(FormView $view, FormInterface $form, array $options)
    {}

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $options['mapped'] = false;
        $builder->add("images", TranslatableImageType::class, $options);
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => IntroductionSlide::class,
        ));
    }
}

And The TranslatableImageType :

<?php

namespace AppBundle\Form\Type;

use AppBundle\EventSubscriber\StaticConfig;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Image;

class TranslatableImageType extends AbstractType
{
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars['locales'] = $options['locales'];
        $view->vars['locale'] = $options['locale'];
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $file_options = array_merge($options, $options["field_options"]);

        unset($file_options['locales'], $file_options['locale'], $file_options["field_options"]); // not valid text options
        $file_options['compound'] = false; // sub items are simple widgets

        $file_options['constraints'] = [new Image([
            'mimeTypes' => ["image/png", "image/jpg", "image/jpeg", "image/gif"],
            'mimeTypesMessage' => "Seul un fichier avec l'extension png, jpg, jpeg ou gif peut être uploadé.",
            "maxSize" => "7M",
            'maxSizeMessage' => "Ce fichier est trop volumineux ({{ size }} {{ suffix }}). La limite de volume est de {{ limit }} {{ suffix }}.",
        ])];

        foreach ($options['locales'] as $locale) {
            $file_options['attr']['lang'] = $locale;
            $builder->add($locale, FileType::class, $file_options);
        }
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => null,
        ));

        $resolver->setDefaults(array(
            'locales' => StaticConfig::getLocales(),
            'locale' => StaticConfig::getLocale(),
            'field_options' => [],
        ));
    }

    public function getBlockPrefix()
    {
        return 'translatableImage';
    }
}

Here what I was thinking Symfony would do (with no data passed to the collection) :
1. define a CollectionType
2. define an IntroductionSlideType inside the CollectionType
3. instanciate an empty IntroductionSlide and use is as data for the IntroductionSlideType
4. create a field "images" to the builder and call $IntroductionSlide->getImages() to feed its value
5. define a TranslatableImageType inside the IntroductionSlideType
6. use $IntroductionSlide->getImages()'s returned value as data (contains an array of locale => image_name)

Instead, Symfony triggers an Exception :

Cannot read index "0" from object of type "AppBundle\Model\IntroductionSlide" because it doesn't implement \ArrayAccess.

I don't see why it would try to reach an index "0" in an IntroductionSlide.

Setting up a break-point showed me this happens when Symfony is trying to build the field "images" in IntroductionSlideType.

Adding $options['mapped'] = false; to $builder->add("images", TranslatableImageType::class, $options); makes it able to reach the view without triggering the Exception.
When submitting the form, after the data is processed by the Form component, here's what it looks like :

Empty arrays of arrays

  • 写回答

0条回答 默认 最新

    报告相同问题?