false I have a Customer object and two one-to-many Customer...">

Symfony2 forms collection does not call addxxx and removexxx, even if "by_reference" => false

I have a Customer object and two one-to-many CustomerPhone and CustomerAddress relationships.

The Customer object has addPhone / removePhone and addAddress / removeAddress "adders".

CustomerType collection options have 'by_reference' => false for both collections.

Entity functions addPhone / removePhone and addAddress / removeAddress are not called after the form is submitted, therefore CustomerPhone and CustomerAddress do not have a parent identifier after saving.

Why, when adding a submit form, the address / delete phone and addAddress / removeAddress cannot be added?

UPD 1.

After the @Baig sentence , I now have addPhone / removePhone "adders", but addAddress / removeAddress is not. It doesnโ€™t work out why, because they are identical.

# 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; 

The same file "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); } 

relations:

 # 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 Form:

 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') ; } 

controller.

 # 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. Check if addAddress is called.

 /** * 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'; } } 
+5
source share
3 answers

This answer is consistent with Symfony 3, but I'm sure it applies to Symfony 2. Also, this answer is more a link than, in particular, an OP problem (which I don't understand)

In ..Symfony/Component/PropertyAccess/PropertyAccessor.php the writeProperty method writeProperty responsible for calling the setXXXXs or addXXX and removeXXXX .

So, here is the order in which he searches for a method:

  • If the object is an array or an instance of Traversable (which is an ArrayCollection ), then a pair

    • addEntityNameSingular()
    • removeEntityNameSingular()

      Source for reference:

       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]; } } 
  • If not, then:

    • setEntityName()
    • entityName()
    • __set()
    • $entity_name (Must be publicly available)
    • __call()

      Source for reference:

       if (!isset($access[self::ACCESS_TYPE])) { $setter = 'set'.$camelized; $getsetter = lcfirst($camelized); // jQuery style, eg 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 ); } } 

To answer the OP problem, based on the above information, the symfony PropertyAccessor class will not be able to read your addXX and removeXX method removeXX . A potential reason may be that it is not identified as an array or ArrayCollection , which must be executed from the constructor of the object

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

For me, this was finally resolved by adding getXXX , which returns the collection in PropertyAccessor . Without this, you continue to wonder why addXXX or removeXXX not called.

Therefore, make sure that:

  • The by_reference parameter by_reference set to false in the field,
  • You have an adder and remover method on the owner side of the relationship,
  • getter is available for PropertyAccessor to check if by_reference can be used,
  • If you want to use prototype to handle add / remove using Javascript, make sure allow_add set to true .
+2
source

I had the same problem, but I'm not sure if this is the same reason.

I get my entity attribute, which has a one-way connection with the relationship "OneToMany", at the end should be "s". So in "handleRequest" (leave it a black box, I did not search inside), symfony will find your "addxxx" without "s".

In the Task - Tag example, he declared "tags", but getTag.

In your case, I would think that you are changing your phone to $ phones, and the method:

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

To the name of the method your form is looking for, simply remove the temporary setter in your entity and submit the form, symfony will tell you.

Hope this helps you :)

0
source

Source: https://habr.com/ru/post/1234568/


All Articles