|
@@ -0,0 +1,563 @@
|
|
|
+<?php
|
|
|
+// Copyright (C) 2010-2017 Combodo SARL
|
|
|
+//
|
|
|
+// This file is part of iTop.
|
|
|
+//
|
|
|
+// iTop is free software; you can redistribute it and/or modify
|
|
|
+// it under the terms of the GNU Affero General Public License as published by
|
|
|
+// the Free Software Foundation, either version 3 of the License, or
|
|
|
+// (at your option) any later version.
|
|
|
+//
|
|
|
+// iTop is distributed in the hope that it will be useful,
|
|
|
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
+// GNU Affero General Public License for more details.
|
|
|
+//
|
|
|
+// You should have received a copy of the GNU Affero General Public License
|
|
|
+// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
|
|
+
|
|
|
+require_once('dbobjectiterator.php');
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * The value for an attribute representing a set of links between the host object and "remote" objects
|
|
|
+ *
|
|
|
+ * @package iTopORM
|
|
|
+ * @copyright Copyright (C) 2010-2017 Combodo SARL
|
|
|
+ * @license http://opensource.org/licenses/AGPL-3.0
|
|
|
+ */
|
|
|
+
|
|
|
+class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
|
|
+{
|
|
|
+ protected $sHostClass; // subclass of DBObject
|
|
|
+ protected $sAttCode; // xxxxxx_list
|
|
|
+ protected $sClass; // class of the links
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @var DBObjectSet
|
|
|
+ */
|
|
|
+ protected $oOriginalSet;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @var DBObject[] array of iObjectId => DBObject
|
|
|
+ */
|
|
|
+ protected $aOriginalObjects = null;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @var bool
|
|
|
+ */
|
|
|
+ protected $bHasDelta = false;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Object from the original set, minus the removed objects
|
|
|
+ * @var DBObject[] array of iObjectId => DBObject
|
|
|
+ */
|
|
|
+ protected $aPreserved;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @var DBObject[] New items
|
|
|
+ */
|
|
|
+ protected $aAdded = array();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @var DBObject[] Modified items (could also be found in aPreserved)
|
|
|
+ */
|
|
|
+ protected $aModified = array();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @var int[] Removed items
|
|
|
+ */
|
|
|
+ protected $aRemoved = array();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @var int Position in the collection
|
|
|
+ */
|
|
|
+ protected $iCursor = 0;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * ormLinkSet constructor.
|
|
|
+ * @param $sHostClass
|
|
|
+ * @param $sAttCode
|
|
|
+ * @param DBObjectSet|null $oOriginalSet
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ public function __construct($sHostClass, $sAttCode, DBObjectSet $oOriginalSet = null)
|
|
|
+ {
|
|
|
+ $this->sHostClass = $sHostClass;
|
|
|
+ $this->sAttCode = $sAttCode;
|
|
|
+ $this->oOriginalSet = $oOriginalSet ? clone $oOriginalSet : null;
|
|
|
+
|
|
|
+ $oAttDef = MetaModel::GetAttributeDef($sHostClass, $sAttCode);
|
|
|
+ if (!$oAttDef instanceof AttributeLinkedSet)
|
|
|
+ {
|
|
|
+ throw new Exception("ormLinkSet: $sAttCode is not a link set");
|
|
|
+ }
|
|
|
+ $this->sClass = $oAttDef->GetLinkedClass();
|
|
|
+ if ($oOriginalSet && ($oOriginalSet->GetClass() != $this->sClass))
|
|
|
+ {
|
|
|
+ throw new Exception("ormLinkSet: wrong class for the original set, found {$oOriginalSet->GetClass()} while expecting {$oAttDef->GetLinkedClass()}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public function GetFilter()
|
|
|
+ {
|
|
|
+ return clone $this->oOriginalSet->GetFilter();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Specify the subset of attributes to load (for each class of objects) before performing the SQL query for retrieving the rows from the DB
|
|
|
+ *
|
|
|
+ * @param hash $aAttToLoad Format: alias => array of attribute_codes
|
|
|
+ *
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ public function OptimizeColumnLoad($aAttToLoad)
|
|
|
+ {
|
|
|
+ $this->oOriginalSet->OptimizeColumnLoad($aAttToLoad);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param DBObject $oLink
|
|
|
+ */
|
|
|
+ public function AddItem(DBObject $oLink)
|
|
|
+ {
|
|
|
+ assert($oLink instanceof $this->sClass);
|
|
|
+ // No impact on the iteration algorithm
|
|
|
+ $this->aAdded[] = $oLink;
|
|
|
+ $this->bHasDelta = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param $iObjectId
|
|
|
+ */
|
|
|
+ public function RemoveItem($iObjectId)
|
|
|
+ {
|
|
|
+ if (array_key_exists($iObjectId, $this->aPreserved))
|
|
|
+ {
|
|
|
+ unset($this->aPreserved[$iObjectId]);
|
|
|
+ $this->aRemoved[$iObjectId] = $iObjectId;
|
|
|
+ $this->bHasDelta = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param DBObject $oLink
|
|
|
+ */
|
|
|
+ public function ModifyItem(DBObject $oLink)
|
|
|
+ {
|
|
|
+ assert($oLink instanceof $this->sClass);
|
|
|
+ $iObjectId = $oLink->GetKey();
|
|
|
+ $this->aModified[$iObjectId] = $oLink;
|
|
|
+ $this->bHasDelta = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function LoadOriginalIds()
|
|
|
+ {
|
|
|
+ if ($this->aOriginalObjects === null)
|
|
|
+ {
|
|
|
+ if ($this->oOriginalSet)
|
|
|
+ {
|
|
|
+ $this->aOriginalObjects = $this->oOriginalSet->ToArray();
|
|
|
+ $this->aPreserved = $this->aOriginalObjects; // Copy (not effective until aPreserved gets modified)
|
|
|
+ foreach ($this->aRemoved as $iObjectId)
|
|
|
+ {
|
|
|
+ if (array_key_exists($iObjectId, $this->aPreserved))
|
|
|
+ {
|
|
|
+ unset($this->aPreserved[$iObjectId]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+
|
|
|
+ // Nothing to load
|
|
|
+ $this->aOriginalObjects = [];
|
|
|
+ $this->aPreserved = [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The class of the objects of the collection (at least a common ancestor)
|
|
|
+ *
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ public function GetClass()
|
|
|
+ {
|
|
|
+ return $this->sClass;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The total number of objects in the collection
|
|
|
+ *
|
|
|
+ * @return int
|
|
|
+ */
|
|
|
+ public function Count()
|
|
|
+ {
|
|
|
+ $this->LoadOriginalIds();
|
|
|
+ $iRet = count($this->aPreserved) + count($this->aAdded);
|
|
|
+ return $iRet;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Position the cursor to the given 0-based position
|
|
|
+ *
|
|
|
+ * @param $iPosition
|
|
|
+ * @throws Exception
|
|
|
+ * @internal param int $iRow
|
|
|
+ */
|
|
|
+ public function Seek($iPosition)
|
|
|
+ {
|
|
|
+ $this->LoadOriginalIds();
|
|
|
+
|
|
|
+ $iCount = $this->Count();
|
|
|
+ if ($iPosition >= $iCount)
|
|
|
+ {
|
|
|
+ throw new Exception("Invalid position $iPosition: the link set is made of $iCount items.");
|
|
|
+ }
|
|
|
+ $this->rewind();
|
|
|
+ for($iPos = 0 ; $iPos < $iPosition ; $iPos++)
|
|
|
+ {
|
|
|
+ $this->next();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Fetch the object at the current position in the collection and move the cursor to the next position.
|
|
|
+ *
|
|
|
+ * @return DBObject|null The fetched object or null when at the end
|
|
|
+ */
|
|
|
+ public function Fetch()
|
|
|
+ {
|
|
|
+ $this->LoadOriginalIds();
|
|
|
+
|
|
|
+ $ret = $this->current();
|
|
|
+ if ($ret === false)
|
|
|
+ {
|
|
|
+ $ret = null;
|
|
|
+ }
|
|
|
+ $this->next();
|
|
|
+ return $ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Return the current element
|
|
|
+ * @link http://php.net/manual/en/iterator.current.php
|
|
|
+ * @return mixed Can return any type.
|
|
|
+ */
|
|
|
+ public function current()
|
|
|
+ {
|
|
|
+ $this->LoadOriginalIds();
|
|
|
+
|
|
|
+ $iPreservedCount = count($this->aPreserved);
|
|
|
+ if ($this->iCursor < $iPreservedCount)
|
|
|
+ {
|
|
|
+ $oRet = current($this->aPreserved);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $oRet = current($this->aAdded);
|
|
|
+ }
|
|
|
+ return $oRet;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Move forward to next element
|
|
|
+ * @link http://php.net/manual/en/iterator.next.php
|
|
|
+ * @return void Any returned value is ignored.
|
|
|
+ */
|
|
|
+ public function next()
|
|
|
+ {
|
|
|
+ $this->LoadOriginalIds();
|
|
|
+
|
|
|
+ $iPreservedCount = count($this->aPreserved);
|
|
|
+ if ($this->iCursor < $iPreservedCount)
|
|
|
+ {
|
|
|
+ next($this->aPreserved);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ next($this->aAdded);
|
|
|
+ }
|
|
|
+ // Increment AFTER moving the internal cursors because when starting aAdded, we must leave it intact
|
|
|
+ $this->iCursor++;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Return the key of the current element
|
|
|
+ * @link http://php.net/manual/en/iterator.key.php
|
|
|
+ * @return mixed scalar on success, or null on failure.
|
|
|
+ */
|
|
|
+ public function key()
|
|
|
+ {
|
|
|
+ return $this->iCursor;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks if current position is valid
|
|
|
+ * @link http://php.net/manual/en/iterator.valid.php
|
|
|
+ * @return boolean The return value will be casted to boolean and then evaluated.
|
|
|
+ * Returns true on success or false on failure.
|
|
|
+ */
|
|
|
+ public function valid()
|
|
|
+ {
|
|
|
+ $this->LoadOriginalIds();
|
|
|
+
|
|
|
+ $iCount = $this->Count();
|
|
|
+ $bRet = ($this->iCursor < $iCount);
|
|
|
+ return $bRet;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Rewind the Iterator to the first element
|
|
|
+ * @link http://php.net/manual/en/iterator.rewind.php
|
|
|
+ * @return void Any returned value is ignored.
|
|
|
+ */
|
|
|
+ public function rewind()
|
|
|
+ {
|
|
|
+ $this->LoadOriginalIds();
|
|
|
+
|
|
|
+ $this->iCursor = 0;
|
|
|
+ reset($this->aPreserved);
|
|
|
+ reset($this->aAdded);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function HasDelta()
|
|
|
+ {
|
|
|
+ return $this->bHasDelta;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This method has been designed specifically for AttributeLinkedSet:Equals and as such it assumes that the passed argument is a clone of this.
|
|
|
+ * @param ormLinkSet $oFellow
|
|
|
+ * @return bool|null
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ public function Equals(ormLinkSet $oFellow)
|
|
|
+ {
|
|
|
+ $bRet = null;
|
|
|
+ if ($this === $oFellow)
|
|
|
+ {
|
|
|
+ $bRet = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if ( ($this->oOriginalSet !== $oFellow->oOriginalSet)
|
|
|
+ && ($this->oOriginalSet->GetFilter()->ToOQL() != $oFellow->oOriginalSet->GetFilter()->ToOQL()) )
|
|
|
+ {
|
|
|
+ throw new Exception('ormLinkSet::Equals assumes that compared link sets have the same original scope');
|
|
|
+ }
|
|
|
+ if ($this->HasDelta())
|
|
|
+ {
|
|
|
+ throw new Exception('ormLinkSet::Equals assumes that left link set had no delta');
|
|
|
+ }
|
|
|
+ $bRet = !$oFellow->HasDelta();
|
|
|
+ }
|
|
|
+ return $bRet;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function UpdateFromCompleteList(iDBObjectSetIterator $oFellow)
|
|
|
+ {
|
|
|
+ if ($oFellow === $this)
|
|
|
+ {
|
|
|
+ throw new Exception('ormLinkSet::UpdateFromCompleteList assumes that the passed link set is at least a clone of the current one');
|
|
|
+ }
|
|
|
+ $bUpdateFromDelta = false;
|
|
|
+ if ($oFellow instanceof ormLinkSet)
|
|
|
+ {
|
|
|
+ if ( ($this->oOriginalSet === $oFellow->oOriginalSet)
|
|
|
+ || ($this->oOriginalSet->GetFilter()->ToOQL() == $oFellow->oOriginalSet->GetFilter()->ToOQL()) )
|
|
|
+ {
|
|
|
+ $bUpdateFromDelta = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($bUpdateFromDelta)
|
|
|
+ {
|
|
|
+ // Same original set -> simply update the delta
|
|
|
+ $this->iCursor = 0;
|
|
|
+ $this->aAdded = $oFellow->aAdded;
|
|
|
+ $this->aRemoved = $oFellow->aRemoved;
|
|
|
+ $this->aModified = $oFellow->aModified;
|
|
|
+ $this->aPreserved = $oFellow->aPreserved;
|
|
|
+ $this->bHasDelta = $oFellow->bHasDelta;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // For backward compatibility reasons, let's rebuild a delta...
|
|
|
+
|
|
|
+ // Reset the delta
|
|
|
+ $this->iCursor = 0;
|
|
|
+ $this->aAdded = array();
|
|
|
+ $this->aRemoved = array();
|
|
|
+ $this->aModified = array();
|
|
|
+ $this->aPreserved = $this->aOriginalObjects;
|
|
|
+ $this->bHasDelta = false;
|
|
|
+
|
|
|
+ /** @var AttributeLinkedSet $oAttDef */
|
|
|
+ $oAttDef = MetaModel::GetAttributeDef($this->sHostClass, $this->sAttCode);
|
|
|
+ $sExtKeyToMe = $oAttDef->GetExtKeyToMe();
|
|
|
+ $sAdditionalKey = null;
|
|
|
+ if ($oAttDef->IsIndirect() && !$oAttDef->DuplicatesAllowed())
|
|
|
+ {
|
|
|
+ $sAdditionalKey = $oAttDef->GetExtKeyToRemote();
|
|
|
+ }
|
|
|
+ // Compare both collections by iterating the whole sets, order them, a build a fingerprint based on meaningful data (what make the difference)
|
|
|
+ $oComparator = new DBObjectSetComparator($this, $oFellow, array($sExtKeyToMe), $sAdditionalKey);
|
|
|
+ $aChanges = $oComparator->GetDifferences();
|
|
|
+ foreach ($aChanges['added'] as $oLink)
|
|
|
+ {
|
|
|
+ $this->AddItem($oLink);
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($aChanges['modified'] as $oLink)
|
|
|
+ {
|
|
|
+ $this->ModifyItem($oLink);
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($aChanges['removed'] as $oLink)
|
|
|
+ {
|
|
|
+ $this->RemoveItem($oLink->GetKey());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param DBObject $oHostObject
|
|
|
+ */
|
|
|
+ public function DBWrite(DBObject $oHostObject)
|
|
|
+ {
|
|
|
+ /** @var AttributeLinkedSet $oAttDef */
|
|
|
+ $oAttDef = MetaModel::GetAttributeDef(get_class($oHostObject), $this->sAttCode);
|
|
|
+ $sExtKeyToMe = $oAttDef->GetExtKeyToMe();
|
|
|
+ $sExtKeyToRemote = $oAttDef->IsIndirect() ? $oAttDef->GetExtKeyToRemote() : 'n/a';
|
|
|
+
|
|
|
+ $aCheckLinks = array();
|
|
|
+ $aCheckRemote = array();
|
|
|
+ foreach ($this->aAdded as $oLink)
|
|
|
+ {
|
|
|
+ if ($oLink->IsNew())
|
|
|
+ {
|
|
|
+ if ($oAttDef->IsIndirect() && !$oAttDef->DuplicatesAllowed())
|
|
|
+ {
|
|
|
+ //todo: faire un test qui passe dans cette branche !
|
|
|
+ $aCheckRemote[] = $oLink->Get($sExtKeyToRemote);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ //todo: faire un test qui passe dans cette branche !
|
|
|
+ $aCheckLinks[] = $oLink->GetKey();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($this->aRemoved as $iLinkId)
|
|
|
+ {
|
|
|
+ $aCheckLinks[] = $iLinkId;
|
|
|
+ }
|
|
|
+ foreach ($this->aModified as $iLinkId => $oLink)
|
|
|
+ {
|
|
|
+ $aCheckLinks[] = $oLink->GetKey();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Critical section : serialize any write access to these links
|
|
|
+ //
|
|
|
+ $oMtx = new iTopMutex('Write-'.$this->sClass);
|
|
|
+ $oMtx->Lock();
|
|
|
+
|
|
|
+ // Check for the existing links
|
|
|
+ //
|
|
|
+ if (count($aCheckLinks) > 0)
|
|
|
+ {
|
|
|
+ $oSearch = new DBObjectSearch($this->sClass);
|
|
|
+ $oSearch->AddCondition('id', $aCheckLinks, 'IN');
|
|
|
+ $oSet = new DBObjectSet($oSearch);
|
|
|
+ /** @var DBObject[] $aExistingLinks */
|
|
|
+ $aExistingLinks = $oSet->ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check for the existing remote objects
|
|
|
+ //
|
|
|
+ if (count($aCheckRemote) > 0)
|
|
|
+ {
|
|
|
+ $oSearch = new DBObjectSearch($this->sClass);
|
|
|
+ $oSearch->AddCondition($sExtKeyToMe, $oHostObject->GetKey(), '=');
|
|
|
+ $oSearch->AddCondition($sExtKeyToRemote, $aCheckRemote, 'IN');
|
|
|
+ $oSet = new DBObjectSet($oSearch);
|
|
|
+ /** @var Int[] $aExistingRemote */
|
|
|
+ $aExistingRemote = $oSet->GetColumnAsArray($sExtKeyToRemote);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Write the links according to the existing links
|
|
|
+ //
|
|
|
+ foreach ($this->aAdded as $oLink)
|
|
|
+ {
|
|
|
+ // Make sure that the objects in the set point to "this"
|
|
|
+ $oLink->Set($sExtKeyToMe, $oHostObject->GetKey());
|
|
|
+
|
|
|
+ if ($oLink->IsNew())
|
|
|
+ {
|
|
|
+ if (count($aCheckRemote) > 0)
|
|
|
+ {
|
|
|
+ if (in_array($oLink->Get($sExtKeyToRemote), $aExistingRemote))
|
|
|
+ {
|
|
|
+ // Do not create a duplicate
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (!array_key_exists($oLink->GetKey(), $aExistingLinks))
|
|
|
+ {
|
|
|
+ $oLink->DBClone();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $oLink->DBWrite();
|
|
|
+ }
|
|
|
+ foreach ($this->aRemoved as $iLinkId)
|
|
|
+ {
|
|
|
+ if (array_key_exists($iLinkId, $aExistingLinks))
|
|
|
+ {
|
|
|
+ $oLink = $aExistingLinks[$iLinkId];
|
|
|
+ if ($oAttDef->IsIndirect())
|
|
|
+ {
|
|
|
+ $oLink->DBDelete();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $oExtKeyToRemote = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToMe);
|
|
|
+ if ($oExtKeyToRemote->IsNullAllowed())
|
|
|
+ {
|
|
|
+ if ($oLink->Get($sExtKeyToMe) == $oHostObject->GetKey())
|
|
|
+ {
|
|
|
+ // Detach the link object from this
|
|
|
+ $oLink->Set($sExtKeyToMe, 0);
|
|
|
+ $oLink->DBUpdate();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $oLink->DBDelete();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Note: process modifications at the end: if a link to remove has also been listed as modified, then it will be gracefully ignored
|
|
|
+ foreach ($this->aModified as $iLinkId => $oLink)
|
|
|
+ {
|
|
|
+ if (array_key_exists($oLink->GetKey(), $aExistingLinks))
|
|
|
+ {
|
|
|
+ $oLink->DBUpdate();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ $oLink->DBClone();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // End of the critical section
|
|
|
+ //
|
|
|
+ $oMtx->Unlock();
|
|
|
+ }
|
|
|
+}
|