55aeebc561f3d78a7774ce35bf8e0e7567419f6d
[yaffs-website] / web / core / lib / Drupal / Core / Config / StorageComparer.php
1 <?php
2
3 namespace Drupal\Core\Config;
4
5 use Drupal\Core\Cache\MemoryBackend;
6 use Drupal\Core\Config\Entity\ConfigDependencyManager;
7 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
8
9 /**
10  * Defines a config storage comparer.
11  */
12 class StorageComparer implements StorageComparerInterface {
13   use DependencySerializationTrait;
14
15   /**
16    * The source storage used to discover configuration changes.
17    *
18    * @var \Drupal\Core\Config\StorageInterface
19    */
20   protected $sourceStorage;
21
22   /**
23    * The source storages keyed by collection.
24    *
25    * @var \Drupal\Core\Config\StorageInterface[]
26    */
27   protected $sourceStorages;
28
29   /**
30    * The target storage used to write configuration changes.
31    *
32    * @var \Drupal\Core\Config\StorageInterface
33    */
34   protected $targetStorage;
35
36   /**
37    * The target storages keyed by collection.
38    *
39    * @var \Drupal\Core\Config\StorageInterface[]
40    */
41   protected $targetStorages;
42
43   /**
44    * The configuration manager.
45    *
46    * @var \Drupal\Core\Config\ConfigManagerInterface
47    */
48   protected $configManager;
49
50   /**
51    * List of changes to between the source storage and the target storage.
52    *
53    * The list is keyed by storage collection name.
54    *
55    * @var array
56    */
57   protected $changelist;
58
59   /**
60    * Sorted list of all the configuration object names in the source storage.
61    *
62    * The list is keyed by storage collection name.
63    *
64    * @var array
65    */
66   protected $sourceNames = [];
67
68   /**
69    * Sorted list of all the configuration object names in the target storage.
70    *
71    * The list is keyed by storage collection name.
72    *
73    * @var array
74    */
75   protected $targetNames = [];
76
77   /**
78    * A memory cache backend to statically cache source configuration data.
79    *
80    * @var \Drupal\Core\Cache\MemoryBackend
81    */
82   protected $sourceCacheStorage;
83
84   /**
85    * A memory cache backend to statically cache target configuration data.
86    *
87    * @var \Drupal\Core\Cache\MemoryBackend
88    */
89   protected $targetCacheStorage;
90
91   /**
92    * Constructs the Configuration storage comparer.
93    *
94    * @param \Drupal\Core\Config\StorageInterface $source_storage
95    *   Storage object used to read configuration.
96    * @param \Drupal\Core\Config\StorageInterface $target_storage
97    *   Storage object used to write configuration.
98    * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
99    *   The configuration manager.
100    */
101   public function __construct(StorageInterface $source_storage, StorageInterface $target_storage, ConfigManagerInterface $config_manager) {
102     // Wrap the storages in a static cache so that multiple reads of the same
103     // raw configuration object are not costly.
104     $this->sourceCacheStorage = new MemoryBackend();
105     $this->sourceStorage = new CachedStorage(
106       $source_storage,
107       $this->sourceCacheStorage
108     );
109     $this->targetCacheStorage = new MemoryBackend();
110     $this->targetStorage = new CachedStorage(
111       $target_storage,
112       $this->targetCacheStorage
113     );
114     $this->configManager = $config_manager;
115     $this->changelist[StorageInterface::DEFAULT_COLLECTION] = $this->getEmptyChangelist();
116   }
117
118   /**
119    * {@inheritdoc}
120    */
121   public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECTION) {
122     if (!isset($this->sourceStorages[$collection])) {
123       if ($collection == StorageInterface::DEFAULT_COLLECTION) {
124         $this->sourceStorages[$collection] = $this->sourceStorage;
125       }
126       else {
127         $this->sourceStorages[$collection] = $this->sourceStorage->createCollection($collection);
128       }
129     }
130     return $this->sourceStorages[$collection];
131   }
132
133   /**
134    * {@inheritdoc}
135    */
136   public function getTargetStorage($collection = StorageInterface::DEFAULT_COLLECTION) {
137     if (!isset($this->targetStorages[$collection])) {
138       if ($collection == StorageInterface::DEFAULT_COLLECTION) {
139         $this->targetStorages[$collection] = $this->targetStorage;
140       }
141       else {
142         $this->targetStorages[$collection] = $this->targetStorage->createCollection($collection);
143       }
144     }
145     return $this->targetStorages[$collection];
146   }
147
148   /**
149    * {@inheritdoc}
150    */
151   public function getEmptyChangelist() {
152     return [
153       'create' => [],
154       'update' => [],
155       'delete' => [],
156       'rename' => [],
157     ];
158   }
159
160   /**
161    * {@inheritdoc}
162    */
163   public function getChangelist($op = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) {
164     if ($op) {
165       return $this->changelist[$collection][$op];
166     }
167     return $this->changelist[$collection];
168   }
169
170   /**
171    * Adds changes to the changelist.
172    *
173    * @param string $collection
174    *   The storage collection to add changes for.
175    * @param string $op
176    *   The change operation performed. Either delete, create, rename, or update.
177    * @param array $changes
178    *   Array of changes to add to the changelist.
179    * @param array $sort_order
180    *   Array to sort that can be used to sort the changelist. This array must
181    *   contain all the items that are in the change list.
182    */
183   protected function addChangeList($collection, $op, array $changes, array $sort_order = NULL) {
184     // Only add changes that aren't already listed.
185     $changes = array_diff($changes, $this->changelist[$collection][$op]);
186     $this->changelist[$collection][$op] = array_merge($this->changelist[$collection][$op], $changes);
187     if (isset($sort_order)) {
188       $count = count($this->changelist[$collection][$op]);
189       // Sort the changelist in the same order as the $sort_order array and
190       // ensure the array is keyed from 0.
191       $this->changelist[$collection][$op] = array_values(array_intersect($sort_order, $this->changelist[$collection][$op]));
192       if ($count != count($this->changelist[$collection][$op])) {
193         throw new \InvalidArgumentException("Sorting the $op changelist should not change its length.");
194       }
195     }
196   }
197
198   /**
199    * {@inheritdoc}
200    */
201   public function createChangelist() {
202     foreach ($this->getAllCollectionNames() as $collection) {
203       $this->changelist[$collection] = $this->getEmptyChangelist();
204       $this->getAndSortConfigData($collection);
205       $this->addChangelistCreate($collection);
206       $this->addChangelistUpdate($collection);
207       $this->addChangelistDelete($collection);
208       // Only collections that support configuration entities can have renames.
209       if ($collection == StorageInterface::DEFAULT_COLLECTION) {
210         $this->addChangelistRename($collection);
211       }
212     }
213     return $this;
214   }
215
216   /**
217    * Creates the delete changelist.
218    *
219    * The list of deletes is sorted so that dependencies are deleted after
220    * configuration entities that depend on them. For example, fields should be
221    * deleted after field storages.
222    *
223    * @param string $collection
224    *   The storage collection to operate on.
225    */
226   protected function addChangelistDelete($collection) {
227     $deletes = array_diff(array_reverse($this->targetNames[$collection]), $this->sourceNames[$collection]);
228     $this->addChangeList($collection, 'delete', $deletes);
229   }
230
231   /**
232    * Creates the create changelist.
233    *
234    * The list of creates is sorted so that dependencies are created before
235    * configuration entities that depend on them. For example, field storages
236    * should be created before fields.
237    *
238    * @param string $collection
239    *   The storage collection to operate on.
240    */
241   protected function addChangelistCreate($collection) {
242     $creates = array_diff($this->sourceNames[$collection], $this->targetNames[$collection]);
243     $this->addChangeList($collection, 'create', $creates);
244   }
245
246   /**
247    * Creates the update changelist.
248    *
249    * The list of updates is sorted so that dependencies are created before
250    * configuration entities that depend on them. For example, field storages
251    * should be updated before fields.
252    *
253    * @param string $collection
254    *   The storage collection to operate on.
255    */
256   protected function addChangelistUpdate($collection) {
257     $recreates = [];
258     foreach (array_intersect($this->sourceNames[$collection], $this->targetNames[$collection]) as $name) {
259       $source_data = $this->getSourceStorage($collection)->read($name);
260       $target_data = $this->getTargetStorage($collection)->read($name);
261       if ($source_data !== $target_data) {
262         if (isset($source_data['uuid']) && $source_data['uuid'] !== $target_data['uuid']) {
263           // The entity has the same file as an existing entity but the UUIDs do
264           // not match. This means that the entity has been recreated so config
265           // synchronization should do the same.
266           $recreates[] = $name;
267         }
268         else {
269           $this->addChangeList($collection, 'update', [$name]);
270         }
271       }
272     }
273
274     if (!empty($recreates)) {
275       // Recreates should become deletes and creates. Deletes should be ordered
276       // so that dependencies are deleted first.
277       $this->addChangeList($collection, 'create', $recreates, $this->sourceNames[$collection]);
278       $this->addChangeList($collection, 'delete', $recreates, array_reverse($this->targetNames[$collection]));
279
280     }
281   }
282
283   /**
284    * Creates the rename changelist.
285    *
286    * The list of renames is created from the different source and target names
287    * with same UUID. These changes will be removed from the create and delete
288    * lists.
289    *
290    * @param string $collection
291    *   The storage collection to operate on.
292    */
293   protected function addChangelistRename($collection) {
294     // Renames will be present in both the create and delete lists.
295     $create_list = $this->getChangelist('create', $collection);
296     $delete_list = $this->getChangelist('delete', $collection);
297     if (empty($create_list) || empty($delete_list)) {
298       return;
299     }
300
301     $create_uuids = [];
302     foreach ($this->sourceNames[$collection] as $name) {
303       $data = $this->getSourceStorage($collection)->read($name);
304       if (isset($data['uuid']) && in_array($name, $create_list)) {
305         $create_uuids[$data['uuid']] = $name;
306       }
307     }
308     if (empty($create_uuids)) {
309       return;
310     }
311
312     $renames = [];
313
314     // Renames should be ordered so that dependencies are renamed last. This
315     // ensures that if there is logic in the configuration entity class to keep
316     // names in sync it will still work. $this->targetNames is in the desired
317     // order due to the use of configuration dependencies in
318     // \Drupal\Core\Config\StorageComparer::getAndSortConfigData().
319     // Node type is a good example of a configuration entity that renames other
320     // configuration when it is renamed.
321     // @see \Drupal\node\Entity\NodeType::postSave()
322     foreach ($this->targetNames[$collection] as $name) {
323       $data = $this->getTargetStorage($collection)->read($name);
324       if (isset($data['uuid']) && isset($create_uuids[$data['uuid']])) {
325         // Remove the item from the create list.
326         $this->removeFromChangelist($collection, 'create', $create_uuids[$data['uuid']]);
327         // Remove the item from the delete list.
328         $this->removeFromChangelist($collection, 'delete', $name);
329         // Create the rename name.
330         $renames[] = $this->createRenameName($name, $create_uuids[$data['uuid']]);
331       }
332     }
333
334     $this->addChangeList($collection, 'rename', $renames);
335   }
336
337   /**
338    * Removes the entry from the given operation changelist for the given name.
339    *
340    * @param string $collection
341    *   The storage collection to operate on.
342    * @param string $op
343    *   The changelist to act on. Either delete, create, rename or update.
344    * @param string $name
345    *   The name of the configuration to remove.
346    */
347   protected function removeFromChangelist($collection, $op, $name) {
348     $key = array_search($name, $this->changelist[$collection][$op]);
349     if ($key !== FALSE) {
350       unset($this->changelist[$collection][$op][$key]);
351     }
352   }
353
354   /**
355    * {@inheritdoc}
356    */
357   public function moveRenameToUpdate($rename, $collection = StorageInterface::DEFAULT_COLLECTION) {
358     $names = $this->extractRenameNames($rename);
359     $this->removeFromChangelist($collection, 'rename', $rename);
360     $this->addChangeList($collection, 'update', [$names['new_name']], $this->sourceNames[$collection]);
361   }
362
363   /**
364    * {@inheritdoc}
365    */
366   public function reset() {
367     $this->changelist = [StorageInterface::DEFAULT_COLLECTION => $this->getEmptyChangelist()];
368     $this->sourceNames = $this->targetNames = [];
369     // Reset the static configuration data caches.
370     $this->sourceCacheStorage->deleteAll();
371     $this->targetCacheStorage->deleteAll();
372     return $this->createChangelist();
373   }
374
375   /**
376    * {@inheritdoc}
377    */
378   public function hasChanges() {
379     foreach ($this->getAllCollectionNames() as $collection) {
380       foreach (['delete', 'create', 'update', 'rename'] as $op) {
381         if (!empty($this->changelist[$collection][$op])) {
382           return TRUE;
383         }
384       }
385     }
386     return FALSE;
387   }
388
389   /**
390    * {@inheritdoc}
391    */
392   public function validateSiteUuid() {
393     $source = $this->sourceStorage->read('system.site');
394     $target = $this->targetStorage->read('system.site');
395     return $source['uuid'] === $target['uuid'];
396   }
397
398   /**
399    * Gets and sorts configuration data from the source and target storages.
400    */
401   protected function getAndSortConfigData($collection) {
402     $source_storage = $this->getSourceStorage($collection);
403     $target_storage = $this->getTargetStorage($collection);
404     $target_names = $target_storage->listAll();
405     $source_names = $source_storage->listAll();
406     // Prime the static caches by reading all the configuration in the source
407     // and target storages.
408     $target_data = $target_storage->readMultiple($target_names);
409     $source_data = $source_storage->readMultiple($source_names);
410     // If the collection only supports simple configuration do not use
411     // configuration dependencies.
412     if ($collection == StorageInterface::DEFAULT_COLLECTION) {
413       $dependency_manager = new ConfigDependencyManager();
414       $this->targetNames[$collection] = $dependency_manager->setData($target_data)->sortAll();
415       $this->sourceNames[$collection] = $dependency_manager->setData($source_data)->sortAll();
416     }
417     else {
418       $this->targetNames[$collection] = $target_names;
419       $this->sourceNames[$collection] = $source_names;
420     }
421   }
422
423   /**
424    * Creates a rename name from the old and new names for the object.
425    *
426    * @param string $old_name
427    *   The old configuration object name.
428    * @param string $new_name
429    *   The new configuration object name.
430    *
431    * @return string
432    *   The configuration change name that encodes both the old and the new name.
433    *
434    * @see \Drupal\Core\Config\StorageComparerInterface::extractRenameNames()
435    */
436   protected function createRenameName($name1, $name2) {
437     return $name1 . '::' . $name2;
438   }
439
440   /**
441    * {@inheritdoc}
442    */
443   public function extractRenameNames($name) {
444     $names = explode('::', $name, 2);
445     return [
446       'old_name' => $names[0],
447       'new_name' => $names[1],
448     ];
449   }
450
451   /**
452    * {@inheritdoc}
453    */
454   public function getAllCollectionNames($include_default = TRUE) {
455     $collections = array_unique(array_merge($this->sourceStorage->getAllCollectionNames(), $this->targetStorage->getAllCollectionNames()));
456     if ($include_default) {
457       array_unshift($collections, StorageInterface::DEFAULT_COLLECTION);
458     }
459     return $collections;
460   }
461
462 }