Skip to content

Instantly share code, notes, and snippets.

@webdevilopers
Last active August 29, 2015 14:18
Show Gist options
  • Save webdevilopers/65716f36ac2249e5e899 to your computer and use it in GitHub Desktop.
Save webdevilopers/65716f36ac2249e5e899 to your computer and use it in GitHub Desktop.
Deep clone Doctrine Entity with related collections
<?php
/**
* @ORM\Entity
* @ORM\Table(name="branches")
*/
class Branch
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string")
*/
private $name;
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Location", mappedBy="branch", cascade={"all"})
* @ORM\OrderBy({"createdAt" = "ASC"})
*/
private $locations;
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Contract", mappedBy="branch")
*/
private $contracts;
public function __construct()
{
$this->locations = new \Doctrine\Common\Collections\ArrayCollection();
$this->contracts = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @see http://doctrine-orm.readthedocs.org/en/latest/cookbook/implementing-wakeup-or-clone.html
*/
public function __clone() {
return; // Error: Maximum execution time of 60 seconds exceeded with ~10 locations
if ($this->id) {
$locations = $this->getLocations();
$this->locations = new \Doctrine\Common\Collections\ArrayCollection();
if (!$locations->isEmpty()) {
foreach ($locations as $location) {
$this->addLocation(clone $location);
}
}
// $this->locations = clone $this->locations;
// $this->locations->setOwner($this, $this->locations->getMapping());
}
$this->contracts->clear();
}
/**
* Add contracts
*
* @param \AppBundle\Entity\Contract $contracts
* @return Branch
*/
public function addContract(\AppBundle\Entity\Contract $contracts)
{
$this->contracts[] = $contracts;
return $this;
}
/**
* Remove contracts
*
* @param \AppBundle\Entity\Contract $contracts
*/
public function removeContract(\AppBundle\Entity\Contract $contracts)
{
$this->contracts->removeElement($contracts);
}
/**
* Get contracts
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getContracts()
{
return $this->contracts;
}
}
<?php
class BranchController extends Controller
{
/**
* @Route("/branch/clone")
* @Security("has_role('ROLE_ADMIN')")
* @Template()
*/
public function cloneAction()
{
$em = $this->getDoctrine()->getManager();
$branch = $em->getRepository('AppBundle:Branch')->find(1);
dump($branch);
echo "Old ID:" . $branch->getId();
$newBranch = clone $branch;
dump($newBranch);
$em->persist($newBranch);
$em->flush();
dump($newBranch);
echo "<br>New Id:" . $newBranch->getId();
return array();
}
}
<?php
/**
* @ORM\Entity
* @ORM\Table(name="contracts")
* @ORM\HasLifecycleCallbacks
*/
class Contract
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=45)
*/
private $number;
/**
* @ORM\Column(type="text", name="custom_fields", nullable=true)
*/
private $customFields;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Branch", inversedBy="contracts")
*/
private $branch;
/**
* @ORM\PrePersist
* @ORM\PreUpdate
*
* Legacy workaround converting custom fields to yaml format utf8 encoded
*/
public function convertCustomFieldsArrayToYaml()
{
$customFields = $this->getCustomFields();
$customFieldsYaml = yaml_emit($customFields, YAML_UTF8_ENCODING);
$this->setCustomFields($customFieldsYaml);
}
/**
* @ORM\PostLoad
*
* Legacy workaround converting custom fields yaml to aray
*/
public function convertCustomFieldsYamlToArray()
{
$customFieldsYaml = $this->getCustomFields();
if (empty($customFieldsYaml)) { return; }
$customFields = yaml_parse($customFieldsYaml);
$this->setCustomFields($customFields);
}
}
<?php
/**
* @ORM\Entity
* @ORM\Table(name="locations")
*/
class Location
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=45)
*/
private $name;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Branch", inversedBy="locations")
* @ORM\JoinColumn(name="branch_id", referencedColumnName="id", nullable=true)
*/
private $branch;
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Bundle", mappedBy="location")
*/
private $bundles;
}
@webdevilopers
Copy link
Author

This clone command executes one INSERT for the new Branch Entity

 INSERT INTO branches
# ...

But also ~1000 UPDATE queries for the PostLoad event on the original Branchs related Contract association:

UPDATE contracts SET custom_fields = ?, [...] WHERE id = ? 

Without the PostLoad event there are no extra queries.

@webdevilopers
Copy link
Author

Cloning does not automatically create copies of related OneToMany collections:

/* @ORM\OneToMany(targetEntity="AppBundle\Entity\Location", mappedBy="branch", cascade={"all"}) */

Even when adding persist to cascade.

You will have to add the related entity collection inside the __clone method.

Unfortunately in my example a count of 10 Locations seem to create an overload.
Error: Maximum execution time of 60 seconds exceeded
as the cloned Location Entity is loading every Collection of itself too.

@webdevilopers
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment