type isvalid form fields collection array php forms symfony doctrine2

php - isvalid - Symfony2 forma eventos y transformadores modelo



symfony form validation (2)

Me estoy haciendo un nudo tratando de luchar con los constructores de formularios, eventos y transformadores de Symfony2 ... ¡ojalá alguien aquí tenga más experiencia y pueda ayudar!

Tengo un campo de formulario (menú desplegable de selección) que contiene algunos valores (una lista breve) que se asigna a una entidad. Una de estas opciones es "otra". Supongamos que no hay AJAX por el momento y cuando un usuario envía el formulario que quiero detectar si elige ''otro'' (o cualquier otra opción que no esté en la lista). Si eligen una de estas opciones, se debe mostrar la lista completa de opciones; de lo contrario, solo muestre la lista preseleccionada. Debería ser fácil, ¿verdad? ;)

Por lo tanto, tengo mi Tipo de formulario y muestra bien la lista básica. El código se ve así:

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) ) ; // ... } // ... }

Ahora, quiero verificar el valor enviado, así que utilizo un oyente de eventos de formulario de la siguiente manera.

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? }); }

Sin embargo, falla con un mensaje de error como este:

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

Supongo que este error se debe a que cuando se linkedFoo el linkedFoo , ¿se eliminó el modelTransformer ? Intenté varias formas de acceder a un generador en el cierre del evento, pero esto no pareció funcionar (los valores de retorno fueron inesperados). ¿Hay algún otro método que debería usar en el evento que no sea $event->getForm()->add() ? ¿O hay un problema más fundamental con mi enfoque aquí?

Básicamente no quiero linkedFoo los config / transformers / labels del campo linkedFoo excepto para cambiar las opciones disponibles ... ¿hay alguna otra manera de hacerlo? Por ejemplo, algo como $form->getField()->updateChoices() ?

¡Gracias de antemano por cualquier ayuda que pueda ofrecer!

do

PD: ¿hay alguna documentación o discusión mejor sobre los formularios, eventos, etc. que en el sitio web de Symfony? Por ejemplo, ¿cuál es la diferencia entre PRE_SET_DATA, PRE_SUBMIT, SUBMIT, etc.? ¿Cuándo son despedidos? ¿Para qué deberían ser utilizados? ¿Cómo funciona la herencia con campos de formulario personalizados? ¿Qué es un Formulario y un Constructor, cómo interactúan y cuándo se debe tratar con cada uno? ¿Cómo, cuándo y por qué debería usar FormFactory a través de $form->getConfig()->getFormFactory() ? Etc ..

Editar: En respuesta a la sugerencia de Florian, aquí hay más información sobre cosas que se probaron pero no funcionan:

Si intentas obtener FormBuilder dentro del evento de esta manera:

/** @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)) );

Entonces obtienes el error:

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

Entonces, prueba algo como sugirió Florian, es decir,

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

... pero obtienes este error en su lugar:

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

Lo que parece sugerir que la segunda línea (que agrega el ModelTransformer) nunca se llama porque la llamada ->add() está fallando antes de que pueda llegar allí.


Gracias a las ideas de sstok (en github), creo que lo estoy trabajando ahora. La clave es crear un tipo de formulario personalizado y luego usarlo para agregar el ModelTransformer.

Crear el tipo de formulario personalizado:

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''; } }

Cree una definición de servicio para el nuevo Tipo:

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

El código de la forma principal ahora se ve así:

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'' )); }); // ... } // ... }

La clave es que este método le permite incrustar el ModelTransformer dentro del tipo de campo personalizado para que, cuando agregue una nueva instancia de este tipo, automáticamente agregue el ModelTransformer y evite el ciclo anterior de "no se puede agregar un campo sin un transformador Y no puede agregar un transformador sin un campo "


Tu oyente se ve (casi :)) ok.

Solo usa PRE_SUBMIT. En ese caso, $event->getData() será el dato sin formato (una matriz) que se envía. $selectedFoo contendrá potentamente "otro".

Si es el caso, reemplazará el campo "corto" de "elección" por uno completo, utilizando formFactory en el oyente.

$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); });

Hiciste tantas preguntas que no sé por dónde empezar.

Con respecto a los Transformadores de datos: hasta que desee transformar los datos sin procesar en una representación diferente ("2013-01-01" -> nuevo DateTime ("2013-01-01")), entonces no necesita transformadores.