diff --git a/src/Importer.php b/src/Importer.php index 02f9b8a..a17162e 100644 --- a/src/Importer.php +++ b/src/Importer.php @@ -3,8 +3,7 @@ namespace Drupal\default_content; use Drupal\Component\Graph\Graph; -use Drupal\Component\Render\FormattableMarkup; -use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\Core\Config\Entity\ConfigEntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\default_content\Event\DefaultContentEvents; @@ -50,20 +49,6 @@ class Importer implements ImporterInterface { protected $entityTypeManager; /** - * A list of vertex objects keyed by their link. - * - * @var array - */ - protected $vertexes = []; - - /** - * The graph entries. - * - * @var array - */ - protected $graph = []; - - /** * The link manager service. * * @var \Drupal\rest\LinkManager\LinkManagerInterface @@ -87,6 +72,9 @@ class Importer implements ImporterInterface { /** * The file map for given module. * + * The array is keyed by entity UUID and the value is the path to the file + * containing the entity. + * * @var array */ protected $fileMap = []; @@ -120,54 +108,100 @@ class Importer implements ImporterInterface { } /** + * {@inheritdoc} + */ + public function importContent($module) { + $folder = drupal_get_path('module', $module) . "/content"; + if (!file_exists($folder)) { + // There's no default to import. + return []; + } + + // Set up. + $this->fileMap = []; + // Default content uses drupal.org as domain. + // @todo Make this use a uri like default-content:. + $this->linkManager->setLinkDomain($this->linkDomain); + + // Build and sort a graph of all of the entities to create. + $graph = $this->buildGraph($folder); + // @todo what if no dependencies? + $sorted = $this->sortGraph($graph); + + // Create the entities. + $created = $this->doImportContent($sorted); + + $this->eventDispatcher->dispatch(DefaultContentEvents::IMPORT, new ImportEvent($created, $module)); + + // Reset link domain. + $this->linkManager->setLinkDomain(FALSE); + return $created; + } + + /** * Given a path to a module's content folder, build the dependency graph. * * @param string $folder * Path to a module's content folder. + * + * @return \Drupal\Component\Graph\Graph + * The graph of all of the entities. */ protected function buildGraph($folder) { - foreach ($this->entityTypeManager->getDefinitions() as $entity_type) { - $this->addEntities($folder, $entity_type); + $graph = []; + // Only process content entity types. + $content_entity_types = array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) { + return !($entity_type instanceof ConfigEntityTypeInterface); + }); + foreach ($content_entity_types as $entity_type) { + $graph = $this->addEntitiesToGraph($graph, $folder, $entity_type); } + return new Graph($graph); } /** - * Given a base contnt folder and entity type, add contents to the graph. + * For a given entity type adds entities to the graph if they are present. * + * @param array $graph + * The graph to add entities to. * @param string $folder * Path to a module's content directory. * @param EntityTypeInterface $entity_type * Entity type to be imported. + * + * @return array + * The graph with the entities added. */ - protected function addEntities($folder, EntityTypeInterface $entity_type) { - $reflection = new \ReflectionClass($entity_type->getClass()); - // We are only interested in importing content entities. - if ($reflection->implementsInterface(ConfigEntityInterface::class)) { - return; - } + protected function addEntitiesToGraph(array $graph, $folder, EntityTypeInterface $entity_type) { if (!file_exists($folder . '/' . $entity_type->id())) { - return; + // There's no entities to add. + return $graph; } - $files = $this->scanner()->scan($folder . '/' . $entity_type->id()); - // Default content uses drupal.org as domain. - // @todo Make this use a uri like default-content:. - $this->linkManager->setLinkDomain(static::LINK_DOMAIN); + $files = $this->scanner->scan($folder . '/' . $entity_type->id()); // Parse all of the files and sort them in order of dependency. foreach ($files as $file) { - $this->addFile($file, $entity_type); + $graph = $this->addEntityToGraph($graph, $file, $entity_type); } + return $graph; } /** - * Parse and decode a default content file, and add its contents to the graph. + * Parse and decode a default content file, and add the entity to the graph. * + * @param array $graph + * The graph array to add the entity to. * @param stdClass $file - * stdClass object with name and uri properties, as returned by - * DefaultContentScanner::scan - * @param EntityTypeInterface $entity_type + * Object with name and uri properties, as returned by + * DefaultContentScanner::scan(). + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type * Entity type associated with the given file. + * + * @return array + * The graph array. + * + * @throws \Exception */ - protected function addFile($file, EntityTypeInterface $entity_type) { + protected function addEntityToGraph(array $graph, $file, EntityTypeInterface $entity_type) { $contents = $this->parseFile($file); // Decode the file contents. $decoded = $this->serializer->decode($contents, 'hal_json'); @@ -176,51 +210,43 @@ class Importer implements ImporterInterface { // Throw an exception when this UUID already exists. if (isset($this->fileMap[$item_uuid])) { - $args = array( - '@uuid' => $item_uuid, - '@first' => $this->fileMap[$item_uuid]->uri, - '@second' => $file->uri, - ); // Reset link domain. $this->linkManager->setLinkDomain(FALSE); - throw new \Exception(new FormattableMarkup('Default content with uuid @uuid exists twice: @first @second', $args)); + throw new \Exception(sprintf('Default content with uuid "%s" exists twice: "%s" "%s"', $item_uuid, $this->file_map[$item_uuid]->uri, $file->uri)); } // Store the entity type with the file. $file->entity_type_id = $entity_type->id(); // Store the file in the file map. $this->fileMap[$item_uuid] = $file; - // Create a vertex for the graph. - $vertex = $this->getVertex($item_uuid); - $this->graph[$vertex->id]['edges'] = []; - if (empty($decoded['_embedded'])) { - // No dependencies to resolve. - return; - } - // Here we need to resolve our dependencies: - foreach ($decoded['_embedded'] as $embedded) { - foreach ($embedded as $item) { - $uuid = $item['uuid'][0]['value']; - $edge = $this->getVertex($uuid); - $this->graph[$vertex->id]['edges'][$edge->id] = TRUE; + // Create a item for the graph. + $graph[$item_uuid]['edges'] = []; + if (!empty($decoded['_embedded'])) { + // Here we need to resolve our dependencies: + foreach ($decoded['_embedded'] as $embedded) { + foreach ($embedded as $item) { + $uuid = $item['uuid'][0]['value']; + $graph[$item_uuid]['edges'][$uuid] = TRUE; + } } } + return $graph; } /** * Iterate the sorted entity dependency graph and save new content. * * @param array $sorted - * The sorted dependency tree, as returned by ::sortTree. + * The sorted dependency tree, as returned by ::sortGraph. * * @return array * An array of created entities, indexed by uuid. */ protected function doImportContent($sorted) { $created = []; - foreach ($sorted as $link => $details) { - if (!empty($this->fileMap[$link])) { - $file = $this->fileMap[$link]; + foreach (array_keys($sorted) as $uuid) { + if (!empty($this->fileMap[$uuid])) { + $file = $this->fileMap[$uuid]; $entity_type_id = $file->entity_type_id; $resource = $this->resourcePluginManager->getInstance(array('id' => 'entity:' . $entity_type_id)); $definition = $resource->getPluginDefinition(); @@ -236,30 +262,6 @@ class Importer implements ImporterInterface { } /** - * {@inheritdoc} - */ - public function importContent($module) { - $created = []; - $folder = drupal_get_path('module', $module) . "/content"; - - if (file_exists($folder)) { - $this->fileMap = []; - $this->buildGraph($folder); - - // @todo what if no dependencies? - $sorted = $this->sortTree($this->graph); - $created = $this->doImportContent($sorted); - - $this->eventDispatcher->dispatch(DefaultContentEvents::IMPORT, new ImportEvent($created, $module)); - } - // Reset the tree. - $this->resetTree(); - // Reset link domain. - $this->linkManager->setLinkDomain(FALSE); - return $created; - } - - /** * Parses content files. * * @param object $file @@ -273,45 +275,18 @@ class Importer implements ImporterInterface { } /** - * Resets tree properties. - */ - protected function resetTree() { - $this->graph = []; - $this->vertexes = []; - } - - /** - * Sorts dependencies tree. + * Sorts the graph. * - * @param array $graph - * Array of dependencies. + * @param \Drupal\Component\Graph\Graph $graph + * The graph of the entities. * * @return array * Array of sorted dependencies. */ - protected function sortTree(array $graph) { - $graph_object = new Graph($graph); - $sorted = $graph_object->searchAndSort(); + protected function sortGraph(Graph $graph) { + $sorted = $graph->searchAndSort(); uasort($sorted, 'Drupal\Component\Utility\SortArray::sortByWeightElement'); return array_reverse($sorted); } - /** - * Returns a vertex object for a given item link. - * - * Ensures that the same object is returned for the same item link. - * - * @param string $item_link - * The item link as a string. - * - * @return object - * The vertex object. - */ - protected function getVertex($item_link) { - if (!isset($this->vertexes[$item_link])) { - $this->vertexes[$item_link] = (object) ['id' => $item_link]; - } - return $this->vertexes[$item_link]; - } - }