ConfigImporter.php 10.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
<?php

/**
 * @file
 * Contains \Drupal\Core\Config\ConfigImporter.
 */

namespace Drupal\Core\Config;

10
use Drupal\Core\Config\TypedConfigManager;
11
use Drupal\Core\Entity\EntityManagerInterface;
12
use Drupal\Core\Lock\LockBackendInterface;
13
use Drupal\Component\Uuid\UuidInterface;
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Defines a configuration importer.
 *
 * A config importer imports the changes into the configuration system. To
 * determine which changes to import a StorageComparer in used.
 *
 * @see \Drupal\Core\Config\StorageComparerInterface
 *
 * The ConfigImporter has a identifier which is used to construct event names.
 * The events fired during an import are:
 * - 'config.importer.validate': Events listening can throw a
 *   \Drupal\Core\Config\ConfigImporterException to prevent an import from
 *   occurring.
 *   @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
 * - 'config.importer.import': Events listening can react to a successful import.
 *   @see \Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber
 *
 * @see \Drupal\Core\Config\ConfigImporterEvent
 */
class ConfigImporter {

  /**
   * The name used to identify events and the lock.
   */
  const ID = 'config.importer';

  /**
   * The storage comparer used to discover configuration changes.
   *
   * @var \Drupal\Core\Config\StorageComparerInterface
   */
  protected $storageComparer;

  /**
   * The event dispatcher used to notify subscribers.
   *
52
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
53
54
55
56
57
58
59
60
61
62
63
64
65
   */
  protected $eventDispatcher;

  /**
   * The configuration factory.
   *
   * @var \Drupal\Core\Config\ConfigFactory
   */
  protected $configFactory;

  /**
   * The plugin manager for entities.
   *
66
   * @var \Drupal\Core\Entity\EntityManagerInterface
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
   */
  protected $entityManager;

  /**
   * The used lock backend instance.
   *
   * @var \Drupal\Core\Lock\LockBackendInterface
   */
  protected $lock;

  /**
   * List of changes processed by the import().
   *
   * @var array
   */
  protected $processed;

  /**
   * Indicates changes to import have been validated.
   *
   * @var bool
   */
  protected $validated;

91
92
93
94
95
96
97
  /**
   * The UUID service.
   *
   * @var \Drupal\Component\Uuid\UuidInterface
   */
  protected $uuidService;

98
99
100
101
102
103
104
  /**
   * The typed config manager.
   *
   * @var \Drupal\Core\Config\TypedConfigManager
   */
  protected $typedConfigManager;

105
106
107
108
109
110
111
112
113
114
  /**
   * Constructs a configuration import object.
   *
   * @param \Drupal\Core\Config\StorageComparerInterface $storage_comparer
   *   A storage comparer object used to determin configuration changes and
   *   access the source and target storage objects.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   The event dispatcher used to notify subscribers of config import events.
   * @param \Drupal\Core\Config\ConfigFactory $config_factory
   *   The config factory that statically caches config objects.
115
   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
116
117
118
   *   The entity manager used to import config entities.
   * @param \Drupal\Core\Lock\LockBackendInterface
   *   The lock backend to ensure multiple imports do not occur at the same time.
119
120
   * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
   *   The UUID service.
121
122
   * @param \Drupal\Core\Config\TypedConfigManager $typed_config
   *   The typed configuration manager.
123
   */
124
  public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManagerInterface $entity_manager, LockBackendInterface $lock, UuidInterface $uuid_service, TypedConfigManager $typed_config) {
125
126
127
128
129
    $this->storageComparer = $storage_comparer;
    $this->eventDispatcher = $event_dispatcher;
    $this->configFactory = $config_factory;
    $this->entityManager = $entity_manager;
    $this->lock = $lock;
130
    $this->uuidService = $uuid_service;
131
    $this->typedConfigManager = $typed_config;
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
    $this->processed = $this->storageComparer->getEmptyChangelist();
  }

  /**
   * Gets the configuration storage comparer.
   *
   * @return \Drupal\Core\Config\StorageComparerInterface
   *   Storage comparer object used to calculate configuration changes.
   */
  public function getStorageComparer() {
    return $this->storageComparer;
  }

  /**
   * Resets the storage comparer and processed list.
   *
   * @return \Drupal\Core\Config\ConfigImporter
   *   The ConfigImporter instance.
   */
  public function reset() {
    $this->storageComparer->reset();
    $this->processed = $this->storageComparer->getEmptyChangelist();
    $this->validated = FALSE;
    return $this;
  }

  /**
   * Checks if there are any unprocessed changes.
   *
   * @param array $ops
   *   The operations to check for changes. Defaults to all operations, i.e.
   *   array('delete', 'create', 'update').
   *
   * @return bool
   *   TRUE if there are changes to process and FALSE if not.
   */
  public function hasUnprocessedChanges($ops = array('delete', 'create', 'update')) {
    foreach ($ops as $op) {
      if (count($this->getUnprocessed($op))) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Gets list of processed changes.
   *
   * @return array
   *   An array containing a list of processed changes.
   */
  public function getProcessed() {
    return $this->processed;
  }

  /**
   * Sets a change as processed.
   *
   * @param string $op
   *   The change operation performed, either delete, create or update.
   * @param string $name
   *   The name of the configuration processed.
   */
  protected function setProcessed($op, $name) {
    $this->processed[$op][] = $name;
  }

  /**
   * Gets a list of unprocessed changes for a given operation.
   *
   * @param string $op
   *   The change operation to get the unprocessed list for, either delete,
   *   create or update.
   *
   * @return array
   *   An array of configuration names.
   */
  public function getUnprocessed($op) {
    return array_diff($this->storageComparer->getChangelist($op), $this->processed[$op]);
  }

  /**
   * Imports the changelist to the target storage.
   *
   * @throws \Drupal\Core\Config\ConfigException
   *
   * @return \Drupal\Core\Config\ConfigImporter
   *   The ConfigImporter instance.
   */
  public function import() {
    if ($this->hasUnprocessedChanges()) {
      // Ensure that the changes have been validated.
      $this->validate();

      if (!$this->lock->acquire(static::ID)) {
        // Another process is synchronizing configuration.
        throw new ConfigImporterException(sprintf('%s is already importing', static::ID));
      }
      $this->importInvokeOwner();
      $this->importConfig();
      // Allow modules to react to a import.
      $this->notify('import');

      // The import is now complete.
      $this->lock->release(static::ID);
      $this->reset();
    }
    return $this;
  }

  /**
   * Dispatches validate event for a ConfigImporter object.
   *
   * Events should throw a \Drupal\Core\Config\ConfigImporterException to
   * prevent an import from occurring.
   */
  public function validate() {
    if (!$this->validated) {
250
251
252
      if (!$this->storageComparer->validateSiteUuid()) {
        throw new ConfigImporterException('Site UUID in source storage does not match the target storage.');
      }
253
254
255
256
257
258
259
260
261
262
263
264
      $this->notify('validate');
      $this->validated = TRUE;
    }
    return $this;
  }

  /**
   * Writes an array of config changes from the source to the target storage.
   */
  protected function importConfig() {
    foreach (array('delete', 'create', 'update') as $op) {
      foreach ($this->getUnprocessed($op) as $name) {
265
        $config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
        if ($op == 'delete') {
          $config->delete();
        }
        else {
          $data = $this->storageComparer->getSourceStorage()->read($name);
          $config->setData($data ? $data : array());
          $config->save();
        }
        $this->setProcessed($op, $name);
      }
    }
  }

  /**
   * Invokes import* methods on configuration entity storage controllers.
   *
   * Allow modules to take over configuration change operations for higher-level
   * configuration data.
   *
   * @todo Add support for other extension types; e.g., themes etc.
   */
  protected function importInvokeOwner() {
    // First pass deleted, then new, and lastly changed configuration, in order
    // to handle dependencies correctly.
    foreach (array('delete', 'create', 'update') as $op) {
      foreach ($this->getUnprocessed($op) as $name) {
        // Call to the configuration entity's storage controller to handle the
        // configuration change.
        $handled_by_module = FALSE;
        // Validate the configuration object name before importing it.
        // Config::validateName($name);
        if ($entity_type = config_get_entity_type_by_name($name)) {
298
          $old_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
299
300
301
          if ($old_data = $this->storageComparer->getTargetStorage()->read($name)) {
            $old_config->initWithData($old_data);
          }
302
303

          $data = $this->storageComparer->getSourceStorage()->read($name);
304
          $new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
          if ($data !== FALSE) {
            $new_config->setData($data);
          }

          $method = 'import' . ucfirst($op);
          $handled_by_module = $this->entityManager->getStorageController($entity_type)->$method($name, $new_config, $old_config);
        }
        if (!empty($handled_by_module)) {
          $this->setProcessed($op, $name);
        }
      }
    }
  }

  /**
   * Dispatches a config importer event.
   *
   * @param string $event_name
   *   The name of the config importer event to dispatch.
   */
  protected function notify($event_name) {
    $this->eventDispatcher->dispatch(static::ID . '.' . $event_name, new ConfigImporterEvent($this));
  }

  /**
   * Determines if a import is already running.
   *
   * @return bool
   *   TRUE if an import is already running, FALSE if not.
   */
  public function alreadyImporting() {
    return !$this->lock->lockMayBeAvailable(static::ID);
  }

  /**
   * Returns the identifier for events and locks.
   *
   * @return string
   *   The identifier for events and locks.
   */
  public function getId() {
    return static::ID;
  }

}