我有 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';
}
}
对我来说,这个问题最终通过添加
getXXX
得到解决,它将集合返回到 PropertyAccessor
。如果没有这个,你就会一直想知道为什么不调用 addXXX
或 removeXXX
。
所以请确保:
by_reference
在现场设置为 false
,adder
和 remover
方法,getter
可供PropertyAccessor
检查by_reference
是否可以使用,prototype
来通过 Javascript 处理添加/删除,请确保 allow_add
设置为 true
。这个答案对应于 Symfony 3,但我确信这也适用于 Symfony 2。此外,这个答案更多的是作为参考,而不是特别解决OP的问题(我不清楚)
在
..Symfony/Component/PropertyAccess/PropertyAccessor.php
上,方法 writeProperty
负责调用 setXXXXs
或 addXXX
& removeXXXX
方法。
所以这是它查找方法的顺序:
如果实体是
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];
}
}
如果没有的话:
setEntityName()
entityName()
__set()
$entity_name
(应该公开)__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();
// ....
}
我也遇到了同样的问题,但我不确定是否是同一原因。
具有 OneToMany 关系的实体属性必须在末尾有一个“s”。这样在“handleRequest”中(让它成为一个黑框,我没有在里面查找),symfony会找到你的“addxxx”而不需要“s”。
在示例“Task - Tag”中,他声明了“tags”但 getTag。
就您而言,我认为您将 $phone 更改为 $phones,方法如下:
public function setPhones($phones){}
public function addPhone(Phone $phone){}
对于您的表单搜索的方法名称,只需删除您实体中的临时设置器并提交您的表单,symfony 会告诉您。
希望这能帮助你:)