default_content-2.0.x-dev/src/Exporter.php
src/Exporter.php
<?php
namespace Drupal\default_content;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\InfoParserInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\default_content\Event\DefaultContentEvents;
use Drupal\default_content\Event\ExportEvent;
use Drupal\default_content\Normalizer\ContentEntityNormalizerInterface;
use Drupal\user\UserInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* A service for handling import of default content.
*
* @todo throw useful exceptions
*/
class Exporter implements ExporterInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity repository.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The info file parser.
*
* @var \Drupal\Core\Extension\InfoParserInterface
*/
protected $infoParser;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The content file storage.
*
* @var \Drupal\default_content\ContentFileStorageInterface
*/
protected $contentFileStorage;
/**
* The YAML normalizer.
*
* @var \Drupal\default_content\Normalizer\ContentEntityNormalizer
*/
protected $contentEntityNormalizer;
/**
* Constructs the default content manager.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Extension\InfoParserInterface $info_parser
* The info file parser.
* @param \Drupal\default_content\ContentFileStorageInterface $content_file_storage
* The content file storage service.
* @param \Drupal\default_content\Normalizer\ContentEntityNormalizerInterface $content_entity_normalizer
* The content entity normalizer.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, InfoParserInterface $info_parser, ContentFileStorageInterface $content_file_storage, ContentEntityNormalizerInterface $content_entity_normalizer) {
$this->entityTypeManager = $entity_type_manager;
$this->entityRepository = $entity_repository;
$this->eventDispatcher = $event_dispatcher;
$this->moduleHandler = $module_handler;
$this->infoParser = $info_parser;
$this->contentFileStorage = $content_file_storage;
$this->contentEntityNormalizer = $content_entity_normalizer;
}
/**
* {@inheritdoc}
*/
public function exportContent($entity_type_id, $entity_id, $destination = NULL) {
$storage = $this->entityTypeManager->getStorage($entity_type_id);
$entity = $storage->load($entity_id);
if (!$entity) {
throw new \InvalidArgumentException(sprintf('Entity "%s" with ID "%s" does not exist', $entity_type_id, $entity_id));
}
if (!($entity instanceof ContentEntityInterface)) {
throw new \InvalidArgumentException(sprintf('Entity "%s" with ID "%s" is not a content entity', $entity_type_id, $entity_id));
}
$normalized = $this->contentEntityNormalizer->normalize($entity);
$return = Yaml::encode($normalized);
if ($destination) {
$folder = dirname(dirname($destination));
$this->contentFileStorage->writeEntity($folder, $return, $entity, basename($destination));
}
$this->eventDispatcher->dispatch(new ExportEvent($entity), DefaultContentEvents::EXPORT);
return $return;
}
/**
* {@inheritdoc}
*/
public function exportContentWithReferences($entity_type_id, $entity_id, $folder = NULL) {
$storage = $this->entityTypeManager->getStorage($entity_type_id);
$entity = $storage->load($entity_id);
if (!$entity) {
throw new \InvalidArgumentException(sprintf('Entity "%s" with ID "%s" does not exist', $entity_type_id, $entity_id));
}
if (!($entity instanceof ContentEntityInterface)) {
throw new \InvalidArgumentException(sprintf('Entity "%s" with ID "%s" is not a content entity', $entity_type_id, $entity_id));
}
$entities = [$entity->uuid() => $entity];
$entities = $this->getEntityReferencesRecursive($entity, 0, $entities);
// Serialize all entities and key them by entity TYPE and uuid.
$serialized_entities_per_type = [];
foreach ($entities as $entity) {
$normalized = $this->contentEntityNormalizer->normalize($entity);
$encoded = Yaml::encode($normalized);
$serialized_entities_per_type[$entity->getEntityTypeId()][$entity->uuid()] = $encoded;
if ($folder) {
$this->contentFileStorage->writeEntity($folder, $encoded, $entity);
}
}
return $serialized_entities_per_type;
}
/**
* {@inheritdoc}
*/
public function exportModuleContent($module_name, $folder = NULL) {
$info_file = $this->moduleHandler->getModule($module_name)->getPathname();
$info = $this->infoParser->parse($info_file);
$exported_content = [];
if (empty($info['default_content'])) {
return $exported_content;
}
foreach ($info['default_content'] as $entity_type => $uuids) {
foreach ($uuids as $uuid) {
$entity = $this->entityRepository->loadEntityByUuid($entity_type, $uuid);
if (!$entity) {
throw new \InvalidArgumentException(sprintf('Entity "%s" with UUID "%s" does not exist', $entity_type, $uuid));
}
$exported_content[$entity_type][$uuid] = $this->exportContent($entity_type, $entity->id());
if ($folder) {
$this->contentFileStorage->writeEntity($folder, $exported_content[$entity_type][$uuid], $entity);
}
}
}
return $exported_content;
}
/**
* {@inheritdoc}
*/
public function exportModuleContentWithReferences($module_name, $folder = NULL) {
$info_file = $this->moduleHandler->getModule($module_name)->getPathname();
$info = $this->infoParser->parse($info_file);
$exported_content = [];
if (empty($info['default_content'])) {
return $exported_content;
}
foreach ($info['default_content'] as $entity_type => $uuids) {
foreach ($uuids as $uuid) {
$entity = $this->entityRepository->loadEntityByUuid($entity_type, $uuid);
if (!$entity) {
throw new \InvalidArgumentException(sprintf('Entity "%s" with UUID "%s" does not exist', $entity_type, $uuid));
}
$exported_content_with_references = $this->exportContentWithReferences($entity_type, $entity->id(), $folder);
foreach ($exported_content_with_references as $ref_entity_type => $entities) {
foreach ($entities as $ref_uuid => $entity) {
$exported_content[$ref_entity_type][$ref_uuid] = $entity;
}
}
}
}
return $exported_content;
}
/**
* Returns all referenced entities of an entity.
*
* This method is also recursive to support use-cases like a node -> media
* -> file.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity.
* @param int $depth
* Guard against infinite recursion.
* @param \Drupal\Core\Entity\ContentEntityInterface[] $indexed_dependencies
* Previously discovered dependencies.
*
* @return \Drupal\Core\Entity\ContentEntityInterface[]
* Keyed array of entities indexed by entity type and ID.
*/
protected function getEntityReferencesRecursive(ContentEntityInterface $entity, $depth = 0, array &$indexed_dependencies = []) {
$entity_dependencies = $entity->referencedEntities();
foreach ($entity_dependencies as $dependent_entity) {
// Config entities should not be exported but rather provided by default
// config.
if (!($dependent_entity instanceof ContentEntityInterface)) {
continue;
}
// Do not export user 0 or 1.
if ($dependent_entity instanceof UserInterface && \in_array($dependent_entity->id(), [0, 1])) {
continue;
}
// Using UUID to keep dependencies unique to prevent recursion.
$key = $dependent_entity->uuid();
if (isset($indexed_dependencies[$key])) {
// Do not add already indexed dependencies.
continue;
}
// Do not export composite entity types directly but include their
// children.
if (!$dependent_entity->getEntityType()->get('entity_revision_parent_type_field')) {
$indexed_dependencies[$key] = $dependent_entity;
}
// Build in some support against infinite recursion.
if ($depth < 6) {
// @todo Make $depth configurable.
$indexed_dependencies += $this->getEntityReferencesRecursive($dependent_entity, $depth + 1, $indexed_dependencies);
}
}
return $indexed_dependencies;
}
}
