我有Customer实体和两个一对多关系CustomerPhone和CustomerAddress。
Customer实体具有addPhone / removePhone和addAddress / removeAddress“adders”。
CustomerType集合选项具有'by_reference'=>这两个集合都是假的。
实体函数addPhone / removePhone和在表单提交后未调用的addAddress / removeAddress,因此CustomerPhone和CustomerAddress在持久化后没有父ID。
为什么可以在表单提交时调用addPhone / removePhone和addAddress / removeAddress?
UPD 1。
@Baig suggestion之后我现在调用了addPhone / removePhone“adders”,但是没有添加地址/删除地址。无法理解,因为它们完全相同。
# 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;
CustomerType表单:
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。
CustomerAddressType.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';
}
}
答案 0 :(得分:3)
这个答案对应于Symfony 3,但我相信这也适用于Symfony 2。此答案更多是作为参考,而不是特别解决OP的问题(我不清楚)
在..Symfony/Component/PropertyAccess/PropertyAccessor.php
上,方法writeProperty
负责调用setXXXXs
或addXXX
&amp; 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();
// ....
}
答案 1 :(得分:3)
对我来说,这最终通过添加getXXX
来解决,PropertyAccessor
将集合返回到addXXX
。如果没有这个,你会继续想知道为什么removeXXX
或by_reference
没有被调用。
所以请确保:
false
在字段adder
remover
和getter
方法,PropertyAccessor
可以访问by_reference
,以检查是否可以使用prototype
,allow_add
来处理通过Javascript添加/删除的问题,请确保将true
设置为case_insensitive_equals
。答案 2 :(得分:1)
我有同样的问题,但我不确定这是同一个原因。
我的实体属性具有OneToMany关系,最后必须有's'。所以在“handleRequest”(留下一个黑盒子,我没有在里面查找),symfony会发现你的“addxxx”没有“s”。
在示例'Task - Tag'中,他声明了“tags”但是getTag。
在您的情况下,我认为您将$ phone更改为$ phone,方法如下:
public function setPhones($phones){}
public function addPhone(Phone $phone){}
对于您的表单搜索方法的名称,只需删除您实体中的临时设置者并提交您的表单,symfony会告诉您。
希望这会帮助你:)