3

I'm sorry for my bad English, I'm not a native speaker. Feel free to correct my text if needed.

The question is really simple (it's in the end of this text), but I've written a rationale with some research and tests.

Rationale

If you want, you can skip this rationale and jump directly to the question itself.

I've been trying for several hours on trying to get a ManyToMany relationship to persist from the inverse side using doctrine:generate:entities and doctrine:generate:crud on Symfony2 console.

From the owning side, the relationship is saved in the database with the generated crud out of the box, but not from the inverse side (this is expected: http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#owning-side-and-inverse-side)

What I want is to make it work from the inverse side as well without changing the autogenerated controllers; I'd like to change only the model (entity).

The easy way would be to add a couple of custom code lines to the controller:

// Controller that works the way I want
// Controller/AlunoController.php

...

public function createAction(Request $request)
{
    $entity  = new Aluno();
    $form = $this->createForm(new AlunoType(), $entity);
    $form->bind($request);

    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();

        // begin custom code
        foreach ($entity->getResponsaveis() as $responsavel) {
            $responsavel->addAluno($entity);
        }
        // end custom code

        $em->persist($entity);
        $em->flush();

        return $this->redirect($this->generateUrl('aluno_edit', array('id' => $entity->getId())));
    }

    return array(
        'entity' => $entity,
        'form'   => $form->createView(),
    ); 
}

...

The previous code works, but it is not what I want, because those custom lines of code refer to the relationship logic, and should be centralized in the Entity itself (if not, it would have to be duplicated all over the controllers that update this entity).

So, what I did next was to change my adders and removers in the Entity file to execute that required logic, adding the code to automatically update both inverse and owning side (as recommended in http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#picking-owning-and-inverse-side and https://stackoverflow.com/a/7045693/1501575)

// Entity/Aluno.php

...

/**
 * Add responsaveis
 *
 * @param \MyBundle\Entity\Responsavel $responsaveis
 * @return Aluno
 */
public function addResponsavei(\MyBundle\Entity\Responsavel $responsaveis)
{

    /* begin custom code */
    //var_dump('addResponsavei');
    $responsavel->addAluno($this);
    /* end custom code */

    $this->responsaveis[] = $responsaveis;
    return $this;
}

...

This should work, because it does work when the same code is in the controller, but it actually does not.

The problem is that the method Aluno##addResponsavei() is never being called when $form->bind($request) runs in the controller (first code sample up there) (I realized this with that var_dump() line. I've also put var_dumps in some other getters and those other methods were called as normal).

So, all the regular setters are indeed called inside $form->bind($request), but not this one. This is weird, because the method names were autogenerated by `doctrine:generate:entities', which I assumed would make $form->bind() know how to call all the setters, getters and adders.

Question

Why is $form->bind() not calling the adder method (Aluno##addResponsavei())?

Is there a special naming convention not followed by doctrine:generate:crud that is preventing the method from being found and executed?

Solution

Thanks for the comment from user1452962 and later the answer from Elnur Abdurrakhimov, I've got it to work and it is actually pretty simple.

All I had to do is to add the option 'by_reference' to false in the properties that hold the inverse side of the relationship, and suddenly addResponsavei() began to be called.

// Form/AlunoType.php

...

$builder
        ->add('nome')
        ->add('cidade_natal')
        ->add('nascimento')
        ->add('email')
        ->add('endereco')
        ->add('nome_sem_acento')
        ->add('data_hora_cadastro')
        ->add('responsaveis', null, array('by_reference' => false))
        ->add('turmas', null, array('by_reference' => false))

...

This way, the relationship logic is gone from the controller, and that what I was looking for. Thank you guys.

Community
  • 1
  • 1
pagliuca
  • 1,129
  • 13
  • 19
  • Well, the setNome() method is called on $form->bind(), and addResponsavei() should be called in that same time. I think $em->persist() only copies the reference of the object to the entity manager, but does not change it in any other way. – pagliuca Feb 27 '13 at 03:04
  • 2
    http://symfony.com/doc/current/reference/forms/types/choice.html#by-reference might help you if i understood your question right. – user1452962 Feb 27 '13 at 07:36

1 Answers1

9

You need to set the by_reference option to false in order for adders to be called.

Elnur Abdurrakhimov
  • 44,533
  • 10
  • 148
  • 133
  • Thank you very much! Problem solved! I've updated my question to give some details about where to set this option. – pagliuca Feb 27 '13 at 13:37
  • 2
    @Elnur: If only this answer could be posted in BIG, bold typeface across the web. This very obscure setting can save A LOT of headaches when persisting collections on the inverse side of relationships. Thank you VERY much. – Mike Sep 16 '13 at 21:49