Symfony2表单集合不调用addxxx和removexxx,即使'by_reference'=> false

问题描述 投票:0回答:3

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

客户实体具有 addPhone/removePhone 和 addAddress/removeAddress“加法器”。

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

实体函数addPhone/removePhone和addAddress/removeAddress在表单提交后不会被调用,因此CustomerPhone和CustomerAddress在持久化后没有父ID。

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

更新1.

在@Baig suggestion之后,现在我调用了addPhone/removePhone“加法器”,但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()
        ));
    }

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

客户电话类型.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';
    }
}
php forms symfony collections doctrine
3个回答
9
投票

对我来说,这个问题最终通过添加

getXXX
得到解决,它将集合返回到
PropertyAccessor
。如果没有这个,你就会一直想知道为什么不调用
addXXX
removeXXX

所以请确保:

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

5
投票

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

..Symfony/Component/PropertyAccess/PropertyAccessor.php
上,方法
writeProperty
负责调用
setXXXXs
addXXX
&
removeXXXX
方法。

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

  1. 如果实体是

    array
    Traversable
    的实例(
    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类无法正确读取您的

addXX
removeXX
方法。潜在的原因可能是未识别为
array
ArrayCollection
,这必须由实体的构造函数来完成

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

1
投票

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

具有 OneToMany 关系的实体属性必须在末尾有一个“s”。这样在“handleRequest”中(让它成为一个黑框,我没有在里面查找),symfony会找到你的“addxxx”而不需要“s”。

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

就您而言,我认为您将 $phone 更改为 $phones,方法如下:

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

对于您的表单搜索的方法名称,只需删除您实体中的临时设置器并提交您的表单,symfony 会告诉您。

希望这能帮助你:)

© www.soinside.com 2019 - 2024. All rights reserved.