1

In this problem Deep Cloning I thought my issue was due to a deep/shallow copy.

I have vainly tested clone() and unserialize(serialize()) methods.

So I tried to write my own clone function using all setters/getters and then I realized what was really my issue, a persisting one.

The fact is I already succeeded in persisting a clone of my entity, in another context.

The main difference between my two situations is that in one case my original object is already managed by doctrine (this is the case where i'm blocked), and in the second case, my original object is just persisted, I don't have called flush() yet (and it's works fine).

So this is the situation when persist do not persist the many to many relations :

public function duplicateCourseAction(Request $request) {
    if ($this->getRequest()->isXmlHttpRequest() == false) {
        return new Response("Bad request", 405);
    }

    $em = $this->getDoctrine()->getManager();
    $parameters = $request->request->all();
    $course = $em->getRepository('EntTimeBundle:Course')->findOneById($parameters['id']);
    $duplicate = clone $course;
    $duplicate->setId(null);
    $duplicate->setDate(new \DateTime($parameters['date']));
    $em->persist($duplicate);
    $em->flush();
    return new Response("200");
}

And this is the situation whe it's works like a charm

        $em->persist($obj);
        while ($new_date < $up_to) {
            if ($this->isAvailable($holidays, $new_date)) {
                $new_course = clone $obj;
                $new_course->setDate($new_date);
                $new_course->setUuid($uuid);
                $new_date = clone $new_date;
                $em->persist($new_course);
            }
            $new_date->modify($modifier);
        }
        $em->flush();

Why is it working for only one situation ? There are almost identical...

EDIT 1 : Entities Mapping

-Course :

class Course {

/**
 * @ORM\Id
 * @ORM\Column(type="integer")
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;

/**
 * @ORM\Column(type="string", length=50, unique=true, nullable=true)
 */
protected $name;

/**
* @ORM\JoinColumn(onDelete="CASCADE")
* @ORM\ManyToOne(targetEntity="Ent\HomeBundle\Entity\Campus", inversedBy="courses")
* @Assert\NotBlank
**/
protected $campus;

/**
* @ORM\JoinColumn(onDelete="CASCADE")
* @ORM\ManyToOne(targetEntity="Ent\HomeBundle\Entity\Room", inversedBy="courses")
* @Assert\NotBlank
**/
protected $room;

/**
 * @ORM\ManyToMany(targetEntity="Ent\UserBundle\Entity\User", inversedBy="courses", cascade={"persist"})
 * @ORM\JoinTable(name="course_teacher",
 *  joinColumns={@ORM\JoinColumn(name="course_id", referencedColumnName="id", onDelete="CASCADE")},
 *  inverseJoinColumns={@ORM\JoinColumn(name="teacher_id", referencedColumnName="id", onDelete="CASCADE")}
 * )
 * @Assert\NotBlank
 */
private $teachers;

/**
* @ORM\JoinColumn(onDelete="CASCADE")
* @ORM\ManyToOne(targetEntity="Matter", inversedBy="courses")
* @Assert\NotBlank
**/
protected $matter;

/**
* @ORM\JoinColumn(onDelete="CASCADE")
* @ORM\ManyToOne(targetEntity="\Ent\UserBundle\Entity\Grade", inversedBy="courses")
* @Assert\NotBlank
**/
protected $grade;

/**
* @ORM\Column(type="datetime")
* @Assert\NotBlank
**/
protected $date;

/**
* @ORM\Column(type="time")
* @Assert\NotBlank
**/
protected $duration;

/**
 * @ORM\Column(type="string", length=30, nullable=true)
 */
protected $uuid;

/**
 * @ORM\ManyToMany(targetEntity="Ent\TimeBundle\Entity\Course", mappedBy="courses")
 * @Exclude
*/
protected $alerts;

public function __toString() {
    if (empty($this->getName())) {
        $string = $this->getMatter().' - '.$this->getRoom().' - ';
        foreach ($this->getTeachers() as $count => $teacher) {
            $string = $string . $teacher;
            if ($count < count($this->getTeachers()) - 1) {
                $string = $string . ', ';
            }
        }
        return $string;
    } else {
        return $this->getName().' - '.$this->getRoom();
    }
}
/**
 * Constructor
 */
public function __construct() {
    $this->teachers = new ArrayCollection();
    $this->alerts = new ArrayCollection();
}

public function __clone() {
    // $this->id = null;
    // $this->teachers = clone $this->teachers;
}
}

-User (Teacher) :

class User implements UserInterface, \Serializable {
/**
 * @ORM\Column(type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;

/**
 * @ORM\Column(type="string", length=30)
 * @Assert\NotBlank
 */
protected $firstName;

/**
 * @ORM\Column(type="string", length=30)
 * @Assert\NotBlank
 */
protected $lastName;

/**
 * @ORM\Column(type="string", length=70, unique=true)
 * @Assert\NotBlank
 */
protected $username;

/**
 * @Gedmo\Slug(fields={"username"}, updatable=false)
 * @ORM\Column(length=50, unique=true)
 */
protected $slug;

/**
 * @ORM\Column(type="string", length=32)
 * @Exclude
 */
protected $salt;

/**
 * @ORM\Column(type="string", length=40)
 * @Exclude
 */
protected $password;

/**
 * @ORM\Column(type="string", length=255, nullable=true)
 */
protected $picture_path;

/**
 * @Assert\File(maxSize="10M", mimeTypesMessage="Please upload a valid Image")
 */
protected $picture;

/**
 * @ORM\Column(type="string", length=60, unique=true)
 * @Exclude
 * @Assert\NotBlank
 */
protected $email;

/**
 * @ORM\Column(name="is_active", type="boolean")
 */
protected $isActive;

/**
 * @ORM\ManyToOne(targetEntity="Group", inversedBy="users")
 * @ORM\JoinColumn(name="role_group", referencedColumnName="role", onDelete="CASCADE")
 */
protected $group;

/**
* @ORM\ManyToMany(targetEntity="Ent\HomeBundle\Entity\Campus", inversedBy="users")
* @Exclude
**/
protected $campuses;

/**
 * @ORM\OneToMany(targetEntity="\Ent\NewsBundle\Entity\News", mappedBy="user")
 * @Exclude
 */
protected $news;

/**
 * @ORM\ManyToMany(targetEntity="\Ent\TimeBundle\Entity\Matter", inversedBy="teachers", cascade={"persist"})
 * @ORM\JoinTable(name="user_matter",
 *  joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")},
 *  inverseJoinColumns={@ORM\JoinColumn(name="matter_id", referencedColumnName="id", onDelete="CASCADE")}
 * )
 */
protected $matters;

/**
* @ORM\ManyToMany(targetEntity="Ent\UserBundle\Entity\Grade")
* @Assert\NotBlank
* @Exclude
**/
protected $grades;

/**
* @ORM\ManyToMany(targetEntity="Ent\TimeBundle\Entity\Course", mappedBy="teachers")
* @Exclude
**/
protected $courses;

/**
* @ORM\OneToMany(targetEntity="\Ent\TimeBundle\Entity\Alert", mappedBy="teacher")
* @Exclude
**/
protected $alerts;

protected $temp;

public function __construct() {
    $this->isActive = true;
    $this->salt = md5(uniqid(null, true));
}

public function __toString() {
    return $this->getFullName();
}
}

EDIT 2 : Solution

Thanks to Paul Andrieux this is the function we made to clone my object :

public function course_deep_clone($course) {
    $em = $this->getDoctrine()->getManager();
    $clone = clone $course;

    $clone->setTeachers(array());
    $teachers = $course->getTeachers();
    foreach ($teachers as $teacher) {
        $clone->addTeacher($teacher);
    }

    return $clone;
}
Community
  • 1
  • 1

1 Answers1

1

Thing is that ManyToMany related entities are not cloned, try that:

public function duplicateCourseAction(Request $request) {
    if ($this->getRequest()->isXmlHttpRequest() == false) {
        return new Response("Bad request", 405);
    }

    $em = $this->getDoctrine()->getManager();
    $parameters = $request->request->all();
    $course = $em->getRepository('EntTimeBundle:Course')->findOneById($parameters['id']);
    $duplicate = clone $course;

    $teachers = $course->getTeachers();
    $duplicate->setTeachers($teachers);

    $duplicate->setId(null);
    $duplicate->setDate(new \DateTime($parameters['date']));
    $em->persist($duplicate);
    $em->flush();
    return new Response("200");
}

This way, you are persisting new relationship and new join table between you two entities

EDIT: Maybe its a cascading problem, what gives you this ? :

$teachers = $course->getTeachers();

foreach ($teachers as $teacher) { 
    $teacher->addCourse($duplicate); 
    $em->persist($teacher); 
}
Paul Andrieux
  • 1,836
  • 11
  • 24
  • Thank you for answering. Actually, my teachers **are setted** by the clone. If I var_dump() my duplicate immediately after clone, I can see them. There are just not persisted... –  Sep 04 '13 at 19:25
  • Other thing: is your schema is correct ? (command `php app/console doctrine:schema:validate`) – Paul Andrieux Sep 04 '13 at 22:08
  • It change nothing... I have some fails with my entities mapping, but nothing in relation with Course or User. –  Sep 09 '13 at 07:31
  • So if I can see my teachers when I var_dump(), why are they not persisted ? Any Idea ? –  Sep 10 '13 at 08:16
  • Did you try to call $duplicate->addTeacher($teacher) in a foreach loop ? – Paul Andrieux Sep 10 '13 at 12:25
  • Ok so, there is something new with what you said. I have a duplicate entry sql error so Doctrine is finally trying to create a relation between my teachers and the course. But This is a duplicate entry because doctrine insert the first course Id while I have $duplicate->setId(null)... Do you know why ? –  Sep 10 '13 at 13:07
  • When you clone an entity, doctrine cannot see the difference between both before you persisted it. Try to call persist($duplicate) just after you clone your $course – Paul Andrieux Sep 10 '13 at 13:10
  • I thought I had to persist after all the setters. But that changes nothing. my duplicate still have the same Id. –  Sep 10 '13 at 13:18
  • Persist does not modify data persistance, it just tell doctrine to register this entity and save it's changes when you call flush. So, juste to test, try to: 1 clone, 2 setId(null), 3 persist, 4 flush, 5 addTeacher($teacher), 6 persist, 7 flush. – Paul Andrieux Sep 10 '13 at 13:22
  • you can persist many times (this is rarely usefull), but each time you call flush, this execute a query, so don't do it too many times. That I asked was just for testing purpose. I think your problem is related to the relationship nature. A ManyToMany has a relationTable and it seems like when you add() a teacher to your course, the new mapping does not work. Had you checked this answer ? http://stackoverflow.com/questions/14158111/deep-clone-doctrine-entity-with-related-entities – Paul Andrieux Sep 10 '13 at 13:48
  • Yeah i already checked, nothing better. So my objects are setted. My only issue is that the main object (duplicate) doesn't have his own Id and I wonder why... –  Sep 10 '13 at 13:57
  • `$duplicate = clone $course; $duplicate->setId(null); $em->persist($duplicate); $em->flush(); $teachers = $course->getTeachers(); foreach ($teachers as $teacher) { $duplicate->addTeacher($teacher); } $duplicate->setDate(new \DateTime($parameters['date'])); $em->persist($duplicate); $em->flush();` Sorry this looks awful in a comment... –  Sep 10 '13 at 14:04
  • try $duplicate->setTeachers(array()); before you first persist $duplicate – Paul Andrieux Sep 10 '13 at 14:17
  • That works ! Thank you :) Can you explain me this situation ? –  Sep 10 '13 at 14:25
  • when you cloned your $course, $teachers collection was not cloned but collection set in $duplicate, with references to existing "course_teacher" (join table) entries. Setting this collection to empty array and reset it tell doctrine to persist new relation entries. Could you try this code : $duplicate = clone $course; $duplicate->setId(null); $duplicate->setTeachers(array()); $em->persist($duplicate); $teachers = $course->getTeachers(); foreach ($teachers as $teacher) { $duplicate->addTeacher($teacher); } $duplicate->setDate(new \DateTime($parameters['date'])); $em->flush(); – Paul Andrieux Sep 10 '13 at 14:37
  • It works fine too, so we don't need double flush, just double persist. Thanks you :) –  Sep 10 '13 at 14:40
  • Actually, we don't need double persist. And we don't need to set the Id to null. –  Sep 10 '13 at 14:46
  • What a good news, add an Edit on your question and show the final code :) – Paul Andrieux Sep 10 '13 at 14:56
  • Quand même, parler anglais entre français il faut le faire ;) Aller merci pour tout et à bientôt peut-être ! –  Sep 10 '13 at 15:03