Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

removeElement() and clear() doesn't work in doctrine 2 with array collection property

I'm trying to get some simple CRUD done with doctrine 2 but when it's time to update a record with one property set as an array collection I don't seem to get removeElement() to work as it's supposed to. I even tried doing it in this ridiculously ugly way:

foreach($entity->getCountries() as $c) {
        $entity->getCountries()->removeElement($c);
        $this->em->persist($entity);
        $this->em->flush();
}

and it didn't work... Anyone knows how to handle this? I've asked for a solution to this in many different forms and haven't got a good response so far... seems there's lack of good examples of Doctrine 2 CRUD handling. I'll post more code at request.

Edit

//in user entity
/**
 * 
 * @param \Doctring\Common\Collections\Collection $property
 * @OneToMany(targetEntity="Countries",mappedBy="user", cascade={"persist", "remove"})
 */
private $countries;

//in countries entity
/**
 *
 * @var User
 * @ManyToOne(targetEntity="User", inversedBy="id") 
 * @JoinColumns({
 *  @JoinColumn(name="user_id", referencedColumnName="id")
 * })
 */
private $user;
like image 746
la_f0ka Avatar asked Jun 23 '11 23:06

la_f0ka


2 Answers

I do something similar in a project with Events which have participants not unlike your User/Country relationship. I will just lay out the process and you can see if there's anything you are doing differently.

On the Participant entity

/**
 * @ManyToOne(targetEntity="Event", inversedBy="participants", fetch="LAZY")
 * @JoinColumn(name="event_id", referencedColumnName="id", nullable="TRUE")
 * @var Event
 */
protected $event;

On the Event entity:

/**
 * @OneToMany(targetEntity="Participant", mappedBy="event")
 * @var \Doctrine\Common\Collections\ArrayCollection
 */
protected $participants;

Also in Event#__constructor I initialize like this:

$this->participants = new \Doctrine\Common\Collections\ArrayCollection();

Here is how I update an event:

public function update(Event $event, Event $changes)
{
    // Remove participants
    $removed = array();
    foreach($event->participants as $participant)
    {
        if(!$changes->isAttending($participant->person))
        {
            $removed[] = $participant;
        }
    }

    foreach($removed as $participant)
    {
        $event->removeParticipant($participant);
        $this->em->remove($participant);
    }

    // Add new participants
    foreach($changes->participants as $participant)
    {
        if(!$event->isAttending($participant->person))
        {
            $event->addParticipant($participant);
            $this->em->perist($participant);
        }
    }

    $event->copyFrom($changes);
    $event->setUpdated();
    $this->em->flush();
}

The methods on the Event entity are:

public function removeParticipant(Participant $participant)
{
    $this->participants->removeElement($participant);
    $participant->unsetEvent();
}

public function addParticipant(Participant $participant)
{
    $participant->setEvent($this);
    $this->participants[] = $participant;
}

The methods on the Participant entity are:

public function setEvent(Event $event)
{
    $this->event = $event;
}

public function unsetEvent()
{
    $this->event = null;
}

UPDATE: isAttending method

/**
 * Checks if the given person is a 
 * participant of the event
 * 
 * @param Person $person
 * @return boolean 
 */
public function isAttending(Person $person)
{
    foreach($this->participants as $participant)
    {
        if($participant->person->id == $person->id)
            return true;
    }

    return false;
}
like image 80
rojoca Avatar answered Sep 20 '22 02:09

rojoca


New answer

In your countries entity, should you not have:

@ManyToOne(targetEntity="User", inversedBy="countries") 

instead of inversedBy="id"?

Initial answer

You need to set the countries field in your entity as remove cascade. For example, on a bidirectional one to many relationship:

class Entity
{
    /**
     * 
     * @OneToMany(targetEntity="Country", mappedBy="entity", cascade={"remove"})
     */
    private $countries;
}

This way, when saving your entity, doctrine will also save changes in collections attached to your entity (such as countries). Otherwise you have to explicitly remove the countries you want to remove before flushing, e.g.

$this->em()->remove($aCountry);

This is also valid for persist, merge and detach operations. More information here.

like image 31
faken Avatar answered Sep 21 '22 02:09

faken