geoB
geoB

Reputation: 4704

Symfony2: inserting the one without the many of a one-to-many

I continue to struggle with the Symfony/Doctrine approach to creating objects. The present issue is how best to create an object on the one side of a one-to-many without creating an object on the many side. The problem seems to stem from the many side being the owning side of the relationship and thus the determinant in whether an object is persisted.

Particulars: a client entity has a one-to-many relationship with member entity. A client can be created by itself with

        $client  = new Client();
        $form = $this->createForm(new ClientType(), $client);
        $form->bind($request);
        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($client);
            $em->flush();
            return $this->redirect($this->generateUrl('client_show', array('id' => $client->getId())));
        }

and a client plus member or members, but not by itself with

$client  = new Client();
$member = new Member();
$client->addMember($member);
$form = $this->createForm(new ClientType(), $client);
$form->bind($request);
if ($form->isValid()) {
    $em = $this->getDoctrine()->getManager();
    $member->setClient($client);
    $em->persist($member);
    $em->flush();
    return $this->redirect($this->generateUrl('client_show', array('id' => $client->getId())));

Client Entity:

class Client
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
    * @ORM\OneToMany(targetEntity="Member", mappedBy="client")
    * 
    */
    protected $members;

    public function __construct()
    {
        $this->members = new ArrayCollection();
    }

    public function getMembers()
    {
        return $this->members;
    }

    public function setMembers($members)
    {
        foreach ($members as $members)
        {
            $members->addClient($this);
        }
        $this->members = $members;
        return $this;
    }

    public function addMember(Member $member)
    {
        $this->members->add($member);
//        $member->setClient($this);
        return $this;
//        $this->members[] = $member;
    }

    public function removeMember(Member $member)
    {
        $this->members->removeElement($member);
        return $this;
    } 

Member Entity:

class Member 
{

    /**
     * @var integer $id
     *
     * @ORM\Column(name="hid", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

     /**
     * @ORM\ManyToOne(targetEntity="Client",inversedBy="members",cascade={"remove", "persist"})
     * @ORM\JoinColumn(name="clientId", referencedColumnName="id")
     * 
     */
     protected $client;

    public function setClient(Client $client)
    {
        $this->client = $client;
        return $this;
    }

    public function getClient()
    {
        return $this->client;
    }

The above code throws an error if there is no member inserted. Can a single bit of code work to add a client with or without a member be specified?

Upvotes: 2

Views: 1792

Answers (2)

geoB
geoB

Reputation: 4704

A createAction that appears to cover both situations: client with and client without a member.

public function createAction(Request $request)
{
    $client  = new Client();
    $form = $this->createForm(new ClientType(), $client);
    $form->bind($request);
    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $members = $client->getMembers();
        foreach ($members as $member)
        {
            $member->setClient($client);
            $em->persist($member);
        }
        $em->persist($client);
        $em->flush();
        return $this->redirect($this->generateUrl('client_show', array('id' => $client->getId())));
    }        
    return array(
        'entity' => $client,
        'form'   => $form->createView(),
    );

Upvotes: 1

Lighthart
Lighthart

Reputation: 3656

Bind takes the form data and writes it to the object, which then gets persisted.

You call:

$client->addMember($member);

before bind, essentially adding an empty object to your client, which does not appear to be what you want to do. In fact, I expect the first code is exactly what you want, but your ClientType() is not proper.

I think you want something like:

<?php

namespace ormed\ormedBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class ClientType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('ClientField' // one of several, probably
                     ,null
                     ,array() // specify options
                     )
                ->add('member'
                     ,null
                     ,array()  // specify options
                     )
         ;
    }
}

adding

$builder->('member' ...

will give the member selector to the form, and then your cascade persist should handle everything else. You might want to specify member as an entity type and build the list via a query builder.

Upvotes: 1

Related Questions