mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-21 09:38:48 +02:00
CRUD reentrance protection
This commit is contained in:
@@ -3075,4 +3075,9 @@ HTML;
|
||||
|
||||
return $sUrl;
|
||||
}
|
||||
|
||||
public static function GetUniqId()
|
||||
{
|
||||
return hash('sha256', uniqid(sprintf('%x', rand()), true).sprintf('%x', rand()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -136,32 +136,6 @@
|
||||
</argument>
|
||||
</arguments>
|
||||
</event>
|
||||
<event id="DBObjectLoaded" _delta="define">
|
||||
<description>An object has been loaded from the database</description>
|
||||
<arguments>
|
||||
<argument id="object">
|
||||
<description>The object loaded</description>
|
||||
<type>DBObject</type>
|
||||
</argument>
|
||||
<argument id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</argument>
|
||||
</arguments>
|
||||
</event>
|
||||
<event id="DBObjectNew" _delta="define">
|
||||
<description>An object has been created in memory</description>
|
||||
<arguments>
|
||||
<argument id="object">
|
||||
<description>The object created</description>
|
||||
<type>DBObject</type>
|
||||
</argument>
|
||||
<argument id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</argument>
|
||||
</arguments>
|
||||
</event>
|
||||
<event id="DBObjectReload" _delta="define">
|
||||
<description>An object has been re-loaded from the database</description>
|
||||
<arguments>
|
||||
@@ -193,7 +167,7 @@
|
||||
</argument>
|
||||
</arguments>
|
||||
</event>
|
||||
<event id="DoCheckToDelete" _delta="define">
|
||||
<event id="OnCheckToDelete" _delta="define">
|
||||
<description>Check an object before it is deleted from the database</description>
|
||||
<replaces>cmdbAbstractObject::DoCheckToDelete</replaces>
|
||||
<arguments>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
42
sources/Application/Service/EventHelper.php
Normal file
42
sources/Application/Service/EventHelper.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2022 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Service;
|
||||
|
||||
use SetupUtils;
|
||||
use utils;
|
||||
|
||||
class EventHelper
|
||||
{
|
||||
|
||||
public static function GetCachedClasses($sModuleName, callable $ListBuilder)
|
||||
{
|
||||
$aClasses = [];
|
||||
$sCacheFileName = '';
|
||||
|
||||
if (!utils::IsDevelopmentEnvironment()) {
|
||||
// Try to read from cache
|
||||
$sCacheFileName = utils::GetCachePath()."EventsClassList/$sModuleName.php";
|
||||
if (is_file($sCacheFileName)) {
|
||||
$aClasses = include $sCacheFileName;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($aClasses)) {
|
||||
$aClasses = call_user_func($ListBuilder);
|
||||
|
||||
if (!utils::IsDevelopmentEnvironment() && !empty($aClasses)) {
|
||||
// Save to cache
|
||||
$sCacheContent = "<?php\n\nreturn ".var_export($aClasses, true).";";
|
||||
SetupUtils::builddir(dirname($sCacheFileName));
|
||||
file_put_contents($sCacheFileName, $sCacheContent);
|
||||
}
|
||||
}
|
||||
|
||||
return $aClasses;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,14 +11,14 @@ use ContextTag;
|
||||
use Exception;
|
||||
use ExecutionKPI;
|
||||
use IssueLog;
|
||||
|
||||
define('LOG_EVENT_SERVICE_CHANNEL', 'EventService');
|
||||
use LogChannels;
|
||||
|
||||
class EventService
|
||||
{
|
||||
public static $aEventListeners = [];
|
||||
private static $iEventIdCounter = 0;
|
||||
private static $aEventDescription = [];
|
||||
Private static $aReentranceProtection = [];
|
||||
|
||||
/**
|
||||
* Register a callback for a specific event
|
||||
@@ -32,7 +32,6 @@ class EventService
|
||||
*
|
||||
* @return string Id of the registration (used for unregistering)
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function RegisterListener(string $sEvent, callable $callback, $sEventSource = null, $aCallbackData = [], $context = null, float $fPriority = 0.0): string
|
||||
{
|
||||
@@ -64,8 +63,8 @@ class EventService
|
||||
foreach (self::$aEventListeners as $aEvent) {
|
||||
$iTotalRegistrations += count($aEvent);
|
||||
}
|
||||
$sEventName = "$sEvent:".self::GetSourcesAsString($sEventSource);
|
||||
IssueLog::Trace("Registering event '$sEventName' for '$sName' with id '$sId' (total $iTotalRegistrations)", LOG_EVENT_SERVICE_CHANNEL);
|
||||
$sLogEventName = "$sEvent:".self::GetSourcesAsString($sEventSource);
|
||||
IssueLog::Trace("Registering event '$sLogEventName' for '$sName' with id '$sId' (total $iTotalRegistrations)", LogChannels::EVENT_SERVICE);
|
||||
|
||||
return $sId;
|
||||
}
|
||||
@@ -88,10 +87,10 @@ class EventService
|
||||
$eventSource = $oEventData->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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user