diff --git a/core/metamodel.class.php b/core/metamodel.class.php index c03ac387a..24fd433ef 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -6852,6 +6852,9 @@ abstract class MetaModel /** * Instantiate an object already persisted to the Database. * + * Note that LinkedSet attributes are not loaded. + * DBObject::Reload() will be called when getting a LinkedSet attribute + * * @api * @see MetaModel::GetObjectWithArchive to get object even if it's archived * @see utils::PushArchiveMode() to enable search on archived objects diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php index 9bb5b51d2..7b7d40725 100644 --- a/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php +++ b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php @@ -206,23 +206,6 @@ abstract class ItopTestCase extends TestCase require_once $this->GetAppRoot() . $sFileRelPath; } - /** - * Helper to load a module file. The caller test must be in that module ! - * Will browse dir up to find a module.*.php - * - * @param string $sFileRelPath for example 'portal/src/Helper/ApplicationHelper.php' - * @since 2.7.10 3.1.1 3.2.0 N°6709 method creation - */ - protected function RequireOnceCurrentModuleFile(string $sFileRelPath): void - { - $aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); - $sCallerFileFullPath = $aStack[0]['file']; - $sCallerDir = dirname($sCallerFileFullPath); - - $sModuleRootPath = static::GetFirstDirUpContainingFile($sCallerDir, 'module.*.php'); - require_once $sModuleRootPath . $sFileRelPath; - } - /** * Require once a unit test file (eg. a mock class) from its relative path from the *current* dir. * This ensure that required files don't crash when unit tests dir is moved in the iTop structure (see N°5608) @@ -240,26 +223,6 @@ abstract class ItopTestCase extends TestCase require_once $sCallerDirAbsPath . DIRECTORY_SEPARATOR . $sFileRelPath; } - private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string - { - for ($iDepth = 0; $iDepth < 8; $iDepth++) { - $aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern); - if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) { - return $sSearchPath . '/'; - } - $iOffsetSep = strrpos($sSearchPath, '/'); - if ($iOffsetSep === false) { - $iOffsetSep = strrpos($sSearchPath, '\\'); - if ($iOffsetSep === false) { - // Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform - return 'Could not find the approot file in ' . $sSearchPath; - } - } - $sSearchPath = substr($sSearchPath, 0, $iOffsetSep); - } - return null; - } - protected function debug($sMsg) { if (static::$DEBUG_UNIT_TEST) { @@ -402,11 +365,11 @@ abstract class ItopTestCase extends TestCase */ private function GetProperty(string $sClass, string $sProperty): \ReflectionProperty { - $class = new \ReflectionClass($sClass); - $property = $class->getProperty($sProperty); - $property->setAccessible(true); + $oClass = new \ReflectionClass($sClass); + $oProperty = $oClass->getProperty($sProperty); + $oProperty->setAccessible(true); - return $property; + return $oProperty; } @@ -417,7 +380,7 @@ abstract class ItopTestCase extends TestCase * * @since 2.7.8 3.0.3 3.1.0 */ - public function SetNonPublicProperty(object $oObject, string $sProperty, $value) + public function SetNonPublicProperty($oObject, string $sProperty, $value) { $oProperty = $this->GetProperty(get_class($oObject), $sProperty); $oProperty->setValue($oObject, $value); diff --git a/tests/php-unit-tests/unitary-tests/core/CRUDEventTest.php b/tests/php-unit-tests/unitary-tests/core/CRUDEventTest.php index 6bd6c8224..4d15ec8cc 100644 --- a/tests/php-unit-tests/unitary-tests/core/CRUDEventTest.php +++ b/tests/php-unit-tests/unitary-tests/core/CRUDEventTest.php @@ -374,6 +374,27 @@ class CRUDEventTest extends ItopDataTestCase $this->assertStringStartsWith('CRUD', $oPerson->Get('first_name'), 'The object should have been modified and recorded in DB by EVENT_DB_AFTER_WRITE handler'); } + public function testAfterDeleteObjectAttributesExceptLinkedSetAreUsable() + { + $oPerson = $this->createObject('Person', [ + 'name' => 'Person_1', + 'first_name' => 'Test', + 'org_id' => $this->getTestOrgId(), + ]); + + $oFetchPerson = MetaModel::GetObject('Person', $oPerson->GetKey()); + + $oEventReceiver = new CRUDEventReceiver($this); + // Set the person's first name during Compute Values + $oEventReceiver->AddCallback(EVENT_DB_AFTER_DELETE, Person::class, 'GetObjectAttributesValues'); + $oEventReceiver->RegisterCRUDEventListeners(EVENT_DB_AFTER_DELETE); + $oEventReceiver->RegisterCRUDEventListeners(EVENT_DB_OBJECT_RELOAD); + + $oFetchPerson->DBDelete(); + + $this->assertEquals(1, self::$aEventCallsCount[EVENT_DB_AFTER_DELETE], 'EVENT_DB_AFTER_DELETE must be called when deleting an object and the object attributes must remain accessible'); + } + /** * Modify one object during EVENT_DB_AFTER_WRITE * Check that the CRUD is protected against infinite loops (when modifying an object in its EVENT_DB_AFTER_WRITE) @@ -881,6 +902,20 @@ class CRUDEventReceiver extends ClassesWithDebug $oObject->Set('first_name', 'CRUD_first_name_'.rand()); } + /** + * @noinspection PhpUnusedPrivateMethodInspection Used as a callback + */ + private function GetObjectAttributesValues(EventData $oData): void + { + $this->Debug(__METHOD__); + $oObject = $oData->Get('object'); + foreach (MetaModel::ListAttributeDefs(get_class($oObject)) as $sAttCode => $oAttDef) { + if (!$oAttDef->IsLinkSet()) { + $oObject->Get($sAttCode); + } + } + } + /** * @noinspection PhpUnusedPrivateMethodInspection Used as a callback */