From: Coleman Watts Date: Thu, 17 Mar 2022 02:00:54 +0000 (-0400) Subject: REF - Refactor ManagedEntities with the ability to reconcile a single module X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=a7db5554d7695492c184077d0d81a6d397c07fcc;p=civicrm-core.git REF - Refactor ManagedEntities with the ability to reconcile a single module Reconciling a single module (e.g. Afform) is more efficient. Further refactoring of ManagedEntities for code simplification and efficiency. Instead of fetching Managed records 3 times (for enabled, disabled and missing modules), now they are fetched only once and sorted into the correct category. --- diff --git a/CRM/Core/ManagedEntities.php b/CRM/Core/ManagedEntities.php index bbae0344b5..1e249fe76d 100644 --- a/CRM/Core/ManagedEntities.php +++ b/CRM/Core/ManagedEntities.php @@ -29,11 +29,11 @@ class CRM_Core_ManagedEntities { protected $moduleIndex; /** - * Actions arising from the managed entities. + * Plan for what to do with each managed entity. * * @var array */ - protected $managedActions = []; + protected $plan = []; /** * @var array @@ -115,19 +115,15 @@ class CRM_Core_ManagedEntities { * Identify any enabled/disabled modules. Add new entities, update * existing entities, and remove orphaned (stale) entities. * - * @param bool $ignoreUpgradeMode - * Unused. + * @param array $modules + * Limits scope of reconciliation to specific module(s). * @throws \CRM_Core_Exception */ - public function reconcile($ignoreUpgradeMode = FALSE) { - $this->loadDeclarations(); - if ($error = $this->validate($this->getDeclarations())) { - throw new CRM_Core_Exception($error); - } - $this->loadManagedEntityActions(); - $this->reconcileEnabledModules(); - $this->reconcileDisabledModules(); - $this->reconcileUnknownModules(); + public function reconcile($modules = NULL) { + $modules = $modules ? (array) $modules : NULL; + $this->loadDeclarations($modules); + $this->createPlan($modules); + $this->reconcileEntities(); } /** @@ -139,230 +135,111 @@ class CRM_Core_ManagedEntities { $mgd = new \CRM_Core_DAO_Managed(); $mgd->copyValues($params); $mgd->find(TRUE); - $this->loadDeclarations(); + $this->loadDeclarations([$mgd->module]); $declarations = CRM_Utils_Array::findAll($this->declarations, [ 'module' => $mgd->module, 'name' => $mgd->name, 'entity' => $mgd->entity_type, ]); if ($mgd->id && isset($declarations[0])) { - $this->updateExistingEntity($mgd, ['update' => 'always'] + $declarations[0]); + $this->updateExistingEntity(['update' => 'always'] + $declarations[0] + $mgd->toArray()); return TRUE; } return FALSE; } /** - * For all enabled modules, add new entities, update - * existing entities, and remove orphaned (stale) entities. + * Take appropriate action on every managed entity. */ - protected function reconcileEnabledModules(): void { - // Note: any thing currently declared is necessarily from - // an active module -- because we got it from a hook! - - // index by moduleName,name - $decls = $this->createDeclarationIndex($this->moduleIndex, $this->getDeclarations()); - foreach ($decls as $moduleName => $todos) { - if ($this->isModuleEnabled($moduleName)) { - $this->reconcileEnabledModule($moduleName); - } + private function reconcileEntities(): void { + foreach ($this->getManagedAction('update') as $item) { + $this->updateExistingEntity($item); } - } - - /** - * For one enabled module, add new entities, update existing entities, - * and remove orphaned (stale) entities. - * - * @param string $module - */ - protected function reconcileEnabledModule(string $module): void { - foreach ($this->getManagedEntitiesToUpdate(['module' => $module]) as $todo) { - $dao = new CRM_Core_DAO_Managed(); - $dao->module = $todo['module']; - $dao->name = $todo['name']; - $dao->entity_type = $todo['entity_type']; - $dao->entity_id = $todo['entity_id']; - $dao->entity_modified_date = $todo['entity_modified_date']; - $dao->id = $todo['id']; - $this->updateExistingEntity($dao, $todo); + // reverse-order so that child entities are cleaned up before their parents + foreach (array_reverse($this->getManagedAction('delete')) as $item) { + $this->removeStaleEntity($item); } - - foreach ($this->getManagedEntitiesToDelete(['module' => $module]) as $todo) { - $dao = new CRM_Core_DAO_Managed(); - $dao->module = $todo['module']; - $dao->name = $todo['name']; - $dao->entity_type = $todo['entity_type']; - $dao->id = $todo['id']; - $dao->cleanup = $todo['cleanup']; - $dao->entity_id = $todo['entity_id']; - $this->removeStaleEntity($dao); + foreach ($this->getManagedAction('create') as $item) { + $this->insertNewEntity($item); } - foreach ($this->getManagedEntitiesToCreate(['module' => $module]) as $todo) { - $this->insertNewEntity($todo); + foreach ($this->getManagedAction('disable') as $item) { + $this->disableEntity($item); } } - /** - * Get the managed entities to be created. - * - * @param array $filters - * - * @return array - */ - protected function getManagedEntitiesToCreate(array $filters = []): array { - return $this->getManagedEntities(array_merge($filters, ['managed_action' => 'create'])); - } - - /** - * Get the managed entities to be updated. - * - * @param array $filters - * - * @return array - */ - protected function getManagedEntitiesToUpdate(array $filters = []): array { - return $this->getManagedEntities(array_merge($filters, ['managed_action' => 'update'])); - } - - /** - * Get the managed entities to be deleted. - * - * @param array $filters - * - * @return array - */ - protected function getManagedEntitiesToDelete(array $filters = []): array { - // Return array in reverse-order so that child entities are cleaned up before their parents - return array_reverse($this->getManagedEntities(array_merge($filters, ['managed_action' => 'delete']))); - } - /** * Get the managed entities that fit the criteria. * - * @param array $filters + * @param string $action * * @return array */ - protected function getManagedEntities(array $filters = []): array { - $return = []; - foreach ($this->managedActions as $actionKey => $action) { - foreach ($filters as $filterKey => $filterValue) { - if ($action[$filterKey] !== $filterValue) { - continue 2; - } - } - $return[$actionKey] = $action; - } - return $return; - } - - /** - * For all disabled modules, disable any managed entities. - */ - protected function reconcileDisabledModules() { - if (empty($this->moduleIndex[FALSE])) { - return; - } - - $in = CRM_Core_DAO::escapeStrings(array_keys($this->moduleIndex[FALSE])); - $dao = new CRM_Core_DAO_Managed(); - $dao->whereAdd("module in ($in)"); - $dao->orderBy('id DESC'); - $dao->find(); - while ($dao->fetch()) { - $this->disableEntity($dao); - - } - } - - /** - * Remove any orphaned (stale) entities that are linked to - * unknown modules. - */ - protected function reconcileUnknownModules() { - $knownModules = []; - if (array_key_exists(0, $this->moduleIndex) && is_array($this->moduleIndex[0])) { - $knownModules = array_merge($knownModules, array_keys($this->moduleIndex[0])); - } - if (array_key_exists(1, $this->moduleIndex) && is_array($this->moduleIndex[1])) { - $knownModules = array_merge($knownModules, array_keys($this->moduleIndex[1])); - } - - $dao = new CRM_Core_DAO_Managed(); - if (!empty($knownModules)) { - $in = CRM_Core_DAO::escapeStrings($knownModules); - $dao->whereAdd("module NOT IN ($in)"); - $dao->orderBy('id DESC'); - } - $dao->find(); - while ($dao->fetch()) { - $this->removeStaleEntity($dao); - } + private function getManagedAction(string $action): array { + return CRM_Utils_Array::findAll($this->plan, ['managed_action' => $action]); } /** * Create a new entity. * - * @param array $todo + * @param array $item * Entity specification (per hook_civicrm_managedEntities). */ - protected function insertNewEntity($todo) { - $params = $todo['params']; + protected function insertNewEntity(array $item) { + $params = $item['params']; // APIv4 if ($params['version'] == 4) { $params['checkPermissions'] = FALSE; // Use "save" instead of "create" action to accommodate a "match" param $params['records'] = [$params['values']]; unset($params['values']); - $result = civicrm_api4($todo['entity_type'], 'save', $params); + $result = civicrm_api4($item['entity_type'], 'save', $params); $id = $result->first()['id']; } // APIv3 else { - $result = civicrm_api($todo['entity_type'], 'create', $params); + $result = civicrm_api($item['entity_type'], 'create', $params); if (!empty($result['is_error'])) { - $this->onApiError($todo['entity_type'], 'create', $params, $result); + $this->onApiError($item['entity_type'], 'create', $params, $result); } $id = $result['id']; } $dao = new CRM_Core_DAO_Managed(); - $dao->module = $todo['module']; - $dao->name = $todo['name']; - $dao->entity_type = $todo['entity_type']; + $dao->module = $item['module']; + $dao->name = $item['name']; + $dao->entity_type = $item['entity_type']; $dao->entity_id = $id; - $dao->cleanup = $todo['cleanup'] ?? NULL; + $dao->cleanup = $item['cleanup'] ?? NULL; $dao->save(); } /** * Update an entity which is believed to exist. * - * @param CRM_Core_DAO_Managed $dao - * @param array $todo + * @param array $item * Entity specification (per hook_civicrm_managedEntities). */ - protected function updateExistingEntity($dao, $todo) { - $policy = $todo['update'] ?? 'always'; + private function updateExistingEntity(array $item) { + $policy = $item['update'] ?? 'always'; $doUpdate = ($policy === 'always'); if ($policy === 'unmodified') { // If this is not an APIv4 managed entity, the entity_modidfied_date will always be null - if (!CRM_Core_BAO_Managed::isApi4ManagedType($dao->entity_type)) { - Civi::log()->warning('ManagedEntity update policy "unmodified" specified for entity type ' . $dao->entity_type . ' which is not an APIv4 ManagedEntity. Falling back to policy "always".'); + if (!CRM_Core_BAO_Managed::isApi4ManagedType($item['entity_type'])) { + Civi::log()->warning('ManagedEntity update policy "unmodified" specified for entity type ' . $item['entity_type'] . ' which is not an APIv4 ManagedEntity. Falling back to policy "always".'); } - $doUpdate = empty($dao->entity_modified_date); + $doUpdate = empty($item['entity_modified_date']); } - if ($doUpdate && $todo['params']['version'] == 3) { - $defaults = ['id' => $dao->entity_id]; - if ($this->isActivationSupported($dao->entity_type)) { + if ($doUpdate && $item['params']['version'] == 3) { + $defaults = ['id' => $item['entity_id']]; + if ($this->isActivationSupported($item['entity_type'])) { $defaults['is_active'] = 1; } - $params = array_merge($defaults, $todo['params']); + $params = array_merge($defaults, $item['params']); $manager = CRM_Extension_System::singleton()->getManager(); - if ($dao->entity_type === 'Job' && !$manager->extensionIsBeingInstalledOrEnabled($dao->module)) { + if ($item['entity_type'] === 'Job' && !$manager->extensionIsBeingInstalledOrEnabled($item['module'])) { // Special treatment for scheduled jobs: // // If we're being called as part of enabling/installing a module then @@ -378,21 +255,23 @@ class CRM_Core_ManagedEntities { unset($params['is_active']); } - $result = civicrm_api($dao->entity_type, 'create', $params); + $result = civicrm_api($item['entity_type'], 'create', $params); if ($result['is_error']) { - $this->onApiError($dao->entity_type, 'create', $params, $result); + $this->onApiError($item['entity_type'], 'create', $params, $result); } } - elseif ($doUpdate && $todo['params']['version'] == 4) { - $params = ['checkPermissions' => FALSE] + $todo['params']; - $params['values']['id'] = $dao->entity_id; + elseif ($doUpdate && $item['params']['version'] == 4) { + $params = ['checkPermissions' => FALSE] + $item['params']; + $params['values']['id'] = $item['entity_id']; // 'match' param doesn't apply to "update" action unset($params['match']); - civicrm_api4($dao->entity_type, 'update', $params); + civicrm_api4($item['entity_type'], 'update', $params); } - if (isset($todo['cleanup']) || $doUpdate) { - $dao->cleanup = $todo['cleanup'] ?? NULL; + if (isset($item['cleanup']) || $doUpdate) { + $dao = new CRM_Core_DAO_Managed(); + $dao->id = $item['id']; + $dao->cleanup = $item['cleanup'] ?? NULL; // Reset the `entity_modified_date` timestamp if reverting record. $dao->entity_modified_date = $doUpdate ? 'null' : NULL; $dao->update(); @@ -403,24 +282,26 @@ class CRM_Core_ManagedEntities { * Update an entity which (a) is believed to exist and which (b) ought to be * inactive. * - * @param CRM_Core_DAO_Managed $dao + * @param array $item * * @throws \CiviCRM_API3_Exception */ - protected function disableEntity($dao): void { - $entity_type = $dao->entity_type; + protected function disableEntity(array $item): void { + $entity_type = $item['entity_type']; if ($this->isActivationSupported($entity_type)) { // FIXME cascading for payproc types? $params = [ 'version' => 3, - 'id' => $dao->entity_id, + 'id' => $item['entity_id'], 'is_active' => 0, ]; - $result = civicrm_api($dao->entity_type, 'create', $params); + $result = civicrm_api($item['entity_type'], 'create', $params); if ($result['is_error']) { - $this->onApiError($dao->entity_type, 'create', $params, $result); + $this->onApiError($item['entity_type'], 'create', $params, $result); } // Reset the `entity_modified_date` timestamp to indicate that the entity has not been modified by the user. + $dao = new CRM_Core_DAO_Managed(); + $dao->id = $item['id']; $dao->entity_modified_date = 'null'; $dao->update(); } @@ -429,11 +310,11 @@ class CRM_Core_ManagedEntities { /** * Remove a stale entity (if policy allows). * - * @param CRM_Core_DAO_Managed $dao + * @param array $item * @throws CRM_Core_Exception */ - protected function removeStaleEntity($dao) { - $policy = empty($dao->cleanup) ? 'always' : $dao->cleanup; + protected function removeStaleEntity(array $item) { + $policy = empty($item['cleanup']) ? 'always' : $item['cleanup']; switch ($policy) { case 'always': $doDelete = TRUE; @@ -444,12 +325,12 @@ class CRM_Core_ManagedEntities { break; case 'unused': - if (CRM_Core_BAO_Managed::isApi4ManagedType($dao->entity_type)) { - $getRefCount = \Civi\Api4\Utils\CoreUtil::getRefCount($dao->entity_type, $dao->entity_id); + if (CRM_Core_BAO_Managed::isApi4ManagedType($item['entity_type'])) { + $getRefCount = \Civi\Api4\Utils\CoreUtil::getRefCount($item['entity_type'], $item['entity_id']); } else { - $getRefCount = civicrm_api3($dao->entity_type, 'getrefcount', [ - 'id' => $dao->entity_id, + $getRefCount = civicrm_api3($item['entity_type'], 'getrefcount', [ + 'id' => $item['entity_id'], ])['values']; } @@ -468,30 +349,30 @@ class CRM_Core_ManagedEntities { // APIv4 delete - deletion from `civicrm_managed` will be taken care of by // CRM_Core_BAO_Managed::on_hook_civicrm_post() - if ($doDelete && CRM_Core_BAO_Managed::isApi4ManagedType($dao->entity_type)) { - civicrm_api4($dao->entity_type, 'delete', [ + if ($doDelete && CRM_Core_BAO_Managed::isApi4ManagedType($item['entity_type'])) { + civicrm_api4($item['entity_type'], 'delete', [ 'checkPermissions' => FALSE, - 'where' => [['id', '=', $dao->entity_id]], + 'where' => [['id', '=', $item['entity_id']]], ]); } // APIv3 delete elseif ($doDelete) { $params = [ 'version' => 3, - 'id' => $dao->entity_id, + 'id' => $item['entity_id'], ]; - $check = civicrm_api3($dao->entity_type, 'get', $params); + $check = civicrm_api3($item['entity_type'], 'get', $params); if ($check['count']) { - $result = civicrm_api($dao->entity_type, 'delete', $params); + $result = civicrm_api($item['entity_type'], 'delete', $params); if ($result['is_error']) { - if (isset($dao->name)) { - $params['name'] = $dao->name; + if (isset($item['name'])) { + $params['name'] = $item['name']; } - $this->onApiError($dao->entity_type, 'delete', $params, $result); + $this->onApiError($item['entity_type'], 'delete', $params, $result); } } CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', [ - 1 => [$dao->id, 'Integer'], + 1 => [$item['id'], 'Integer'], ]); } } @@ -547,22 +428,20 @@ class CRM_Core_ManagedEntities { /** * @param array $declarations * - * @return string|bool - * string on error, or FALSE + * @throws CRM_Core_Exception */ protected function validate($declarations) { foreach ($declarations as $module => $declare) { foreach (['name', 'module', 'entity', 'params'] as $key) { if (empty($declare[$key])) { $str = print_r($declare, TRUE); - return ts('Managed Entity (%1) is missing field "%2": %3', [$module, $key, $str]); + throw new CRM_Core_Exception(ts('Managed Entity (%1) is missing field "%2": %3', [$module, $key, $str])); } } if (!$this->isModuleRecognised($declare['module'])) { - return ts('Entity declaration references invalid or inactive module name [%1]', [$declare['module']]); + throw new CRM_Core_Exception(ts('Entity declaration references invalid or inactive module name [%1]', [$declare['module']])); } } - return FALSE; } /** @@ -600,16 +479,17 @@ class CRM_Core_ManagedEntities { /** * @param array $declarations - * - * @return array + * @param string $moduleName + * Filter to a single module. */ - protected function cleanDeclarations(array $declarations): array { - foreach ($declarations as $name => &$declare) { - if (!array_key_exists('name', $declare)) { - $declare['name'] = $name; + protected function setDeclarations(array $declarations, $moduleName = NULL) { + $this->declarations = []; + foreach ($declarations as $name => $declare) { + $declare += ['name' => $name]; + if (!$moduleName || $declare['module'] === $moduleName) { + $this->declarations[$name] = $declare; } } - return $declarations; } /** @@ -655,35 +535,53 @@ class CRM_Core_ManagedEntities { * Load declarations into the class property. * * This picks it up from hooks and enabled components. + * + * @param array|null $modules + * Limit reconciliation specified modules. */ - protected function loadDeclarations(): void { - $this->declarations = []; - foreach (CRM_Core_Component::getEnabledComponents() as $component) { - $this->declarations = array_merge($this->declarations, $component->getManagedEntities()); + protected function loadDeclarations($modules = NULL): void { + $declarations = []; + // Exclude components if given a module name. + if (!$modules || $modules === ['civicrm']) { + foreach (CRM_Core_Component::getEnabledComponents() as $component) { + $declarations = array_merge($declarations, $component->getManagedEntities()); + } } - CRM_Utils_Hook::managed($this->declarations); - $this->declarations = $this->cleanDeclarations($this->declarations); + // Ideally, given a $moduleName like 'org.foo.demo' we'd just call the function `demo_civicrm_managed()` + // But alas, that only works with old-style hooks and not the event dispatcher. + // So here we go loading declarations from every module whether we need them or not. + CRM_Utils_Hook::managed($declarations); + $this->validate($declarations); + $this->setDeclarations($declarations); } - protected function loadManagedEntityActions(): void { - $managedEntities = Managed::get(FALSE)->addSelect('*')->execute(); - $this->managedActions = []; + /** + * Builds $this->managedActions array + * + * @param array|null $modules + */ + protected function createPlan($modules = NULL): void { + $where = $modules ? [['module', 'IN', $modules]] : []; + $managedEntities = Managed::get(FALSE) + ->setWhere($where) + ->execute(); + $this->plan = []; foreach ($managedEntities as $managedEntity) { $key = "{$managedEntity['module']}_{$managedEntity['name']}_{$managedEntity['entity_type']}"; - // Set to 'delete' - it will be overwritten below if it is to be updated. - $action = 'delete'; - $this->managedActions[$key] = array_merge($managedEntity, ['managed_action' => $action]); + // Set to disable or delete if module is disabled or missing - it will be overwritten below module is active. + $action = $this->isModuleDisabled($managedEntity['module']) ? 'disable' : 'delete'; + $this->plan[$key] = array_merge($managedEntity, ['managed_action' => $action]); } foreach ($this->declarations as $declaration) { $key = "{$declaration['module']}_{$declaration['name']}_{$declaration['entity']}"; - if (isset($this->managedActions[$key])) { - $this->managedActions[$key]['params'] = $declaration['params']; - $this->managedActions[$key]['managed_action'] = 'update'; - $this->managedActions[$key]['cleanup'] = $declaration['cleanup'] ?? NULL; - $this->managedActions[$key]['update'] = $declaration['update'] ?? 'always'; + if (isset($this->plan[$key])) { + $this->plan[$key]['params'] = $declaration['params']; + $this->plan[$key]['managed_action'] = 'update'; + $this->plan[$key]['cleanup'] = $declaration['cleanup'] ?? NULL; + $this->plan[$key]['update'] = $declaration['update'] ?? 'always'; } else { - $this->managedActions[$key] = [ + $this->plan[$key] = [ 'module' => $declaration['module'], 'name' => $declaration['name'], 'entity_type' => $declaration['entity'], diff --git a/ext/afform/core/Civi/Api4/Action/Afform/Revert.php b/ext/afform/core/Civi/Api4/Action/Afform/Revert.php index 272d02d406..0fbfb399d7 100644 --- a/ext/afform/core/Civi/Api4/Action/Afform/Revert.php +++ b/ext/afform/core/Civi/Api4/Action/Afform/Revert.php @@ -3,6 +3,7 @@ namespace Civi\Api4\Action\Afform; use Civi\Api4\Generic\Result; +use CRM_Afform_ExtensionUtil as E; /** * @inheritDoc @@ -32,8 +33,7 @@ class Revert extends \Civi\Api4\Generic\BasicBatchAction { _afform_clear(); if ($this->flushManaged) { - // FIXME: more targeted reconciliation - \CRM_Core_ManagedEntities::singleton()->reconcile(); + \CRM_Core_ManagedEntities::singleton()->reconcile(E::LONG_NAME); } if ($this->flushMenu) { \CRM_Core_Menu::store(); diff --git a/ext/afform/core/Civi/Api4/Utils/AfformSaveTrait.php b/ext/afform/core/Civi/Api4/Utils/AfformSaveTrait.php index 286ca52c3e..0d109351af 100644 --- a/ext/afform/core/Civi/Api4/Utils/AfformSaveTrait.php +++ b/ext/afform/core/Civi/Api4/Utils/AfformSaveTrait.php @@ -2,6 +2,8 @@ namespace Civi\Api4\Utils; +use CRM_Afform_ExtensionUtil as E; + /** * Class AfformSaveTrait * @package Civi\Api4\Action\Afform @@ -70,8 +72,7 @@ trait AfformSaveTrait { $isChanged('is_dashlet') || (!empty($meta['is_dashlet']) && $isChanged('title')) ) { - // FIXME: more targeted reconciliation - \CRM_Core_ManagedEntities::singleton()->reconcile(); + \CRM_Core_ManagedEntities::singleton()->reconcile(E::LONG_NAME); } // Right now, permission-checks are completely on-demand. diff --git a/tests/phpunit/api/v4/Entity/ManagedEntityTest.php b/tests/phpunit/api/v4/Entity/ManagedEntityTest.php index ee238e7797..946aed53c0 100644 --- a/tests/phpunit/api/v4/Entity/ManagedEntityTest.php +++ b/tests/phpunit/api/v4/Entity/ManagedEntityTest.php @@ -383,7 +383,7 @@ class ManagedEntityTest extends UnitTestCase implements TransactionalInterface, } public function testManagedNavigationWeights() { - $this->_managedEntities = [ + $managedEntities = [ [ 'module' => 'unit.test.fake.ext', 'name' => 'Navigation_Test_Parent', @@ -474,13 +474,14 @@ class ManagedEntityTest extends UnitTestCase implements TransactionalInterface, ], ], ]; + $this->_managedEntities = $managedEntities; // Throw a monkey wrench by placing duplicates in another domain $d2 = Domain::create(FALSE) ->addValue('name', 'Decoy domain') ->addValue('version', \CRM_Utils_System::version()) ->execute()->single(); - foreach ($this->_managedEntities as $item) { + foreach ($managedEntities as $item) { $decoys[] = civicrm_api4('Navigation', 'create', [ 'checkPermissions' => FALSE, 'values' => ['domain_id' => $d2['id']] + $item['params']['values'], @@ -535,6 +536,8 @@ class ManagedEntityTest extends UnitTestCase implements TransactionalInterface, $allModules = [ new \CRM_Core_Module('unit.test.fake.ext', FALSE), ]; + // If module is disabled it will not run hook_civicrm_managed. + $this->_managedEntities = []; (new \CRM_Core_ManagedEntities($allModules))->reconcile(); // Children's weight should have been unaffected, but they should be disabled @@ -556,6 +559,7 @@ class ManagedEntityTest extends UnitTestCase implements TransactionalInterface, $allModules = [ new \CRM_Core_Module('unit.test.fake.ext', TRUE), ]; + $this->_managedEntities = $managedEntities; (new \CRM_Core_ManagedEntities($allModules))->reconcile(); // Children's weight should have been unaffected, but they should be enabled