【问题标题】:Symfony2 form collection not calling addxxx and removexxx even if 'by_reference' => false即使'by_reference' => false,Symfony2 表单集合也不会调用 addxxx 和 removexxx
【发布时间】:2016-01-26 08:00:52
【问题描述】:

我有 Customer 实体和两个一对多关系 CustomerPhone 和 CustomerAddress。

客户实体有 addPhone/removePhone 和 addAddress/removeAddress "adders"。

CustomerType 集合选项对于两个集合都具有 'by_reference' => false。

实体函数 addPhone/removePhone 和 addAddress/removeAddress 在表单提交后没有被调用,所以 CustomerPhone 和 CustomerAddress 在持久化后没有父 id。

为什么在提交表单时不会调用 addPhone/removePhone 和 addAddress/removeAddress?

UPD 1.

在@Baig suggestion 之后,现在我调用了 addPhone/removePhone “adders”,但没有调用 addAddress/removeAddress。不知道为什么,因为它们是相同的。

 # TestCustomerBundle/Entity/Customer.php

 /**
 * @var string
 *
 * @ORM\OneToMany(targetEntity="CustomerPhone", mappedBy="customerId", cascade={"persist"}, orphanRemoval=true)
 */
private $phone;

/**
 * @var string
 *
 * @ORM\OneToMany(targetEntity="CustomerAddress", mappedBy="customerId", cascade={"persist"}, orphanRemoval=true)
 */
private $address;

同一个文件“adders”

# TestCustomerBundle/Entity/Customer.php
/**
 * Add customer phone.
 *
 * @param Phone $phone
 */
public function addPhone(CustomerPhone $phone) {
    $phone->setCustomerId($this);
    $this->phone->add($phone);

    return $this;
}

/**
 * Remove customer phone.
 *
 * @param Phone $phone customer phone
 */
public function removePhone(CustomerPhone $phone) {
    $this->phone->remove($phone);
}
/**
 * Add customer address.
 *
 * @param Address $address
 */
public function addAddress(CustomerAddress $address) {
    $address->setCustomerId($this);
    $this->address->add($address);

    return $this;
}

/**
 * Remove customer address.
 *
 * @param Address $address customer address
 */
public function removeAddress(CustomerAddress $address) {
    $this->address->remove($address);
}

关系:

# TestCustomerBundle/Entity/CustomerPhone.php
/**
 * @ORM\ManyToOne(targetEntity="Customer", inversedBy="phone")
 * @ORM\JoinColumn(name="customer_id", referencedColumnName="id")
 **/
private $customerId;

#TestCustomerBundle/Entity/CustomerAddress.php
/**
 * @ORM\ManyToOne(targetEntity="Customer", inversedBy="address")
 * @ORM\JoinColumn(name="customer_id", referencedColumnName="id")
 **/
private $customerId;

客户类型表单:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('name')
        ->add('phone', 'collection', array(
            'type' => new CustomerPhoneType(),
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false,
            'options' => array('label' => false)
        ))
        ->add('address', 'collection', array(
            'type' => new CustomerAddressType(),
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false,
            'options' => array('label' => false)
        ))
        ->add('submit', 'submit')
    ;
}

控制器。

# TestCustomerBundle/Controller/DefaultController.php

public function newAction(Request $request)
    {
        $customer = new Customer();
        // Create form.
        $form = $this->createForm(new CustomerType(), $customer);
        // Handle form to store customer obect with doctrine.
        if ($request->getMethod() == 'POST')
        {
            $form->bind($request);
            if ($form->isValid())
            {
                /*$em = $this->get('doctrine')->getEntityManager();
                $em->persist($customer);
                $em->flush();*/
                $request->getSession()->getFlashBag()->add('success', 'New customer added');
            }
        }
        // Display form.
        return $this->render('DeliveryCrmBundle:Default:customer_form.html.twig', array(
            'form' => $form->createView()
        ));
    }

UPD 2. 测试是否调用了 addAddress。

/**
     * Add customer address.
     *
     * @param Address $address
     */
    public function addAddress(Address $address) {
        jkkh; // Test for error if method called. Nothing throws.
        $address->setCustomerId($this);
        $this->address->add($address);        
    }

UPD 3.

客户地址类型.php

<?php

namespace Delivery\CrmBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class CustomerAddressType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('street')
            ->add('house')
            ->add('building', 'text', ['required' => false])
            ->add('flat', 'text', ['required' => false])
        ;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Delivery\CrmBundle\Entity\CustomerAddress'
        ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'delivery_crmbundle_customeraddress';
    }
}

CustomerPhoneType.php

<?php

namespace Delivery\CrmBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class CustomerPhoneType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('number')
        ;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Delivery\CrmBundle\Entity\CustomerPhone'
        ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'phone';
    }
}

【问题讨论】:

  • 您能更具体地说明您遇到的问题吗?很难说你认为问题出在哪里。
  • @kdbanman 实体函数 addPhone/removePhone 和 addAddress/removeAddress 在表单提交后不会被调用。它不像这里描述的那样工作 symfony.com/doc/current/cookbook/form/form_collections.html 因此,CustomerPhone 和 CustomerAddress 在持久化后没有父 ID。问题已更新。
  • 您可以尝试删除$em-&gt;persist($customer);,然后尝试。我也在使用集合类型,一切似乎都设置正确,虽然我使用的是yml,但这不应该有所作为。现在只是尝试删除$em-&gt;flush(); 我也不明白你的表单创建之上的所有内容的目的,我认为你不需要那个
  • 当您使用collectionType 时,symfony 会自动查找add 方法,因此您不必在控制器中指定该方法,因此我建议您删除上面的代码$form = $this-&gt;createForm(new CustomerType(), $customer);跨度>
  • @Baig 谢谢。这得到了家长的帮助。现在我调用了 addPhone/removePhone,但没有调用 addAddress/removeAddress。 CustomerAddress 在没有客户 ID 的情况下仍然保存。有问题的控制器代码已更新。

标签: forms symfony collections doctrine


【解决方案1】:

对我来说,最终通过添加getXXX 解决了这个问题,这会将集合返回到PropertyAccessor。否则,您会一直想知道为什么不调用 addXXXremoveXXX

所以请确保:

  • 选项by_reference在现场设置为false
  • 您在关系的拥有方同时拥有 adderremover 方法,
  • PropertyAccessor 可以访问getter 以检查是否可以使用by_reference
  • 如果您想使用 prototype 通过 Javascript 处理添加/删除,请确保将 allow_add 设置为 true

【讨论】:

    【解决方案2】:

    我遇到了同样的问题,但我不确定是不是同一个原因。

    你的实体的具有 OneToMany 关系的属性必须在末尾有一个“s”。这样在“handleRequest”中(留下一个黑盒子,我没有往里面看),symfony 会找到你的“addxxx”而没有“s”。

    在示例 'Task - Tag' 中,他声明了“标签”,但声明了 getTag。

    在您的情况下,我认为您将 $phone 更改为 $phones 并且方法如下:

    public function setPhones($phones){}
    public function addPhone(Phone $phone){}
    

    到你的表单搜索的方法名称,只需在你的实体中临时删除setter并提交你的表单,symfony会告诉你。

    希望这能帮到你:)

    【讨论】:

      【解决方案3】:

      这个答案对应于 Symfony 3,但我确信这也适用于 Symfony 2。此外,这个答案更像是一个参考,而不是特别解决 OP 的问题(我不明确)

      ..Symfony/Component/PropertyAccess/PropertyAccessor.php 上,方法writeProperty 负责调用setXXXXsaddXXXremoveXXXX 方法。

      所以这里是它寻找方法的顺序:

      1. 如果实体是arrayTraversable 的实例(ArrayCollection 是),那么一对

        • addEntityNameSingular()
        • removeEntityNameSingular()

          参考来源:

          if (is_array($value) || $value instanceof \Traversable) {
              $methods = $this->findAdderAndRemover($reflClass, $singulars);
          
              if (null !== $methods) {
                  $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
                  $access[self::ACCESS_ADDER] = $methods[0];
                  $access[self::ACCESS_REMOVER] = $methods[1];
              }
          }
          
      2. 如果没有,那么:

        1. setEntityName()
        2. entityName()
        3. __set()
        4. $entity_name(应该公开)
        5. __call()

          参考来源:

          if (!isset($access[self::ACCESS_TYPE])) {
              $setter = 'set'.$camelized;
              $getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item)
          
              if ($this->isMethodAccessible($reflClass, $setter, 1)) {
                  $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
                  $access[self::ACCESS_NAME] = $setter;
              } elseif ($this->isMethodAccessible($reflClass, $getsetter, 1)) {
                  $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
                  $access[self::ACCESS_NAME] = $getsetter;
              } elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
                  $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
                  $access[self::ACCESS_NAME] = $property;
              } elseif ($access[self::ACCESS_HAS_PROPERTY] && $reflClass->getProperty($property)->isPublic()) {
                  $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
                  $access[self::ACCESS_NAME] = $property;
              } elseif ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2)) {
                  // we call the getter and hope the __call do the job
                  $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC;
                  $access[self::ACCESS_NAME] = $setter;
              } else {
                  $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
                  $access[self::ACCESS_NAME] = sprintf(
                      'Neither the property "%s" nor one of the methods %s"%s()", "%s()", '.
                      '"__set()" or "__call()" exist and have public access in class "%s".',
                      $property,
                      implode('', array_map(function ($singular) {
                          return '"add'.$singular.'()"/"remove'.$singular.'()", ';
                      }, $singulars)),
                      $setter,
                      $getsetter,
                      $reflClass->name
                  );
              }
          }
          

      为了回答 OP 的问题,根据上述信息,symfony 的 PropertyAccessor 类无法正确读取您的 addXXremoveXX 方法。潜在的原因可能是未标识为 arrayArrayCollection,这必须从实体的构造函数中完成

      public function __construct() {
           $this->address = new ArrayCollection();
           // ....
      }
      

      【讨论】:

      • @Downvoter,我想知道答案有什么问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-07
      相关资源
      最近更新 更多