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