How to create a clone of an entity collection with

2019-09-10 05:27发布

I've been trying to figure out how to get this to work for along time but without any luck. Due to a complex logic in an app I'm working on, I need to create an isolated clone of a entity collection without preserving what so ever relation to the database. Whatever changes I do on the cloned collection should not be tracked by Doctrine at all and should be treated as if it doesn't exist at all.

Here's an example code:

 /* 
  * @ORM\Entity()
  */
class Person
{
    /**
     * @var integer
     *
     * @ORM\Id
     * @ORM\Column(name="person_id", type="integer",nullable=false)
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    public $id;

    /**
     * @var ArrayCollection
     *
     * @ORM\OneToMany(targetEntity="Car", mappedBy="person", cascade={"persist"})
     */
    public $cars;
}



/**
 * @ORM\Entity()
 * @ORM\HasLifecycleCallbacks()
 */
class Car
{
    /**
     * @var integer
     *
     * @ORM\Id
     * @ORM\Column(name="car_id", type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /*
     * @ORM\JoinColumn(name="person_id", referencedColumnName="person_id", nullable=true)
     * @ORM\ManyToOne(targetEntity="Person", inversedBy="cars", cascade={"persist"})
     */
    private $person;

}

I've already tried the following code in my controller to store the collection into the session but it still somehow stores the relationships:

 $tmp = clone $person;
 $this->get('session')->set('carCollection', $tmp->getCars());

 $tmpCars = clone $person->getCars();
 $tmpCollection = new ArrayCollection();
 foreach($tmpCars as $car) {
     $tmpCollection->add(clone $car);
 }

 $this->get('session')->set('carCollection', $tmpCollection);

 $tmpCars = clone $person->getCars();
 $tmpCollection = new ArrayCollection();
 foreach($tmpCars as $car) {
     $clone = clone $car;
     $entityManager->detach($car);
     $tmpCollection->add(clone $clone);
 }

 $this->get('session')->set('carCollection', $tmpCollection);

Apparently I'm doing something wrong here because I end up having more results in the Car collection when flushing the entity even though the collection itself has the correct number of records. I have a suspicion that somewhere in the chain Doctrine doesn't compute correctly what needs to be done.

Any ideas or directions on how to solve or debug this?

Follow-up question: When retrieving back the cloned collection from the session will it still be an isolated clone or Doctrine will try merge it back?

2条回答
倾城 Initia
2楼-- · 2019-09-10 05:31

I think hydrators/extractors would be the way to go for you.

They can extract the data from an entity and you can pass them to a newly created instance of that entity via the hydrator. The only thing you'll need to do in between is the unsetting of the relation properties. They should be fetchable via a metadata class via doctrine somehow.

查看更多
Juvenile、少年°
3楼-- · 2019-09-10 05:52

I'm writing this answer to give directions to anybody who might have similar issues. I couldn't find many topics or documentation in this manner which is why I decided to share my experience. I am no deep expert on Doctrine an how it internally works, so I won't go into big details of "how it works". I will rather focus on the end result.

Storing entities which have relations to other entities into a session is quite problematic. When you retrieve it from the session, Doctrine loses track of the relationships (OneToMany, ManyToOne, etc). This leads to some undesired effects:

  1. Doctrine wrongly decides to insert a new record of an existing entity.
  2. Doctrine might throw exceptions such as A new entity was found through the relationship 'Acme\MyBundle\Entity\Person#cars' that was not configured to cascade persist operations for entity: Opel. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}). and at least 2 other types of exceptions which might seem totally irrelevant at first.

Apparently when fetching a result from the database and it "as-is" in your session things get really messy, specially if the entity has relations to other entities (which was my case). Pay big attention if you have entity relationships - they might need to be "refreshed" if you start getting strange exceptions.

There are a couple of ways to overcome this issue. One of which is to use the data sent via the form (as @flec suggested) by using $myForm->getData(). This approach might work well for you, but unfortunately it was not the case with me (too complex to explain).

What I ended up doing was implementing the \Serializable in the entity. I also created a method called __toArray() which converted my entity into an array. What data you return in the __toArray() method is totally up to you and your business logic. The array data is stored into the session and you use it to re-create a fresh object with all necessary relations.

Hope this helps somebody.

查看更多
登录 后发表回答