Este foro ya no está activo, así que no puedes publicar nuevas preguntas ni responder a las preguntas existentes.

Problema con el uso del prototype en Formularios

1 de abril de 2015

Hola. Tengo lo siguiente.

En mi aplicación debo crear órdenes de compra. Al crearse la orden de compra, está tiene una relación de ManyToOne con un listado de proveedores, relación que no debe poder modificarse una vez creada la entidad ya que cada proveedor tiene sus propios productos y no se permiten más de dos proveedores.

OrdenCompra

class OrdenesCompra
{
    // ...
 
    /**
     * @ORM\ManyToOne(targetEntity="Proveedores", inversedBy="ordenes")
     * @ORM\JoinColumn(name="proveedor_id", referencedColumnName="id")
     */
    protected $proveedores;
}

Proveedores

class Proveedores
{
    /**
     * @ORM\OneToMany(targetEntity="OrdenesCompra", mappedBy="proveedores")
     */
    private $ordenes;
 
    /**
     * @ORM\OneToMany(targetEntity="Productos", mappedBy="proveedores")
     */
    protected $productos;
}

Productos

class Productos
{
    /**
     * @ORM\ManyToOne(targetEntity="Proveedores", inversedBy="productos")
     * @ORM\JoinColumn(name="proveedor_id", referencedColumnName="id")
     */
    private $proveedores;
}

La clase del formulario de OrdenCompra:

use [bundle]\[bundle]Bundle\Form\EventListener\OrdenesCompraSubscriber;
 
class OrdenesCompraType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
      $builder->addEventSubscriber(new OrdenesCompraSubscriber());
    }
 
    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => '[Bundle]\[Bundle]Bundle\Entity\OrdenesCompra',
            // 'cascade_validation' => true
        ));
    }
 
    /**
     * @return string
     */
    public function getName()
    {
        return 'orden_compra';
    }
}
class OrdenesCompraSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return array(
          FormEvents::PRE_SET_DATA => 'preSetData'
          );
    }
 
    public function preSetData(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();
 
        if (null === $data) {
            return;
        }
 
        if (!$data->getId()) {
            $form->add('numerofactura', 'text', array(
              'label' => 'Número de factura'
            ));
            $form->add('proveedores', 'entity', array(
              'class' => 'ProveedoresBundle:Proveedores',
              'property' => 'proveedor',
              'empty_value'=> 'Seleccione',
              'label' => 'Proveedor'
            ));
        } else {
            $form
              ->add('ordenes', 'collection', array('type' => new OrdenesDetalleType(),
              'label' => false,
              'by_reference' => false,
              'allow_delete' => true,
              'allow_add'    => true,
              'prototype'    => true,
            ));
        }
    }
}

OrdenesDetalle

use [Bundle]\[Bundle]Bundle\Form\EventListener\AddProveedoresFieldSubscriber;
 
class OrdenesDetalleType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
 
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
          //->add("productos")
          ->add('cantidad')
          ->add('precio')
          ->add('ivaAduanal')
          ->add('igi');
 
        $builder->addEventSubscriber(new AddProveedoresFieldSubscriber());
    }
 
    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => '[Bundle]\[Bundle]Bundle\Entity\OrdenesDetalle'
        ));
    }
 
    /**
     * @return string
     */
    public function getName()
    {
        return 'sisfc_operacionesbundle_ordenesproductos';
    }
}

Evento de formulario para agregar el campo Productos a partir del proveedor de la orden de compra:

class AddProveedoresFieldSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return array(
          FormEvents::PRE_SET_DATA => 'preSetData',
          // FormEvents::PRE_BIND     => 'preBind'
          );
    }
 
    public function preSetData(FormEvent $event)
    {
        $data = $event->getData();
        $form = $event->getForm();
 
        if (null === $data) {
            return;
        }
 
        $proveedorId = $data->getOrdenes()->getProveedores()->getId();
 
        if ($data->getOrdenes()) {
          $form->add("productos",'entity', array(
              'class' => 'ProveedoresBundle:Productos',
              'property' => "producto",
              'query_builder' => function(EntityRepository $er) use($proveedorId){
                  return $er->createQueryBuilder('p')->where('p.proveedores = :proveedor')
                  ->setParameter('proveedor', $proveedorId);
                },
              'empty_value' => "Seleccione"
              )
            );
        }
    }

Hasta aquí todo funciona. El gran problema es en el momento de renderizar el formulario y usar el data-prototype. En la clase OrdenesProductosType quito el campo de Productos y creo un evento de formulario para saber el id del Proveedor relacionado con la OrdenCompra y así poder generar la consulta que necesito para llamar los productos de este proveedor.

<form >
        {% macro prototype(orden) %}
           <tr>
             <td>{{ form_widget(orden.productos) }}</td>
             <td>{{ form_widget(orden.cantidad) }} </td>
             <td>{{ form_widget(orden.precio) }} </td>
             <td>{{ form_widget(orden.ivaAduanal) }} </td>
             <td>{{ form_widget(orden.igi) }}</td>
             <td>
              <button class="btn btn-default btn-delete"><span class="glyphicon glyphicon-remove"></span></button>
             </td>
           </tr>
       {% endmacro %}
 
       <table class="table table-condensed">
           <caption></caption>
           <thead>
            ...
           </thead>
           <tbody class="collections" data-prototype="{{ _self.prototype(edit_form.ordenes.vars.prototype)|e }}">
               {% for orden in edit_form.ordenes %}
                  {{_self.prototype(orden)}}
               {% endfor %}
           </tbody>
       </table>
       <div class="btn-group" role="group" aria-label="...">
        <button type="submit" class="btn btn-primary">
          <span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Actualizar
        </button>
        <button type="button" class="btn btn-default add">
          <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Agregar
        </button>
      </div>
      </form>

Al parecer, prototype no es capaz de entender la información que se crea a partir del evento del formulario. Si lo dejo como está la aplicación me arroja el error:

Method "productos" for object "Symfony\Component\Form\FormView" does not
exist in OperacionesBundle:OrdenesCompra:edit.html.twig

Es obvio, porque la línea productos, en la clase OrdenesDetalle está comentada.

Si descomento la línea //->add("productos”) -> ->add("productos”) la aplicación funciona normalmente, sólo que me arroja en la vista todos los productos de la aplicación.

Quedo atento a cualquier solución. De antemano, quedo agradecido. Saludos