Symfony2 form events and model transformers

Jestem przywiązany w węzłach próbując zmagać się z budowniczymi form Symfony2, wydarzeniami i Transformerami... mam nadzieję, że ktoś tutaj jest bardziej doświadczony i może pomóc!

Mam pole formularza (wybierz rozwijane), które zawiera pewne wartości (krótka lista), które mapuje do encji. Jedną z tych opcji jest "inna". Załóżmy, że na razie nie ma Ajaxu, a gdy użytkownik przesyła formularz, chcę wykryć, czy wybrał opcję "INNE" (lub jakąkolwiek inną opcję nie na krótkiej liście). Jeśli wybrali jedną z tych Opcje następnie należy wyświetlić pełną listę opcji, w przeciwnym razie wystarczy pokazać listę skróconą. Powinno być łatwo, prawda? ;)

Więc, mam swój typ formularza i wyświetla podstawową listę w sam raz. Kod wygląda mniej więcej tak:

namespace Company\ProjectBundle\Form\Type;

use ...

class FancyFormType extends AbstractType {
    private $fooRepo;

    public function __construct(EntityManager $em, FooRepository $fooRepo)
    {
        $this->fooRepo = $fooRepo;
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {
        /** @var Bar $bar */
        $bar = $builder->getData();
        $fooTransformer = new FooToStringTransformer($options['em']);

        $builder
            ->add($builder
                ->create('linkedFoo', 'choice', array(
                    'choices' => $this->fooRepo->getListAsArray(
                        $bar->getLinkedfoo()->getId()
                    ),
                ))
                ->addModelTransformer($fooTransformer)
            )
        ;

        // ...

    }

    // ...
}

Teraz chcę sprawdzić przesłaną wartość, więc używam słuchacza zdarzeń formularza w następujący sposób.

public function buildForm(FormBuilderInterface $builder, array $options) {
    // ... This code comes just after the snippet shown above

    $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
        /** @var EntityManager $em */
        $em = $event->getForm()->getConfig()->getOption('em');

        $data = $event->getData();
        if (empty($data['linkedFoo'])) return;
        $selectedFoo = $data['linkedfoo'];

        $event->getForm()->add('linkedFoo', 'choice', array(
            'choices' => $em
                ->getRepository('CompanyProjectBundle:FooShortlist')
                ->getListAsArray($selectedFoo)
            ,
        ));
        //@todo - needs transformer?
    });
}

Jednak nie powiedzie się z takim Komunikatem o błędzie:

Notice: Object of class Proxies\__CG__\Company\ProjectBundle\Entity\Foo could not be converted to int in \path\to\project\symfony\symfony\src\Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList.php line 458 

Zakładam, że ten błąd wynika z tego, że gdy linkedFoo została nadpisana usunięto modelTransformer? Próbowałem różnych sposobów dostępu do Buildera podczas zamykania zdarzenia, ale to nie działało (wartości zwracane były nieoczekiwane). Czy jest jakaś inna metoda, którą powinienem użyć w przypadku, inna niż $event->getForm()->add()? A może jest bardziej fundamentalny problem z moim podejściem tutaj?

Zasadniczo nie chcę mieszać z pole linkedFooConfig/transformers/labels z wyjątkiem, aby zmienić dostępne opcje... jest na to jakiś inny sposób? Np. coś w rodzaju $form->getField()->updateChoices()?

Z góry dziękuję za każdą pomoc, jaką możesz zaoferować!

C

P. S. czy jest jakaś lepsza dokumentacja lub omówienie formularzy, wydarzeń itp. niż na stronie Symfony? Np. jaka jest różnica między PRE_SET_DATA, PRE_SUBMIT, SUBMIT, itp? Kiedy są zwolnieni? Do czego powinny być używane? Jak działa dziedziczenie z niestandardowymi polami formularza? Co To jest formularz i budowniczy, w jaki sposób współdziałają i kiedy należy sobie z nimi radzić? Jak, kiedy i dlaczego należy stosować FormFactory można uzyskać dostęp przez $form->getConfig()->getFormFactory()? Itd..


Edit: w odpowiedzi na sugestię Floriana tutaj jest trochę więcej informacji o rzeczach, które zostały wypróbowane, ale nie działają:

Jeśli spróbujesz uzyskać FormBuilder w zdarzeniu w następujący sposób:

/** @var FormBuilder $builder */
$builder = $event->getForm()->get('linkedFoo')->getConfig();

$event->getForm()->add($builder
    ->create('linkedFoo', 'choice', array(
        'choices' => $newChoices,
        'label'   =>'label',
    ))
    ->addModelTransformer(new FooToStringTransformer($em))
);

Potem pojawia się błąd:

FormBuilder methods cannot be accessed anymore once the builder is turned
into a FormConfigInterface instance.

Więc spróbuj czegoś, co zasugerował Florian, czyli

$event->getForm()->add('linkedFoo', 'choice', array(
    'choices' => $newChoices,
));
$event->getForm()->get('linkedFoo')->getConfig()->addModelTransformer(new FooToStringTransformer($em));

...ale zamiast tego dostajesz ten błąd:

Notice: Object of class Proxies\__CG__\Company\ProjectBundle\Entity\Foo could not be converted to int 
in C:\path\to\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList.php line 458

Co wydaje się sugerować, że druga linijka (który dodaje ModelTransformer) nigdy nie jest wywoływany, ponieważ wywołanie {[13] } zawodzi, zanim będziesz mógł się tam dostać.

Author: caponica, 2013-10-15

2 answers

Dzięki pomysłom z sstok (na GitHubie), myślę, że już to działa. Kluczem jest utworzenie niestandardowego typu formularza, a następnie użycie go do dodania ModelTransformer.

Utwórz niestandardowy typ formularza:

namespace Caponica\MagnetBundle\Form\Type;

use ...

class FooShortlistChoiceType extends AbstractType {
    protected $em;

    public function __construct(EntityManager $entityManager)
    {
        $this->em                   = $entityManager;
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {
        $fooTransformer = new FooToStringTransformer($this->em);

        $builder
            ->addModelTransformer($fooTransformer)
        ;
    }

    public function getParent() {
        return 'choice';
    }

    public function getName() {
        return 'fooShortlist';
    }
}

Utwórz definicję usługi dla nowego typu:

company_project.form.type.foo_shortlist:
    class: Company\ProjectBundle\Form\Type\FooShortlistChoiceType
    tags:
        - { name: form.type, alias: fooShortlist }
    arguments:
        - @doctrine.orm.entity_manager

Kod głównego formularza wygląda teraz mniej więcej tak:

namespace Company\ProjectBundle\Form\Type;

use ...

class FancyFormType extends AbstractType {
    private $fooRepo;

    public function __construct(FooRepository $fooRepo)
    {
        $this->fooRepo = $fooRepo;
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {
        /** @var Bar $bar */
        $bar = $builder->getData();
        $fooTransformer = new FooToStringTransformer($options['em']);

        $builder
            ->add('linkedFoo', 'fooShortlist', array(
                'choices' => $this->fooRepo->getListAsArray(
                    $bar->getLinkedfoo()->getId()
                ),
            ))
        ;

        $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
            /** @var EntityManager $em */
            $em = $event->getForm()->getConfig()->getOption('em');

            $data = $event->getData();
            if (empty($data['linkedFoo'])) return;
            $selectedFoo = $data['linkedFoo'];

            $event->getForm()->add('linkedFoo', 'fooShortlist', array(
                'choices'       => $em->getRepository('CaponicaMagnetBundle:FooShortlist')->getListAsArray($selectedFoo),
                'label'         => 'label'
            ));
        });

        // ...

    }

    // ...
}

Kluczem jest to, że ta metoda pozwala na osadzenie ModelTransformer w niestandardowym typie pola tak, że po dodaniu nowego instancja tego typu automatycznie dodaje ModelTransformer dla Ciebie i zapobiega poprzedniej pętli " can 't add a field without a transformer AND can' t add a transformer without a field "

 27
Author: caponica,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-25 13:05:10

Twój słuchacz wygląda (prawie:)) ok.

Wystarczy użyć PRE_SUBMIT. W takim przypadku $event->getData() będą surowymi danymi formularza (tablicą), które zostaną wysłane. $selectedFoo będzie zawierać "inne".

W takim przypadku pole "short" 'choice' zamienisz na pełne, używając formFactory w słuchaczu.

$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
    $data = $event->getData();
    if (empty($data['linkedFoo']) || $data['linkedFoo'] !== 'other') {
        return;
    }

    // now we know user choose "other"
    // so we'll change the "linkedFoo" field with a "fulllist"


    $event->getForm()->add('linkedFoo', 'choice', array(
        'choices' => $fullList, // $em->getRepository('Foo')->getFullList() ?
    ));
    $event->getForm()->get('linkedFoo')->getConfig()->addModelTransformer(new FooTransformer);
});
Zadałaś tyle pytań, że nie wiem od czego zacząć.

Dotyczące datatransformatorów: dopóki nie chcesz przekształcić surowych danych w inny reprezentacja ("2013-01-01" - > new DateTime("2013-01-01")), wtedy nie potrzebujesz transformatorów.

 1
Author: Florian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-23 13:04:58