diff --git a/application/utils.inc.php b/application/utils.inc.php
index 7f761d864c..cf62cabc70 100644
--- a/application/utils.inc.php
+++ b/application/utils.inc.php
@@ -3075,4 +3075,9 @@ HTML;
return $sUrl;
}
+
+ public static function GetUniqId()
+ {
+ return hash('sha256', uniqid(sprintf('%x', rand()), true).sprintf('%x', rand()));
+ }
}
diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php
index 4a070d2397..5d665943b0 100644
--- a/core/cmdbobject.class.inc.php
+++ b/core/cmdbobject.class.inc.php
@@ -608,15 +608,7 @@ abstract class CMDBObject extends DBObject
public function DBUpdate()
{
- // Copy the changes list before the update (the list should be reset afterwards)
- $aChanges = $this->ListChanges();
- if (count($aChanges) == 0)
- {
- return;
- }
-
- $ret = parent::DBUpdate();
- return $ret;
+ parent::DBUpdate();
}
diff --git a/core/datamodel.core.xml b/core/datamodel.core.xml
index c2306317d1..55bce1be8b 100644
--- a/core/datamodel.core.xml
+++ b/core/datamodel.core.xml
@@ -136,32 +136,6 @@
-
- An object has been loaded from the database
-
-
- The object loaded
- DBObject
-
-
- Debug string
- string
-
-
-
-
- An object has been created in memory
-
-
- The object created
- DBObject
-
-
- Debug string
- string
-
-
-
An object has been re-loaded from the database
@@ -193,7 +167,7 @@
-
+
Check an object before it is deleted from the database
cmdbAbstractObject::DoCheckToDelete
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index 7b564005ad..dc3aa14312 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -149,7 +149,6 @@ abstract class DBObject implements iDisplay
* @var string local events suffix
*/
protected $m_sEventUniqId = '';
- protected static $m_iEventUniqCounter = 0;
/**
@@ -176,9 +175,8 @@ abstract class DBObject implements iDisplay
$this->m_bFullyLoaded = $this->IsFullyLoaded();
$this->m_aTouchedAtt = array();
$this->m_aModifiedAtt = array();
- $this->m_sEventUniqId = 'DataModel_'.get_class($this).'_'.self::$m_iEventUniqCounter++;
+ $this->m_sEventUniqId = get_class($this).'::'.$this->GetKey().'_'.utils::GetUniqId();
$this->RegisterEvents();
- $this->FireEvent(EVENT_SERVICE_DB_OBJECT_LOADED);
return;
}
// Creation of a brand new object
@@ -206,9 +204,8 @@ abstract class DBObject implements iDisplay
$this->UpdateMetaAttributes();
- $this->m_sEventUniqId = 'DataModel_'.self::$m_iEventUniqCounter++;
+ $this->m_sEventUniqId = get_class($this).'::0'.'_'.utils::GetUniqId();
$this->RegisterEvents();
- $this->FireEvent(EVENT_SERVICE_DB_OBJECT_NEW);
}
protected function RegisterEvents()
@@ -504,7 +501,6 @@ abstract class DBObject implements iDisplay
// Load extended data
if ($aExtendedDataSpec != null)
{
- $aExtendedDataSpec['table'];
foreach($aExtendedDataSpec['fields'] as $sColumn)
{
$sColRef = $sClassAlias.'_extdata_'.$sColumn;
@@ -1633,10 +1629,10 @@ abstract class DBObject implements iDisplay
{
if ($sType == FriendlyNameType::SHORT) {
return $this->Get('friendlyname');
- } else {
- $oExpression = MetaModel::GetNameExpression(get_class($this), $sType);
- $this->EvaluateExpression($oExpression);
}
+ $oExpression = MetaModel::GetNameExpression(get_class($this), $sType);
+
+ return $this->EvaluateExpression($oExpression);
}
/**
@@ -2809,6 +2805,10 @@ abstract class DBObject implements iDisplay
}
$sClass = get_class($this);
+ if ($sClass == 'UserRequest') {
+ IssueLog::Debug("CRUD: DBInsert $sClass::0 Starting", LogChannels::DM_CRUD);
+ }
+
$sRootClass = MetaModel::GetRootClass($sClass);
// Ensure the update of the values (we are accessing the data directly)
@@ -2893,7 +2893,6 @@ abstract class DBObject implements iDisplay
}
$this->OnObjectKeyReady();
- $this->FireEvent(EVENT_SERVICE_DB_OBJECT_KEY_READY);
$this->DBWriteLinks();
$this->WriteExternalAttributes();
@@ -3105,6 +3104,11 @@ abstract class DBObject implements iDisplay
}
$this->Reload();
+ $sClass = get_class($this);
+ if ($sClass == 'UserRequest') {
+ IssueLog::Debug("CRUD: DBInsert $sClass::{$this->m_iKey} Created", LogChannels::DM_CRUD);
+ }
+
return $this->m_iKey;
}
@@ -3168,15 +3172,22 @@ abstract class DBObject implements iDisplay
}
// Protect against reentrance (e.g. cascading the update of ticket logs)
static $aUpdateReentrance = array();
- $sKey = get_class($this).'::'.$this->GetKey();
+ $sClass = get_class($this);
+ $sKey = $sClass.'::'.$this->GetKey();
+ if ($sClass == 'UserRequest') {
+ IssueLog::Debug("CRUD: DBUpdate $sKey Starting", LogChannels::DM_CRUD);
+ }
+
if (array_key_exists($sKey, $aUpdateReentrance))
{
+ if ($sClass == 'UserRequest') {
+ IssueLog::Debug("CRUD: DBUpdate $sKey Rejected (reentrance)", LogChannels::DM_CRUD);
+ }
+
return false;
}
$aUpdateReentrance[$sKey] = true;
- $this->InitPreviousValuesForUpdatedAttributes();
-
try
{
$this->DoComputeValues();
@@ -3184,7 +3195,7 @@ abstract class DBObject implements iDisplay
$sState = $this->GetState();
if ($sState != '')
{
- foreach (MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
+ foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeStopWatch)
{
@@ -3202,12 +3213,16 @@ abstract class DBObject implements iDisplay
$this->OnUpdate();
$this->FireEvent(EVENT_SERVICE_BEFORE_UPDATE);
+ $this->InitPreviousValuesForUpdatedAttributes();
$aChanges = $this->ListChanges();
if (count($aChanges) == 0)
{
// Attempting to update an unchanged object
unset($aUpdateReentrance[$sKey]);
+ if ($sClass == 'UserRequest') {
+ IssueLog::Debug("CRUD: DBUpdate $sKey Aborted (no change)", LogChannels::DM_CRUD);
+ }
return $this->m_iKey;
}
@@ -3218,7 +3233,7 @@ abstract class DBObject implements iDisplay
{
throw new CoreCannotSaveObjectException(array(
'issues' => $aIssues,
- 'class' => get_class($this),
+ 'class' => $sClass,
'id' => $this->GetKey()
));
}
@@ -3227,7 +3242,6 @@ abstract class DBObject implements iDisplay
$aOriginalValues = $this->m_aOrigValues;
// Activate any existing trigger
- $sClass = get_class($this);
// - TriggerOnObjectMention
$this->ActivateOnMentionTriggers(false);
@@ -3236,7 +3250,7 @@ abstract class DBObject implements iDisplay
$aDBChanges = array();
foreach ($aChanges as $sAttCode => $valuecurr)
{
- $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+ $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef->IsExternalKey())
{
$bHasANewExternalKeyValue = true;
@@ -3275,7 +3289,7 @@ abstract class DBObject implements iDisplay
// Update the left & right indexes for each hierarchical key
foreach ($aHierarchicalKeys as $sAttCode => $oAttDef)
{
- $sTable = MetaModel::DBGetTable(get_class($this), $sAttCode);
+ $sTable = MetaModel::DBGetTable($sClass, $sAttCode);
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` AS `right`, `".$oAttDef->GetSQLLeft()."` AS `left` FROM `$sTable` WHERE id=".$this->GetKey();
$aRes = CMDBSource::QueryToArray($sSQL);
$iMyLeft = $aRes[0]['left'];
@@ -3315,7 +3329,7 @@ abstract class DBObject implements iDisplay
// Update scalar attributes
if (count($aDBChanges) != 0)
{
- $oFilter = new DBObjectSearch(get_class($this));
+ $oFilter = new DBObjectSearch($sClass);
$oFilter->AddCondition('id', $this->m_iKey, '=');
$oFilter->AllowAllData();
@@ -3360,7 +3374,7 @@ abstract class DBObject implements iDisplay
$aErrors = array($e->getMessage());
throw new CoreCannotSaveObjectException(array(
'id' => $this->GetKey(),
- 'class' => get_class($this),
+ 'class' => $sClass,
'issues' => $aErrors
), $e);
}
@@ -3383,7 +3397,7 @@ abstract class DBObject implements iDisplay
$aErrors = array($e->getMessage());
throw new CoreCannotSaveObjectException(array(
'id' => $this->GetKey(),
- 'class' => get_class($this),
+ 'class' => $sClass,
'issues' => $aErrors,
));
}
@@ -3412,7 +3426,6 @@ abstract class DBObject implements iDisplay
}
$this->AfterUpdate();
- $this->FireEvent(EVENT_SERVICE_AFTER_UPDATE);
// Reload to get the external attributes
if ($bHasANewExternalKeyValue) {
@@ -3432,13 +3445,18 @@ abstract class DBObject implements iDisplay
catch (Exception $e)
{
$aErrors = array($e->getMessage());
- throw new CoreCannotSaveObjectException(array('id' => $this->GetKey(), 'class' => get_class($this), 'issues' => $aErrors));
+ throw new CoreCannotSaveObjectException(array('id' => $this->GetKey(), 'class' => $sClass, 'issues' => $aErrors));
}
}
finally
{
unset($aUpdateReentrance[$sKey]);
}
+ $this->FireEvent(EVENT_SERVICE_AFTER_UPDATE, ['changes' => $aChanges]);
+
+ if ($sClass == 'UserRequest') {
+ IssueLog::Debug("CRUD: DBUpdate $sKey Updated", LogChannels::DM_CRUD);
+ }
return $this->m_iKey;
}
@@ -3931,7 +3949,9 @@ abstract class DBObject implements iDisplay
IssueLog::Error("$sClass: Transition $sStimulusCode is not allowed in ".$this->Get($sStateAttCode));
return false;
}
-
+ if ($sClass == 'UserRequest') {
+ IssueLog::Debug("CRUD: ApplyStimulus $sStimulusCode $sClass::{$this->m_iKey} Starting", LogChannels::DM_CRUD);
+ }
// save current object values in case of an action failure (in memory rollback)
$aBackupValues = array();
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) {
@@ -3947,7 +3967,7 @@ abstract class DBObject implements iDisplay
$aTransitionDef = $aStateTransitions[$sStimulusCode];
- $this->FireEvent(EVENT_SERVICE_BEFORE_APPLY_STIMULUS);
+ $this->FireEvent(EVENT_SERVICE_BEFORE_APPLY_STIMULUS, ['stimulus' => $sStimulusCode]);
// Change the state before proceeding to the actions, this is necessary because an action might
// trigger another stimuli (alternative: push the stimuli into a queue)
@@ -4067,7 +4087,7 @@ abstract class DBObject implements iDisplay
}
}
- $this->FireEvent(EVENT_SERVICE_AFTER_APPLY_STIMULUS);
+ $this->FireEvent(EVENT_SERVICE_AFTER_APPLY_STIMULUS, ['stimulus' => $sStimulusCode]);
}
else
{
@@ -4077,7 +4097,9 @@ abstract class DBObject implements iDisplay
$this->m_aCurrValues[$sAttCode] = $aBackupValues[$sAttCode];
}
}
-
+ if ($sClass == 'UserRequest') {
+ IssueLog::Debug("CRUD: ApplyStimulus $sStimulusCode $sClass::{$this->m_iKey} Ending", LogChannels::DM_CRUD);
+ }
return $bSuccess;
}
diff --git a/core/kpi.class.inc.php b/core/kpi.class.inc.php
index e2510555c2..a61e7f31cd 100644
--- a/core/kpi.class.inc.php
+++ b/core/kpi.class.inc.php
@@ -15,8 +15,6 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see
-use Combodo\iTop\Service\EventData;
-use Combodo\iTop\Service\EventService;
/**
@@ -39,8 +37,6 @@ class ExecutionKPI
* @var array[ExecutionKPI]
*/
static protected $m_aExecutionStack = []; // embedded execution stats
- // Event listener
- static protected $oKPIEventListener;
protected $m_fStarted = null;
protected $m_fChildrenDuration = 0; // Count embedded
@@ -292,9 +288,6 @@ class ExecutionKPI
public function __construct()
{
- if (is_null(static::$oKPIEventListener)) {
- static::$oKPIEventListener = new KPIEventListener();
- }
$this->ResetCounters();
self::Push($this);
}
@@ -306,7 +299,7 @@ class ExecutionKPI
*/
private static function Push(ExecutionKPI $oExecutionKPI)
{
- array_push(self::$m_aExecutionStack, $oExecutionKPI);
+ self::$m_aExecutionStack[] = $oExecutionKPI;
}
/**
@@ -456,21 +449,3 @@ class ExecutionKPI
return 0;
}
}
-
-class KPIEventListener
-{
- // Data model is not yet loaded, so EVENT_SERVICE_DB_OBJECT_LOADED is not yet defined
- const EVENT_SERVICE_DB_OBJECT_LOADED = 'DBObjectLoaded';
-
- public function __construct()
- {
- EventService::RegisterListener(self::EVENT_SERVICE_DB_OBJECT_LOADED, [get_class($this), 'OnObjectLoaded']);
- }
-
- public static function OnObjectLoaded(EventData $oEventData)
- {
- $oObject = $oEventData->GetEventData()['object'];
- $oKPI = new ExecutionKPI();
- $oKPI->ComputeStats('Objects Loaded (no duration)', get_class($oObject));
- }
-}
\ No newline at end of file
diff --git a/core/log.class.inc.php b/core/log.class.inc.php
index 7597bad666..311653ad5a 100644
--- a/core/log.class.inc.php
+++ b/core/log.class.inc.php
@@ -568,6 +568,14 @@ class LogChannels
public const INLINE_IMAGE = 'InlineImage';
public const PORTAL = 'portal';
+
+ /**
+ * @var string
+ * @since 3.1.0 specific channel for event service
+ */
+ public const EVENT_SERVICE = 'EventService';
+
+ public const DM_CRUD = 'DMCRUD';
}
diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php
index a688e4c6d8..30afdde343 100644
--- a/lib/composer/autoload_classmap.php
+++ b/lib/composer/autoload_classmap.php
@@ -357,6 +357,7 @@ return array(
'Combodo\\iTop\\Renderer\\FormRenderer' => $baseDir . '/sources/Renderer/FormRenderer.php',
'Combodo\\iTop\\Renderer\\RenderingOutput' => $baseDir . '/sources/Renderer/RenderingOutput.php',
'Combodo\\iTop\\Service\\EventData' => $baseDir . '/sources/application/Service/EventData.php',
+ 'Combodo\\iTop\\Service\\EventHelper' => $baseDir . '/sources/application/Service/EventHelper.php',
'Combodo\\iTop\\Service\\EventService' => $baseDir . '/sources/application/Service/EventService.php',
'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php',
'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php',
@@ -499,7 +500,6 @@ return array(
'JSButtonItem' => $baseDir . '/application/applicationextension.inc.php',
'JSPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php',
'JsonPage' => $baseDir . '/sources/application/WebPage/JsonPage.php',
- 'KPIEventListener' => $baseDir . '/core/kpi.class.inc.php',
'KeyValueStore' => $baseDir . '/core/counter.class.inc.php',
'ListExpression' => $baseDir . '/core/oql/expression.class.inc.php',
'ListOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php
index 4e57a99398..2151d531d8 100644
--- a/lib/composer/autoload_static.php
+++ b/lib/composer/autoload_static.php
@@ -587,6 +587,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'Combodo\\iTop\\Renderer\\FormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FormRenderer.php',
'Combodo\\iTop\\Renderer\\RenderingOutput' => __DIR__ . '/../..' . '/sources/Renderer/RenderingOutput.php',
'Combodo\\iTop\\Service\\EventData' => __DIR__ . '/../..' . '/sources/application/Service/EventData.php',
+ 'Combodo\\iTop\\Service\\EventHelper' => __DIR__ . '/../..' . '/sources/application/Service/EventHelper.php',
'Combodo\\iTop\\Service\\EventService' => __DIR__ . '/../..' . '/sources/application/Service/EventService.php',
'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php',
'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php',
@@ -729,7 +730,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'JSButtonItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'JSPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'JsonPage' => __DIR__ . '/../..' . '/sources/application/WebPage/JsonPage.php',
- 'KPIEventListener' => __DIR__ . '/../..' . '/core/kpi.class.inc.php',
'KeyValueStore' => __DIR__ . '/../..' . '/core/counter.class.inc.php',
'ListExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
'ListOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
diff --git a/sources/Application/Service/EventHelper.php b/sources/Application/Service/EventHelper.php
new file mode 100644
index 0000000000..3d3a7ffdc6
--- /dev/null
+++ b/sources/Application/Service/EventHelper.php
@@ -0,0 +1,42 @@
+GetEventSource();
$oKPI = new ExecutionKPI();
$sSource = isset($aEventData['debug_info']) ? " {$aEventData['debug_info']}" : '';
- $sEventName = "$sEvent:".self::GetSourcesAsString($eventSource);
- IssueLog::Trace("Fire event '$sEventName'$sSource", LOG_EVENT_SERVICE_CHANNEL);
+ $sLogEventName = "$sEvent - ".self::GetSourcesAsString($eventSource);
+ IssueLog::Trace("Fire event '$sLogEventName'$sSource", LogChannels::EVENT_SERVICE);
if (!isset(self::$aEventListeners[$sEvent])) {
- IssueLog::Trace("No registration found for event '$sEvent'", LOG_EVENT_SERVICE_CHANNEL);
+ IssueLog::Trace("No registration found for event '$sEvent'", LogChannels::EVENT_SERVICE);
$oKPI->ComputeStats('FireEvent', $sEvent);
return;
@@ -105,18 +104,26 @@ class EventService
continue;
}
$sName = $aEventCallback['name'];
- IssueLog::Debug("Fire event '$sEventName'$sSource calling '$sName'", LOG_EVENT_SERVICE_CHANNEL);
+ // Reentrance protection
+ if (isset(self::$aReentranceProtection[$aEventCallback['id']])) {
+ if (!self::$aReentranceProtection[$aEventCallback['id']]) {
+ unset(self::$aReentranceProtection[$aEventCallback['id']]);
+ }
+ IssueLog::Debug("Reentrance protection for '$sLogEventName'$sSource for '$sName'", LogChannels::EVENT_SERVICE);
+ continue;
+ }
+ IssueLog::Debug("Fire event '$sLogEventName'$sSource calling '$sName'", LogChannels::EVENT_SERVICE);
try {
if (is_callable($aEventCallback['callback'])) {
$oEventData->SetCallbackData($aEventCallback['data']);
call_user_func($aEventCallback['callback'], $oEventData);
} else {
- IssueLog::Debug("Callback '$sName' not a callable anymore, unregister", LOG_EVENT_SERVICE_CHANNEL);
+ IssueLog::Debug("Callback '$sName' not a callable anymore, unregister", LogChannels::EVENT_SERVICE);
self::UnRegisterCallback($aEventCallback['id']);
}
}
catch (Exception $e) {
- IssueLog::Error("Event '$sEventName' for '$sName' id {$aEventCallback['id']} failed with error: ".$e->getMessage());
+ IssueLog::Error("Event '$sLogEventName' for '$sName' id {$aEventCallback['id']} failed with error: ".$e->getMessage());
throw $e;
}
}
@@ -134,19 +141,19 @@ class EventService
return false;
}
if (is_string($srcRegistered)) {
- $aSrcRegistered = array($srcRegistered);
+ $aSrcRegistered = [$srcRegistered];
} elseif (is_array($srcRegistered)) {
$aSrcRegistered = $srcRegistered;
} else {
- $aSrcRegistered = array();
+ $aSrcRegistered = [];
}
if (is_string($srcEvent)) {
- $aSrcEvent = array($srcEvent);
+ $aSrcEvent = [$srcEvent];
} elseif (is_array($srcEvent)) {
$aSrcEvent = $srcEvent;
} else {
- $aSrcEvent = array();
+ $aSrcEvent = [];
}
foreach ($aSrcEvent as $sSrcEvent) {
@@ -187,10 +194,10 @@ class EventService
return '';
}
if (is_string($srcRegistered)) {
- return $srcRegistered;
+ return substr($srcRegistered, 0, 30);
}
if (is_array($srcRegistered)) {
- return implode(',', $srcRegistered);
+ return substr(implode(',', $srcRegistered), 0, 30);
}
return '';
@@ -207,7 +214,7 @@ class EventService
if ($aEventCallback['id'] == $sId) {
$sName = self::$aEventListeners[$sEvent][$idx]['name'];
unset (self::$aEventListeners[$sEvent][$idx]);
- IssueLog::Trace("Unregistered callback '$sName' id $sId' on event '$sEvent'", LOG_EVENT_SERVICE_CHANNEL);
+ IssueLog::Trace("Unregistered callback '$sName' id $sId' on event '$sEvent'", LogChannels::EVENT_SERVICE);
return false;
}
@@ -216,7 +223,7 @@ class EventService
});
if (!$bRemoved) {
- IssueLog::Trace("No registration found for callback '$sId'", LOG_EVENT_SERVICE_CHANNEL);
+ IssueLog::Trace("No registration found for callback '$sId'", LogChannels::EVENT_SERVICE);
}
}
@@ -228,13 +235,13 @@ class EventService
public static function UnRegisterEvent(string $sEvent)
{
if (!isset(self::$aEventListeners[$sEvent])) {
- IssueLog::Trace("No registration found for event '$sEvent'", LOG_EVENT_SERVICE_CHANNEL);
+ IssueLog::Trace("No registration found for event '$sEvent'", LogChannels::EVENT_SERVICE);
return;
}
unset(self::$aEventListeners[$sEvent]);
- IssueLog::Trace("Unregistered all the callbacks on event '$sEvent'", LOG_EVENT_SERVICE_CHANNEL);
+ IssueLog::Trace("Unregistered all the callbacks on event '$sEvent'", LogChannels::EVENT_SERVICE);
}
/**
@@ -243,7 +250,7 @@ class EventService
public static function UnRegisterAll()
{
self::$aEventListeners = array();
- IssueLog::Trace("Unregistered all events", LOG_EVENT_SERVICE_CHANNEL);
+ IssueLog::Trace("Unregistered all events", LogChannels::EVENT_SERVICE);
}
/**
@@ -271,7 +278,7 @@ class EventService
{
if (isset(self::$aEventDescription[$sEvent])) {
$sPrevious = self::$aEventDescription[$sEvent]['module'];
- IssueLog::Error("The Event $sEvent defined by $sModule has already been defined in $sPrevious, check your delta", LOG_EVENT_SERVICE_CHANNEL);
+ IssueLog::Error("The Event $sEvent defined by $sModule has already been defined in $sPrevious, check your delta", LogChannels::EVENT_SERVICE);
}
self::$aEventDescription[$sEvent] = [
@@ -295,9 +302,31 @@ class EventService
return implode('_', $aRet);
}
-
public static function GetDefinedEventsAsJSON()
{
return json_encode(self::$aEventDescription, JSON_PRETTY_PRINT);
}
+
+ /**
+ * @param string $sListenerId The id given by the RegisterListener() method
+ * @param bool $bPermanentProtection Be very careful when setting false, infinite loops can occur
+ *
+ * @return void
+ */
+ public static function EnableReentranceProtection($sListenerId, $bPermanentProtection = true)
+ {
+ self::$aReentranceProtection[$sListenerId] = $bPermanentProtection;
+ }
+
+ /**
+ * @param string $sListenerId The id given by the RegisterListener() method
+ *
+ * @return void
+ */
+ public static function DisableReentranceProtection($sListenerId)
+ {
+ if (isset(self::$aReentranceProtection[$sListenerId])) {
+ unset(self::$aReentranceProtection[$sListenerId]);
+ }
+ }
}
diff --git a/test/service/EventTest.php b/test/service/EventTest.php
index 9c5f26e355..4bf8918c80 100644
--- a/test/service/EventTest.php
+++ b/test/service/EventTest.php
@@ -6,6 +6,7 @@ use Combodo\iTop\Service\EventData;
use Combodo\iTop\Service\EventService;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
use ContextTag;
+use Exception;
use TypeError;
/**
@@ -55,7 +56,10 @@ class EventTest extends ItopTestCase
public function testNoParameterCallbackFunction()
{
- $sId = EventService::RegisterListener('event', function () { $this->debug("Closure: event received !!!"); self::IncrementCallCount(); });
+ $sId = EventService::RegisterListener('event', function () {
+ $this->debug("Closure: event received !!!");
+ self::IncrementCallCount();
+ });
$this->debug("Registered $sId");
EventService::FireEvent(new EventData('event'));
$this->assertEquals(1, self::$iEventCalls);
@@ -81,9 +85,10 @@ class EventTest extends ItopTestCase
public function GoodCallbackProvider()
{
$oReceiver = new TestEventReceiver();
+
return array(
- 'method' => array(array($oReceiver, 'OnEvent1')),
- 'static' => array('Combodo\iTop\Test\UnitTest\Service\TestEventReceiver::OnStaticEvent1'),
+ 'method' => array(array($oReceiver, 'OnEvent1')),
+ 'static' => array('Combodo\iTop\Test\UnitTest\Service\TestEventReceiver::OnStaticEvent1'),
'static2' => array(array('Combodo\iTop\Test\UnitTest\Service\TestEventReceiver', 'OnStaticEvent1')),
);
}
@@ -291,6 +296,157 @@ class EventTest extends ItopTestCase
self::$iEventCalls++;
}
+ /**
+ * @dataProvider ReentranceProvider
+ */
+ public function testReentrance($aClasses, $iEventCount)
+ {
+ foreach ($aClasses as $sName => $aClass) {
+ new TestReentrance($sName, $aClass['prio'], $aClass['events'], $aClass['permanent_protection']);
+ }
+ EventService::FireEvent(new EventData('event1', 'main'));
+ $this->assertEquals($iEventCount, self::$iEventCalls);
+ }
+
+ public function ReentranceProvider()
+ {
+ return [
+ '1 class' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => true, 'event2' => false], 'listener2' => ['event1' => false, 'event2' => false]]],
+ ],
+ 'iEventCount' => 1,
+ ],
+ '2 classes - 1' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => true, 'event2' => false], 'listener2' => ['event1' => false, 'event2' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => false, 'event2' => false], 'listener2' => ['event1' => false, 'event2' => false]]],
+ ],
+ 'iEventCount' => 3,
+ ],
+ '2 classes - 2' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => true, 'event2' => true], 'listener2' => ['event1' => false, 'event2' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => false, 'event2' => false], 'listener2' => ['event1' => false, 'event2' => false]]],
+ ],
+ 'iEventCount' => 4,
+ ],
+ '2 classes - 3' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => false, 'event2' => true], 'listener2' => ['event1' => false, 'event2' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => false, 'event2' => false], 'listener2' => ['event1' => false, 'event2' => true]]],
+ ],
+ 'iEventCount' => 3,
+ ],
+ '3 classes - 1' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => false, 'event2' => true], 'listener2' => ['event1' => false, 'event2' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => false, 'event2' => true], 'listener2' => ['event1' => false, 'event2' => false]]],
+ 'Class C' => ['prio' => 20, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => false, 'event2' => true], 'listener2' => ['event1' => false, 'event2' => true]]],
+ ],
+ 'iEventCount' => 11,
+ ],
+ '3 classes - non permanent' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => false, 'events' => ['listener1' => ['event1' => false, 'event2' => true], 'listener2' => ['event1' => false, 'event2' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => false, 'event2' => true], 'listener2' => ['event1' => false, 'event2' => false]]],
+ 'Class C' => ['prio' => 20, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => false, 'event2' => true], 'listener2' => ['event1' => false, 'event2' => true]]],
+ ],
+ 'iEventCount' => 12,
+ ],
+ '2 classes - loop' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => true, 'event2' => false], 'listener2' => ['event1' => false, 'event2' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => true, 'event2' => false], 'listener2' => ['event1' => false, 'event2' => false]]],
+ ],
+ 'iEventCount' => 4,
+ ],
+ '2 classes - loop2' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => false, 'events' => ['listener1' => ['event1' => true, 'event2' => false], 'listener2' => ['event1' => false, 'event2' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['event1' => true, 'event2' => false], 'listener2' => ['event1' => false, 'event2' => false]]],
+ ],
+ 'iEventCount' => 5,
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider ReentranceCRUDProvider
+ */
+ public function testReentranceCRUD($aClasses, $iEventCount)
+ {
+ foreach ($aClasses as $sName => $aClass) {
+ new TestReentranceCRUD($sName, $aClass['prio'], $aClass['events'], $aClass['permanent_protection']);
+ }
+ $oObject = new TestCRUDObject();
+ $oObject->DBInsert('main');
+ $this->assertEquals($iEventCount, self::$iEventCalls);
+ }
+
+ public function ReentranceCRUDProvider()
+ {
+ return [
+ '1 class' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => false], 'listener2' => ['update' => false]]],
+ ],
+ 'iEventCount' => 1,
+ ],
+ '2 classes - 1' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => false], 'listener2' => ['update' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => false], 'listener2' => ['update' => false]]],
+ ],
+ 'iEventCount' => 2,
+ ],
+ '2 classes - 2'=> [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => true], 'listener2' => ['update' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => false], 'listener2' => ['update' => false]]],
+ ],
+ 'iEventCount' => 4,
+ ],
+ '2 classes - 3' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => true], 'listener2' => ['update' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => false], 'listener2' => ['update' => true]]],
+ ],
+ 'iEventCount' => 3,
+ ],
+ '3 classes - 1' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => true], 'listener2' => ['update' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => true], 'listener2' => ['update' => false]]],
+ 'Class C' => ['prio' => 20, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => true], 'listener2' => ['update' => true]]],
+ ],
+ 'iEventCount' => 11,
+ ],
+ '3 classes - non permanent' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => false, 'events' => ['listener1' => ['update' => true], 'listener2' => ['update' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => true], 'listener2' => ['update' => false]]],
+ 'Class C' => ['prio' => 20, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => true], 'listener2' => ['update' => true]]],
+ ],
+ 'iEventCount' => 12,
+ ],
+ '2 classes - loop' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => false], 'listener2' => ['update' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => false], 'listener2' => ['update' => false]]],
+ ],
+ 'iEventCount' => 4,
+ ],
+ '2 classes - loop2' => [
+ 'aClasses' => [
+ 'Class A' => ['prio' => 0, 'permanent_protection' => false, 'events' => ['listener1' => ['update' => false], 'listener2' => ['update' => false]]],
+ 'Class B' => ['prio' => 10, 'permanent_protection' => true, 'events' => ['listener1' => ['update' => false], 'listener2' => ['update' => false]]],
+ ],
+ 'iEventCount' => 5,
+ ],
+ ];
+ }
+
/**
* static version of the debug to be accessible from other objects
*
@@ -298,21 +454,50 @@ class EventTest extends ItopTestCase
*/
public static function DebugStatic($sMsg)
{
- if (DEBUG_UNIT_TEST)
- {
- if (is_string($sMsg))
- {
+ if (DEBUG_UNIT_TEST) {
+ if (is_string($sMsg)) {
echo "$sMsg\n";
- }
- else
- {
+ } else {
print_r($sMsg);
}
}
}
}
-class TestEventReceiver
+class TestClassesWithDebug
+{
+ /**
+ * static version of the debug to be accessible from other objects
+ *
+ * @param $sMsg
+ */
+ public static function DebugStatic($sMsg)
+ {
+ if (DEBUG_UNIT_TEST) {
+ if (is_string($sMsg)) {
+ echo "$sMsg\n";
+ } else {
+ print_r($sMsg);
+ }
+ }
+ }
+
+ /**
+ * @param $sMsg
+ */
+ public function Debug($sMsg)
+ {
+ if (DEBUG_UNIT_TEST) {
+ if (is_string($sMsg)) {
+ echo "$sMsg\n";
+ } else {
+ print_r($sMsg);
+ }
+ }
+ }
+}
+
+class TestEventReceiver extends TestClassesWithDebug
{
// Event callbacks
@@ -363,44 +548,201 @@ class TestEventReceiver
self::DebugStatic(__METHOD__.": received event '{$sEvent}'");
EventTest::IncrementCallCount();
}
-
- /**
- * static version of the debug to be accessible from other objects
- *
- * @param $sMsg
- */
- public static function DebugStatic($sMsg)
- {
- if (DEBUG_UNIT_TEST)
- {
- if (is_string($sMsg))
- {
- echo "$sMsg\n";
- }
- else
- {
- print_r($sMsg);
- }
- }
- }
- /**
- * static version of the debug to be accessible from other objects
- *
- * @param $sMsg
- */
- public function Debug($sMsg)
- {
- if (DEBUG_UNIT_TEST)
- {
- if (is_string($sMsg))
- {
- echo "$sMsg\n";
- }
- else
- {
- print_r($sMsg);
- }
- }
- }
-
}
+
+class TestCRUDObject extends TestClassesWithDebug
+{
+ private static $aEventList = [];
+
+ public function DBInsert($sName)
+ {
+ $this->sName = $sName;
+ $this->FireEvent('insert');
+ }
+
+ public function DBUpdate($sName)
+ {
+ $this->sName = $sName;
+ $this->FireEvent('update');
+ }
+
+ public function FireEvent($sEvent, $aEventData = [])
+ {
+ $aEventData['name'] = $this->sName;
+ $aEventData['object'] = $this;
+ $oEventData = new EventData($sEvent, $this->sName, $aEventData);
+
+ $bFireEventNow = empty(self::$aEventList['event']);
+ self::$aEventList['event'][] = $oEventData;
+
+ $iCount = 0;
+ while ($bFireEventNow) {
+ $sEvent = $oEventData->GetEvent();
+ $sName = $oEventData->Get('name');
+ $this->Debug("Object from $sName: Fire event '$sEvent'");
+ EventService::FireEvent($oEventData);
+ $this->Debug("Object from $sName: End of event '$sEvent'");
+
+ array_shift(self::$aEventList['event']);
+ $oEventData = reset(self::$aEventList['event']);
+
+ if ($oEventData === false) {
+ $bFireEventNow = false;
+ }
+ if ($iCount++ > 10) {
+ throw new Exception('Infinite loop');
+ }
+ }
+ }
+}
+
+class TestReentranceCRUD extends TestClassesWithDebug
+{
+ public $sName;
+ public $sEvent1;
+ public $sEvent2;
+ public $bPermanentProtection;
+
+ private static $iIndent1 = 0;
+ private static $iIndent2 = 0;
+
+ public $aSendEvent = [];
+
+ /**
+ * @param $sName
+ * @param $fPriority
+ * @param array $aSendEvent
+ * @param bool $bPermanentProtection
+ */
+ public function __construct($sName, $fPriority, array $aSendEvent, $bPermanentProtection)
+ {
+ $this->sName = $sName;
+ $this->sEvent1 = EventService::RegisterListener('insert', [$this, 'OnInsert'], null, [], null, $fPriority);
+ $this->sEvent2 = EventService::RegisterListener('update', [$this, 'OnUpdate'], null, [], null, $fPriority);
+ $this->aSendEvent = $aSendEvent;
+ $this->bPermanentProtection = $bPermanentProtection;
+ }
+
+ public function OnInsert(EventData $oData)
+ {
+ $sIndent = str_repeat(' ', self::$iIndent1);
+ $oObject = $oData->Get('object');
+ $sEvent = $oData->GetEvent();
+ $sSource = $oData->GetEventSource();
+ $sName = $sIndent.$this->sName;
+ $this->Debug("$sName: received event '$sEvent' from '$sSource'");
+ EventTest::IncrementCallCount();
+
+ if ($this->aSendEvent['listener1']['update']) {
+ self::$iIndent2 += 1;
+ $this->Debug("$sName: Update");
+ EventService::EnableReentranceProtection($this->sEvent2, $this->bPermanentProtection);
+ $oObject->DBUpdate($sName);
+ EventService::DisableReentranceProtection($this->sEvent2);
+ self::$iIndent2 -= 1;
+ }
+ }
+
+ public function OnUpdate(EventData $oData)
+ {
+ $sIndent = str_repeat(' ', self::$iIndent2);
+ $oObject = $oData->Get('object');
+ $sEvent = $oData->GetEvent();
+ $sSource = $oData->GetEventSource();
+ $sName = $sIndent.$this->sName;
+ $this->Debug("$sName: received event '$sEvent' from '$sSource'");
+ EventTest::IncrementCallCount();
+ if ($this->aSendEvent['listener2']['update']) {
+ self::$iIndent2 += 1;
+ $this->Debug("$sName: Update");
+ EventService::EnableReentranceProtection($this->sEvent2, $this->bPermanentProtection);
+ $oObject->DBUpdate($sName);
+ EventService::DisableReentranceProtection($this->sEvent2);
+ self::$iIndent2 -= 1;
+ }
+ }
+}
+
+class TestReentrance extends TestClassesWithDebug
+{
+ public $sName;
+ public $sEvent1;
+ public $sEvent2;
+ public $bPermanentProtection;
+
+ private static $iIndent1 = 0;
+ private static $iIndent2 = 0;
+
+ public $aSendEvent = [];
+
+ /**
+ * @param $sName
+ * @param $fPriority
+ * @param array $aSendEvent
+ * @param bool $bPermanentProtection
+ */
+ public function __construct($sName, $fPriority, array $aSendEvent, $bPermanentProtection)
+ {
+ $this->sName = $sName;
+ $this->sEvent1 = EventService::RegisterListener('event1', [$this, 'ListenerEvent1'], null, [], null, $fPriority);
+ $this->sEvent2 = EventService::RegisterListener('event2', [$this, 'ListenerEvent2'], null, [], null, $fPriority);
+ $this->aSendEvent = $aSendEvent;
+ $this->bPermanentProtection = $bPermanentProtection;
+ }
+
+ public function ListenerEvent1(EventData $oData)
+ {
+ $sIndent = str_repeat(' ', self::$iIndent1);
+ $sEvent = $oData->GetEvent();
+ $sSource = $oData->GetEventSource();
+ $sName = $sIndent.$this->sName;
+ $this->Debug("$sName: received event '$sEvent' from '$sSource'");
+ EventTest::IncrementCallCount();
+ if ($this->aSendEvent['listener1']['event1']) {
+ $this->Debug("$sName: Fire event 'event1'");
+ self::$iIndent1 += 1;
+ EventService::EnableReentranceProtection($this->sEvent1, $this->bPermanentProtection);
+ EventService::FireEvent(new EventData('event1', $sName));
+ EventService::DisableReentranceProtection($this->sEvent1);
+ self::$iIndent1 -= 1;
+ $this->Debug("$sName: End of event 'event1'");
+ }
+ if ($this->aSendEvent['listener1']['event2']) {
+ $this->Debug("$sName: Fire event 'event2'");
+ self::$iIndent2 += 1;
+ EventService::EnableReentranceProtection($this->sEvent2, $this->bPermanentProtection);
+ EventService::FireEvent(new EventData('event2', $sName));
+ EventService::DisableReentranceProtection($this->sEvent2);
+ self::$iIndent2 -= 1;
+ $this->Debug("$sName: End of event 'event2'");
+ }
+ }
+
+ public function ListenerEvent2(EventData $oData)
+ {
+ $sIndent = str_repeat(' ', self::$iIndent2);
+ $sEvent = $oData->GetEvent();
+ $sSource = $oData->GetEventSource();
+ $sName = $sIndent.$this->sName;
+ $this->Debug("$sName: received event '$sEvent' from '$sSource'");
+ EventTest::IncrementCallCount();
+ if ($this->aSendEvent['listener2']['event1']) {
+ $this->Debug("$sName: Fire event 'event1'");
+ self::$iIndent1 += 1;
+ EventService::EnableReentranceProtection($this->sEvent1, $this->bPermanentProtection);
+ EventService::FireEvent(new EventData('event1', $sName));
+ EventService::DisableReentranceProtection($this->sEvent1);
+ self::$iIndent1 -= 1;
+ $this->Debug("$sName: End of event 'event1'");
+ }
+ if ($this->aSendEvent['listener2']['event2']) {
+ $this->Debug("$sName: Fire event 'event2'");
+ self::$iIndent2 += 1;
+ EventService::EnableReentranceProtection($this->sEvent2, $this->bPermanentProtection);
+ EventService::FireEvent(new EventData('event2', $sName));
+ EventService::DisableReentranceProtection($this->sEvent2);
+ self::$iIndent2 -= 1;
+ $this->Debug("$sName: End of event 'event2'");
+ }
+ }
+}
\ No newline at end of file