I am trying to implement a "Clone" action on a page listing entities ("Circuits"). The workflow is the following:
- user selects an entity then selects the "Clone" action in a dropdown
- the CircuitsController::indexAction method is fired, redirects the user to the CircuitsController::editAction page and pass along the Circuit's ID and a GET parameter
$is_clone = true;
- user modifies whatever he wants, then submits
- the CircuitsController::editAction method sees that a clone request has been made, clones the Circuit entity, persist it then flush the EntityManager.
My issue here is that when the EntityManager::flush method is called, two strictly identical INSERT queries are performed, thus leading to a rollback and no new circuit persisted.
Here is the portion of code from the CircuitsController::indexAction that redirects:
case 'duplicate':
$this->denyAccessUnlessGranted('ROLE_NETWORK', null, 'Insufficient rights to perform selected action.');
if (count($formData['circuits']) != 1) {
throw new \RuntimeException('Cannot duplicate more than one item.');
}
/* 'circuits' key points to an array of circuits */
$circuit = $formData['circuits'][0];
return $this->redirect($this->generateUrl('net_dev_web_circuits_edit',
['id' => $circuit->getId(),
'type' => $type,
'clone' => true]));
My CircuitsController::editAction:
public function editAction(Request $request, Circuit $circuit, $type = 'mnms') {
$this->denyAccessUnlessGranted('ROLE_NETWORK', null, 'Insufficient rights to perform selected action.');
$em = $this->getDoctrine()->getManager();
$clone = $request->query->get('clone');
$circuitForm = $this->buildCircuitsForm($circuit, $type, ['is_clone' => $clone]);
if (empty($clone)) {
$lcmForm = $this->generateLcmForm($em, $circuit);
}
else {
$lcmForm = null;
}
if ($request->isMethod('POST')) {
if ($circuitForm->handleRequest($request)->isValid()) {
if ($clone) {
$em->detach($circuit);
$circuit = clone $circuit;
$em->persist($circuit);
$endPointRepo = $em->getRepository('NetDevCoreBundle:EndPoint');
$mnms = $circuit->getMnmsCircuit();
$endPoints = $endPointRepo->findByCircuit($mnms);
foreach ($endPoints as $endPoint) {
$device = $endPoint->getDevice(); /* @var $device Device */
$defaultEndPoint = (new EndPoint())
->setCircuit($circuit)
->setDevice($device)
->setSite($endPoint->getSite())
;
$device->addCircuit($circuit);
$em->persist($defaultEndPoint);
}
}
$em->flush();
$request->getSession()->getFlashBag()->add('notice', 'Circuit saved');
return $this->redirect($this->generateUrl('net_dev_web_circuits_edit', array('type' => $type,
'id' => $circuit->getId())));
}
/* [.. unrelated code (I hope) to get the edit form ..] */
}
Following is the __clone method of my entity:
public function __clone() {
if ($this->id) {
$this->id = null;
$this->listenPops = new ArrayCollection();
$this->endPoints = new ArrayCollection();
}
}
And finally, the two issued SQL statements:
#20 0.29 ms:
"START TRANSACTION"
Parameters: [null]
#21 0.29 ms:
INSERT INTO Circuit (
two_way, circuit_name, circuit_type,
mtu, port_bindings, user_enable,
bandwidth, tierLevel, translate,
crossconnect, header_compression,
padding_removal, untag_enable, landside_tx_duplicate_enable,
landside_tx_duplicate_vlan_id,
priority, trunk_force_mask, connection_number,
aptUserVlanIds, user_max_age, validationDate,
synchronizationDate, updatedAt,
macAddresses, vlan_id, mnms_circuit,
parent_route, customer
)
VALUES
(
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
Parameters: { 1: 1, 2: MNMS_Tier1_ZZZ_XX_3171, 3: mnms, 4: 23, 5: 1, 6: 1, 7: 100, 8: 0, 9: 0, 10: '', 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 1, 17: 0, 18: 1, 19: 'a:4:{i:1;N;i:2;N;i:3;N;i:4;N;}', 20: null, 21: '2015-08-19 11:33:42', 22: null, 23: '2015-09-07 18:48:37', 24: 'a:0:{}', 25: 233, 26: null, 27: 13, 28: 64 }
#22 0.00 ms:
INSERT INTO Circuit (
two_way, circuit_name, circuit_type,
mtu, port_bindings, user_enable,
bandwidth, tierLevel, translate,
crossconnect, header_compression,
padding_removal, untag_enable, landside_tx_duplicate_enable,
landside_tx_duplicate_vlan_id,
priority, trunk_force_mask, connection_number,
aptUserVlanIds, user_max_age, validationDate,
synchronizationDate, updatedAt,
macAddresses, vlan_id, mnms_circuit,
parent_route, customer
)
VALUES
(
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
Parameters: { 1: 1, 2: MNMS_Tier1_ZZZ_XX_3171, 3: mnms, 4: 23, 5: 1, 6: 1, 7: 100, 8: 0, 9: 0, 10: '', 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 1, 17: 0, 18: 1, 19: 'a:4:{i:1;N;i:2;N;i:3;N;i:4;N;}', 20: null, 21: '2015-08-19 11:33:42', 22: null, 23: '2015-09-07 18:48:37', 24: 'a:0:{}', 25: 233, 26: null, 27: 13, 28: 64 }
#23 3.96 ms
"ROLLBACK"
Parameters: [null]
I have been fiddling around with that but to no avail. I am wondering if this would not be caused by Doctrine trying to persist the newly cloned Entity AND the one used as a template but I have no idea how I could (dis)prove that.
Any help would be greatly appreciated.
EDIT 1:
Thanks to @user2268997's comment, I now know that the second INSERT refers to the "template" circuit. Doctrine tries to update it as well, which is not what I want (I need the original data to be left unchanged). Any way I can tell Doctrine not to update the original entity ?