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 linkedFoo
Config/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ć.
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 "
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.
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