"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/lib/Drupal/Core/Config/ConfigManager.php" (26 Nov 2020, 20567 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ConfigManager.php" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.0.8_vs_9.1.0-rc1.

    1 <?php
    2 
    3 namespace Drupal\Core\Config;
    4 
    5 use Drupal\Component\Diff\Diff;
    6 use Drupal\Core\Config\Entity\ConfigDependencyManager;
    7 use Drupal\Core\Config\Entity\ConfigEntityInterface;
    8 use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
    9 use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
   10 use Drupal\Core\Entity\EntityManagerInterface;
   11 use Drupal\Core\Entity\EntityRepositoryInterface;
   12 use Drupal\Core\Entity\EntityTypeInterface;
   13 use Drupal\Core\Entity\EntityTypeManagerInterface;
   14 use Drupal\Core\Serialization\Yaml;
   15 use Drupal\Core\StringTranslation\StringTranslationTrait;
   16 use Drupal\Core\StringTranslation\TranslationInterface;
   17 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
   18 
   19 /**
   20  * The ConfigManager provides helper functions for the configuration system.
   21  */
   22 class ConfigManager implements ConfigManagerInterface {
   23   use StringTranslationTrait;
   24   use DeprecatedServicePropertyTrait;
   25   use StorageCopyTrait;
   26 
   27   /**
   28    * {@inheritdoc}
   29    */
   30   protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
   31 
   32   /**
   33    * The entity type manager.
   34    *
   35    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   36    */
   37   protected $entityTypeManager;
   38 
   39   /**
   40    * The entity repository.
   41    *
   42    * @var \Drupal\Core\Entity\EntityRepositoryInterface
   43    */
   44   protected $entityRepository;
   45 
   46   /**
   47    * The configuration factory.
   48    *
   49    * @var \Drupal\Core\Config\ConfigFactoryInterface
   50    */
   51   protected $configFactory;
   52 
   53   /**
   54    * The typed config manager.
   55    *
   56    * @var \Drupal\Core\Config\TypedConfigManagerInterface
   57    */
   58   protected $typedConfigManager;
   59 
   60   /**
   61    * The active configuration storage.
   62    *
   63    * @var \Drupal\Core\Config\StorageInterface
   64    */
   65   protected $activeStorage;
   66 
   67   /**
   68    * The event dispatcher.
   69    *
   70    * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   71    */
   72   protected $eventDispatcher;
   73 
   74   /**
   75    * The configuration collection info.
   76    *
   77    * @var \Drupal\Core\Config\ConfigCollectionInfo
   78    */
   79   protected $configCollectionInfo;
   80 
   81   /**
   82    * The configuration storages keyed by collection name.
   83    *
   84    * @var \Drupal\Core\Config\StorageInterface[]
   85    */
   86   protected $storages;
   87 
   88   /**
   89    * Creates ConfigManager objects.
   90    *
   91    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   92    *   The entity type manager.
   93    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   94    *   The configuration factory.
   95    * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
   96    *   The typed config manager.
   97    * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
   98    *   The string translation service.
   99    * @param \Drupal\Core\Config\StorageInterface $active_storage
  100    *   The active configuration storage.
  101    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
  102    *   The event dispatcher.
  103    * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
  104    *   The entity repository.
  105    */
  106   public function __construct(EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typed_config_manager, TranslationInterface $string_translation, StorageInterface $active_storage, EventDispatcherInterface $event_dispatcher, EntityRepositoryInterface $entity_repository = NULL) {
  107     if ($entity_type_manager instanceof EntityManagerInterface) {
  108       @trigger_error('Passing the entity.manager service to ConfigManager::__construct() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Pass the new dependencies instead. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
  109       $this->entityTypeManager = \Drupal::entityTypeManager();
  110     }
  111     else {
  112       $this->entityTypeManager = $entity_type_manager;
  113     }
  114     $this->configFactory = $config_factory;
  115     $this->typedConfigManager = $typed_config_manager;
  116     $this->stringTranslation = $string_translation;
  117     $this->activeStorage = $active_storage;
  118     $this->eventDispatcher = $event_dispatcher;
  119     if ($entity_repository) {
  120       $this->entityRepository = $entity_repository;
  121     }
  122     else {
  123       @trigger_error('The entity.repository service must be passed to ConfigManager::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
  124       $this->entityRepository = \Drupal::service('entity.repository');
  125     }
  126   }
  127 
  128   /**
  129    * {@inheritdoc}
  130    */
  131   public function getEntityTypeIdByName($name) {
  132     $entities = array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) use ($name) {
  133       return ($entity_type instanceof ConfigEntityTypeInterface && $config_prefix = $entity_type->getConfigPrefix()) && strpos($name, $config_prefix . '.') === 0;
  134     });
  135     return key($entities);
  136   }
  137 
  138   /**
  139    * {@inheritdoc}
  140    */
  141   public function loadConfigEntityByName($name) {
  142     $entity_type_id = $this->getEntityTypeIdByName($name);
  143     if ($entity_type_id) {
  144       $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
  145       $id = substr($name, strlen($entity_type->getConfigPrefix()) + 1);
  146       return $this->entityTypeManager->getStorage($entity_type_id)->load($id);
  147     }
  148     return NULL;
  149   }
  150 
  151   /**
  152    * {@inheritdoc}
  153    */
  154   public function getEntityManager() {
  155     @trigger_error('ConfigManagerInterface::getEntityManager() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use ::getEntityTypeManager() instead. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
  156     return \Drupal::service('entity.manager');
  157   }
  158 
  159   /**
  160    * {@inheritdoc}
  161    */
  162   public function getEntityTypeManager() {
  163     return $this->entityTypeManager;
  164   }
  165 
  166   /**
  167    * {@inheritdoc}
  168    */
  169   public function getConfigFactory() {
  170     return $this->configFactory;
  171   }
  172 
  173   /**
  174    * {@inheritdoc}
  175    */
  176   public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) {
  177     if ($collection != StorageInterface::DEFAULT_COLLECTION) {
  178       $source_storage = $source_storage->createCollection($collection);
  179       $target_storage = $target_storage->createCollection($collection);
  180     }
  181     if (!isset($target_name)) {
  182       $target_name = $source_name;
  183     }
  184     // The output should show configuration object differences formatted as YAML.
  185     // But the configuration is not necessarily stored in files. Therefore, they
  186     // need to be read and parsed, and lastly, dumped into YAML strings.
  187     $source_data = explode("\n", Yaml::encode($source_storage->read($source_name)));
  188     $target_data = explode("\n", Yaml::encode($target_storage->read($target_name)));
  189 
  190     // Check for new or removed files.
  191     if ($source_data === ['false']) {
  192       // Added file.
  193       // Cast the result of t() to a string, as the diff engine doesn't know
  194       // about objects.
  195       $source_data = [(string) $this->t('File added')];
  196     }
  197     if ($target_data === ['false']) {
  198       // Deleted file.
  199       // Cast the result of t() to a string, as the diff engine doesn't know
  200       // about objects.
  201       $target_data = [(string) $this->t('File removed')];
  202     }
  203 
  204     return new Diff($source_data, $target_data);
  205   }
  206 
  207   /**
  208    * {@inheritdoc}
  209    */
  210   public function createSnapshot(StorageInterface $source_storage, StorageInterface $snapshot_storage) {
  211     self::replaceStorageContents($source_storage, $snapshot_storage);
  212   }
  213 
  214   /**
  215    * {@inheritdoc}
  216    */
  217   public function uninstall($type, $name) {
  218     $entities = $this->getConfigEntitiesToChangeOnDependencyRemoval($type, [$name], FALSE);
  219     // Fix all dependent configuration entities.
  220     /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
  221     foreach ($entities['update'] as $entity) {
  222       $entity->save();
  223     }
  224     // Remove all dependent configuration entities.
  225     foreach ($entities['delete'] as $entity) {
  226       $entity->setUninstalling(TRUE);
  227       $entity->delete();
  228     }
  229 
  230     $config_names = $this->configFactory->listAll($name . '.');
  231     foreach ($config_names as $config_name) {
  232       $this->configFactory->getEditable($config_name)->delete();
  233     }
  234 
  235     // Remove any matching configuration from collections.
  236     foreach ($this->activeStorage->getAllCollectionNames() as $collection) {
  237       $collection_storage = $this->activeStorage->createCollection($collection);
  238       $collection_storage->deleteAll($name . '.');
  239     }
  240 
  241     $schema_dir = drupal_get_path($type, $name) . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY;
  242     if (is_dir($schema_dir)) {
  243       // Refresh the schema cache if uninstalling an extension that provides
  244       // configuration schema.
  245       $this->typedConfigManager->clearCachedDefinitions();
  246     }
  247   }
  248 
  249   /**
  250    * {@inheritdoc}
  251    */
  252   public function getConfigDependencyManager() {
  253     $dependency_manager = new ConfigDependencyManager();
  254     // Read all configuration using the factory. This ensures that multiple
  255     // deletes during the same request benefit from the static cache. Using the
  256     // factory also ensures configuration entity dependency discovery has no
  257     // dependencies on the config entity classes. Assume data with UUID is a
  258     // config entity. Only configuration entities can be depended on so we can
  259     // ignore everything else.
  260     $data = array_map(function ($config) {
  261       $data = $config->get();
  262       if (isset($data['uuid'])) {
  263         return $data;
  264       }
  265       return FALSE;
  266     }, $this->configFactory->loadMultiple($this->activeStorage->listAll()));
  267     $dependency_manager->setData(array_filter($data));
  268     return $dependency_manager;
  269   }
  270 
  271   /**
  272    * {@inheritdoc}
  273    */
  274   public function findConfigEntityDependents($type, array $names, ConfigDependencyManager $dependency_manager = NULL) {
  275     if (!$dependency_manager) {
  276       $dependency_manager = $this->getConfigDependencyManager();
  277     }
  278     $dependencies = [];
  279     foreach ($names as $name) {
  280       $dependencies = array_merge($dependencies, $dependency_manager->getDependentEntities($type, $name));
  281     }
  282     return $dependencies;
  283   }
  284 
  285   /**
  286    * {@inheritdoc}
  287    */
  288   public function findConfigEntityDependentsAsEntities($type, array $names, ConfigDependencyManager $dependency_manager = NULL) {
  289     $dependencies = $this->findConfigEntityDependents($type, $names, $dependency_manager);
  290     $entities = [];
  291     $definitions = $this->entityTypeManager->getDefinitions();
  292     foreach ($dependencies as $config_name => $dependency) {
  293       // Group by entity type to efficient load entities using
  294       // \Drupal\Core\Entity\EntityStorageInterface::loadMultiple().
  295       $entity_type_id = $this->getEntityTypeIdByName($config_name);
  296       // It is possible that a non-configuration entity will be returned if a
  297       // simple configuration object has a UUID key. This would occur if the
  298       // dependents of the system module are calculated since system.site has
  299       // a UUID key.
  300       if ($entity_type_id) {
  301         $id = substr($config_name, strlen($definitions[$entity_type_id]->getConfigPrefix()) + 1);
  302         $entities[$entity_type_id][] = $id;
  303       }
  304     }
  305     $entities_to_return = [];
  306     foreach ($entities as $entity_type_id => $entities_to_load) {
  307       $storage = $this->entityTypeManager->getStorage($entity_type_id);
  308       // Remove the keys since there are potential ID clashes from different
  309       // configuration entity types.
  310       $entities_to_return = array_merge($entities_to_return, array_values($storage->loadMultiple($entities_to_load)));
  311     }
  312     return $entities_to_return;
  313   }
  314 
  315   /**
  316    * {@inheritdoc}
  317    */
  318   public function getConfigEntitiesToChangeOnDependencyRemoval($type, array $names, $dry_run = TRUE) {
  319     $dependency_manager = $this->getConfigDependencyManager();
  320 
  321     // Store the list of dependents in three separate variables. This allows us
  322     // to determine how the dependency graph changes as entities are fixed by
  323     // calling the onDependencyRemoval() method.
  324 
  325     // The list of original dependents on $names. This list never changes.
  326     $original_dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
  327 
  328     // The current list of dependents on $names. This list is recalculated when
  329     // calling an entity's onDependencyRemoval() method results in the entity
  330     // changing. This list is passed to each entity's onDependencyRemoval()
  331     // method as the list of affected entities.
  332     $current_dependents = $original_dependents;
  333 
  334     // The list of dependents to process. This list changes as entities are
  335     // processed and are either fixed or deleted.
  336     $dependents_to_process = $original_dependents;
  337 
  338     // Initialize other variables.
  339     $affected_uuids = [];
  340     $return = [
  341       'update' => [],
  342       'delete' => [],
  343       'unchanged' => [],
  344     ];
  345 
  346     // Try to fix the dependents and find out what will happen to the dependency
  347     // graph. Entities are processed in the order of most dependent first. For
  348     // example, this ensures that Menu UI third party dependencies on node types
  349     // are fixed before processing the node type's other dependents.
  350     while ($dependent = array_pop($dependents_to_process)) {
  351       /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $dependent */
  352       if ($dry_run) {
  353         // Clone the entity so any changes do not change any static caches.
  354         $dependent = clone $dependent;
  355       }
  356       $fixed = FALSE;
  357       if ($this->callOnDependencyRemoval($dependent, $current_dependents, $type, $names)) {
  358         // Recalculate dependencies and update the dependency graph data.
  359         $dependent->calculateDependencies();
  360         $dependency_manager->updateData($dependent->getConfigDependencyName(), $dependent->getDependencies());
  361         // Based on the updated data rebuild the list of current dependents.
  362         // This will remove entities that are no longer dependent after the
  363         // recalculation.
  364         $current_dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
  365         // Rebuild the list of entities that we need to process using the new
  366         // list of current dependents and removing any entities that we've
  367         // already processed.
  368         $dependents_to_process = array_filter($current_dependents, function ($current_dependent) use ($affected_uuids) {
  369           return !in_array($current_dependent->uuid(), $affected_uuids);
  370         });
  371         // Ensure that the dependent has actually been fixed. It is possible
  372         // that other dependencies cause it to still be in the list.
  373         $fixed = TRUE;
  374         foreach ($dependents_to_process as $key => $entity) {
  375           if ($entity->uuid() == $dependent->uuid()) {
  376             $fixed = FALSE;
  377             unset($dependents_to_process[$key]);
  378             break;
  379           }
  380         }
  381         if ($fixed) {
  382           $affected_uuids[] = $dependent->uuid();
  383           $return['update'][] = $dependent;
  384         }
  385       }
  386       // If the entity cannot be fixed then it has to be deleted.
  387       if (!$fixed) {
  388         $affected_uuids[] = $dependent->uuid();
  389         // Deletes should occur in the order of the least dependent first. For
  390         // example, this ensures that fields are removed before field storages.
  391         array_unshift($return['delete'], $dependent);
  392       }
  393     }
  394     // Use the list of affected UUIDs to filter the original list to work out
  395     // which configuration entities are unchanged.
  396     $return['unchanged'] = array_filter($original_dependents, function ($dependent) use ($affected_uuids) {
  397       return !(in_array($dependent->uuid(), $affected_uuids));
  398     });
  399 
  400     return $return;
  401   }
  402 
  403   /**
  404    * {@inheritdoc}
  405    */
  406   public function getConfigCollectionInfo() {
  407     if (!isset($this->configCollectionInfo)) {
  408       $this->configCollectionInfo = new ConfigCollectionInfo();
  409       $this->eventDispatcher->dispatch(ConfigEvents::COLLECTION_INFO, $this->configCollectionInfo);
  410     }
  411     return $this->configCollectionInfo;
  412   }
  413 
  414   /**
  415    * Calls an entity's onDependencyRemoval() method.
  416    *
  417    * A helper method to call onDependencyRemoval() with the correct list of
  418    * affected entities. This list should only contain dependencies on the
  419    * entity. Configuration and content entity dependencies will be converted
  420    * into entity objects.
  421    *
  422    * @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
  423    *   The entity to call onDependencyRemoval() on.
  424    * @param \Drupal\Core\Config\Entity\ConfigEntityInterface[] $dependent_entities
  425    *   The list of dependent configuration entities.
  426    * @param string $type
  427    *   The type of dependency being checked. Either 'module', 'theme', 'config'
  428    *   or 'content'.
  429    * @param array $names
  430    *   The specific names to check. If $type equals 'module' or 'theme' then it
  431    *   should be a list of module names or theme names. In the case of 'config'
  432    *   or 'content' it should be a list of configuration dependency names.
  433    *
  434    * @return bool
  435    *   TRUE if the entity has changed as a result of calling the
  436    *   onDependencyRemoval() method, FALSE if not.
  437    */
  438   protected function callOnDependencyRemoval(ConfigEntityInterface $entity, array $dependent_entities, $type, array $names) {
  439     $entity_dependencies = $entity->getDependencies();
  440     if (empty($entity_dependencies)) {
  441       // No dependent entities nothing to do.
  442       return FALSE;
  443     }
  444 
  445     $affected_dependencies = [
  446       'config' => [],
  447       'content' => [],
  448       'module' => [],
  449       'theme' => [],
  450     ];
  451 
  452     // Work out if any of the entity's dependencies are going to be affected.
  453     if (isset($entity_dependencies[$type])) {
  454       // Work out which dependencies the entity has in common with the provided
  455       // $type and $names.
  456       $affected_dependencies[$type] = array_intersect($entity_dependencies[$type], $names);
  457 
  458       // If the dependencies are entities we need to convert them into objects.
  459       if ($type == 'config' || $type == 'content') {
  460         $affected_dependencies[$type] = array_map(function ($name) use ($type) {
  461           if ($type == 'config') {
  462             return $this->loadConfigEntityByName($name);
  463           }
  464           else {
  465             // Ignore the bundle.
  466             list($entity_type_id,, $uuid) = explode(':', $name);
  467             return $this->entityRepository->loadEntityByConfigTarget($entity_type_id, $uuid);
  468           }
  469         }, $affected_dependencies[$type]);
  470       }
  471     }
  472 
  473     // Merge any other configuration entities into the list of affected
  474     // dependencies if necessary.
  475     if (isset($entity_dependencies['config'])) {
  476       foreach ($dependent_entities as $dependent_entity) {
  477         if (in_array($dependent_entity->getConfigDependencyName(), $entity_dependencies['config'])) {
  478           $affected_dependencies['config'][] = $dependent_entity;
  479         }
  480       }
  481     }
  482 
  483     // Key the entity arrays by config dependency name to make searching easy.
  484     foreach (['config', 'content'] as $dependency_type) {
  485       $affected_dependencies[$dependency_type] = array_combine(
  486         array_map(function ($entity) {
  487           return $entity->getConfigDependencyName();
  488         }, $affected_dependencies[$dependency_type]),
  489         $affected_dependencies[$dependency_type]
  490       );
  491     }
  492 
  493     // Inform the entity.
  494     return $entity->onDependencyRemoval($affected_dependencies);
  495   }
  496 
  497   /**
  498    * {@inheritdoc}
  499    */
  500   public function findMissingContentDependencies() {
  501     $content_dependencies = [];
  502     $missing_dependencies = [];
  503     foreach ($this->activeStorage->readMultiple($this->activeStorage->listAll()) as $config_data) {
  504       if (isset($config_data['dependencies']['content'])) {
  505         $content_dependencies = array_merge($content_dependencies, $config_data['dependencies']['content']);
  506       }
  507       if (isset($config_data['dependencies']['enforced']['content'])) {
  508         $content_dependencies = array_merge($content_dependencies, $config_data['dependencies']['enforced']['content']);
  509       }
  510     }
  511     foreach (array_unique($content_dependencies) as $content_dependency) {
  512       // Format of the dependency is entity_type:bundle:uuid.
  513       list($entity_type, $bundle, $uuid) = explode(':', $content_dependency, 3);
  514       if (!$this->entityRepository->loadEntityByUuid($entity_type, $uuid)) {
  515         $missing_dependencies[$uuid] = [
  516           'entity_type' => $entity_type,
  517           'bundle' => $bundle,
  518           'uuid' => $uuid,
  519         ];
  520       }
  521     }
  522     return $missing_dependencies;
  523   }
  524 
  525 }