Major Productions
Major Productions

Reputation: 6062

Doctrine 2 ArrayCollections - am I missing something?

I have a bidirectional many-to-one relationship. Let's go with Foo and Bar. Foo has the following:

class Foo
{
    /**
     * @ORM\OneToMany(targetEntity="Bar", mappedBy="foo", cascade={"all"}, orphanRemoval=true)
     */
    private $bars;

    public function __construct()
    {
        $this->bars = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * @param mixed $bars
     */
    public function setBars($bars)
    {
        $this->bars = $bars;
    }

    /**
     * @param Bar $bar
     */
    public function addBar(Bar $bar)
    {
        $this->bars->add($bar);
    }

    /**
     * @return mixed
     */
    public function getBars()
    {
        return $this->bars;
    }
}

And Bar:

class Bar
{
    /**
     * @ORM\Id()
     * @ORM\ManyToOne(targetEntity="Foo", inversedBy="bars")
     * @ORM\JoinColumn(name="foo_id", referencedColumnName="id", nullable=false)
     */
    protected $foo;

    /**
     * @param Foo $foo
     */
    public function setFoo(Foo $foo)
    {
        $this->foo = $foo;
    }

    /**
     * @return Foo
     */
    public function getFoo()
    {
        return $this->foo;
    }
}

Okay, so with the classes above, when I write:

$foo = new Foo();

$bar1 = new Bar();
$bar2 = new Bar();

$foo->addBar($bar1);
$foo->addBar($bar2);

And try to persist, I get an error about how each Bar's foo property is null:

enter image description here

Similarly, if I flip it around to:

$foo = new Foo();

$bar1 = new Bar();
$bar2 = new Bar();

$bar1->setFoo($foo);
$bar2->setFoo($foo);

I can persist, but then my Foos newly generated PersistentCollection for that property is empty:

enter image description here

Is this intended behavior? I'm used to other ORMs on different platforms wiring up the bidirectional relationship upon assignment, so I'm not sure if this is normal for Doctrine/PHP, or if my annotations are messed up (which the error message above leads me to believe).

So, any help would be greatly appreciated.

Upvotes: 1

Views: 141

Answers (4)

ins0
ins0

Reputation: 3928

add this to your addBar function. you missed the back ref. from bar entity to the foo entity

/**
* @param Bar $bar
*/
public function addBar(Bar $bar)
{
    $bar->setFoo($this);
    $this->bars->add($bar);
}

Upvotes: 0

qooplmao
qooplmao

Reputation: 17759

Your main issue is that you are not setting Foo in Bar when you add a Bar.

To auto set that you can do the following (I've also added a check to see if the Bar has already been added and a return to chain set's)..

/**
 * Add bar
 *
 * @param Bar $bar
 * @return $this
 */
public function addBar(Bar $bar)
{
    if (!$this->bars->contains($bar)) {
        $this->bars->add($bar);
        $bar->setFoo($this)
    }

    return $this;
}

You should probably get rid of the setBars as I can see that causing an issue if you used set but didn't use an array collection.

Also you should add a remove bar so you don't have get, remove, and then reset every time, like..

/**
 * Remove bar
 *
 * @param Bar $bar
 * @return $this
 */
public function removeBar(Bar $bar)
{
    if ($this->bars->contains($bar)) {
        $this->bars->removeElement($bar);
        $bar->setFoo(null);
    }

    return $this;
}

Which would mean your setFoo would need to be able to accept a null value..

/**
 * Set foo
 *
 * @param Foo $foo
 */
public function setFoo(Foo $foo = null)
{
    $this->foo = $foo;
}

Upvotes: 1

AlixB
AlixB

Reputation: 1238

You need to set the two sides of the association, for instance

/**
 * @param Bar $bar
 */
public function addBar(Bar $bar)
{
    if(!$this->bars->contains($bar)) {
        $this->bars->add($bar);
        $bar->setFoo($this);
    }
}
----
/**
 * @param Foo $foo
 */
public function setFoo(Foo $foo)
{
    if($this->foo !== $foo) {
        $this->foo = $foo;
        $foo->addBar($this);
    }
}

At the moment your only setting one side of the association and Doctrine does not do the rest for you.

If you don't want to modify your add/remove methods you have to

 $bar->setFoo($foo)
 $foo->addBar($bar)

Everytime you want to add a foo or a bar

EDIT:

If you want to use only one side to do all the operation, read about owning/inverse side. Maybe it could help with your problem. But I'm not sure it will do the correct bidir link between your entities.

Upvotes: 1

Juan Sosa
Juan Sosa

Reputation: 5280

The error message you are getting is telling that an entity can't be persisted without filling the id field. Also, it looks like you are missing id field in your Foo entity.

Anyway, the id field is probably already in your database (autogenerated by Doctrine), but no autoincrement is set so filling it manually is mandatory before persist.

Try adding the following code in your entities:

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

Hope it helps.

Upvotes: 0

Related Questions