contentserialize-8.x-1.x-dev/src/Commands/ContentSerializeCommands.php
src/Commands/ContentSerializeCommands.php
<?php namespace Drupal\contentserialize\Commands; use Drupal\bulkentity\EntityLoaderInterface; use Drupal\contentserialize\Destination\FileDestination; use Drupal\contentserialize\ExporterInterface; use Drupal\contentserialize\ImporterInterface; use Drupal\contentserialize\Source\FileSource; use Drupal\contentserialize\Traversables; use Drupal\contentserialize\Utility; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drush\Commands\DrushCommands; /** * Provides drush 9 commands for Content Serialization. * * D8 standards are not to translate exception messages, but many/all used here * are user-facing, like eg. \Drush\Commands\sql\SqlSyncCommands::validate(), so * they're passed through dt(). */ class ContentSerializeCommands extends DrushCommands { /** * The options provider. * * @var \Drupal\contentserialize\Commands\ContentSerializeOptionsProvider */ protected $optionsProvider; /** * The entity type manager. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** * The entity type bundle information service. * * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface */ protected $bundleInfo; /** * The bulk entity loader. * * @var \Drupal\bulkentity\EntityLoaderInterface */ protected $bulkLoader; /** * The content exporter. * * @var \Drupal\contentserialize\ExporterInterface */ protected $exporter; /** * The content importer. * * @var \Drupal\contentserialize\ImporterInterface */ protected $importer; /** * Create the content serialization commands object. * * @param \Drupal\contentserialize\Commands\ContentSerializeOptionsProvider $options_provider */ public function __construct( $options_provider, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info, EntityLoaderInterface $bulk_loader, ImporterInterface $importer, ExporterInterface $exporter ) { parent::__construct(); $this->optionsProvider = $options_provider; $this->entityTypeManager = $entity_type_manager; $this->bundleInfo = $bundle_info; $this->bulkLoader = $bulk_loader; $this->importer = $importer; $this->exporter = $exporter; } /** * Exports a single entity * * @param $entity_type * The entity type to export. * @param $entity_id * The ID of the entity to export. * @param array $options An associative array of options whose values come * from cli, aliases, config, etc. * * @option destination * Folder to export to; you can also use the environment variable CONTENTSERIALIZE_EXPORT_DESTINATION; defaults to the current directory * @option format * The serialization format * * @command contentserialize:export * @aliases cse,contentserialize-export * * @throws \Exception * On errors. */ public function export($entity_type, $entity_id, array $options = ['destination' => self::REQ, 'format' => self::REQ]) { $entity = $this->loadContentEntity($entity_type, $entity_id); $destination = $this->getExportDestination($options); list($format, $context) = $this->optionsProvider->getFormatAndContext($options); $destination->save($this->exporter->export($entity, $format, $context)); } /** * Exports an entity and any others that reference it * * @param $entity_type * The entity type to export. * @param $entity_id * The ID of the entity to export. * @param array $options * An associative array of options whose values come from cli, aliases, * config, etc. * * @option exclude * Entity types and/or bundles to exclude * @option destination * Folder to export to; you can also use the environment variable CONTENTSERIALIZE_EXPORT_DESTINATION; defaults to the current directory * @option format * The serialization format * * @command contentserialize:export-referenced * @aliases cser,contentserialize-export-referenced * * @throws \Exception * On error */ public function exportReferenced($entity_type, $entity_id, array $options = ['exclude' => self::REQ, 'destination' => self::REQ, 'format' => self::REQ]) { $entity = $this->loadContentEntity($entity_type, $entity_id); $destination = $this->getExportDestination($options); $entities = Utility::enumerateEntitiesAndDependencies([$entity]); $excluded = $this->optionsProvider->getExcluded($options); if ($excluded) { // Filter out entity types and bundles specified in the exclude option. $entities = Traversables::filter($entities, function (ContentEntityInterface $entity) use ($excluded) { $entity_type = $entity->getEntityTypeId(); $bundle = $entity->bundle(); $type_allowed = !in_array($entity_type, $excluded['entity_type']); $bundle_allowed = empty($excluded['bundles'][$entity_type]) || !in_array($bundle, $excluded['bundles'][$entity_type]); return $type_allowed && $bundle_allowed; }); } list($format, $context) = $this->optionsProvider->getFormatAndContext($options); $destination->saveMultiple($this->exporter->exportMultiple($entities, $format, $context)); } /** * Exports all content from any appropriate entity types. * * @param array $options * An associative array of options whose values come from cli, aliases, * config, etc. * * @option exclude * Entity types and/or bundles to exclude * @option destination * Folder to export to; you can also use the environment variable CONTENTSERIALIZE_EXPORT_DESTINATION; defaults to the current directory * @option format * The serialization format * @usage drush csea * Export all content entities on the site into the current directory. * @usage drush csea --destination=/path/to/content * Export all content entities into the specified directory. * @usage drush csea --exclude=taxonomy_term * Export all content entities except taxonomy terms. * @usage drush csea --exclude=node:page:blog * Export all content entities except the node bundles 'page' and 'blog'. * @usage drush csea --exclude=node:page,user * Export all content entities except the node bundle 'page' and users. * * @command contentserialize:export-all * @aliases csea,contentserialize-export-all */ public function exportAll(array $options = ['exclude' => self::REQ, 'destination' => self::REQ, 'format' => self::REQ]) { // Filter out any non-content entity types. /** @var \Drupal\Core\Entity\EntityTypeInterface[] $definitions */ $definitions = $this->entityTypeManager->getDefinitions(); $definitions = array_filter($definitions, function (EntityTypeInterface $definition) { return is_a($definition->getClass(), ContentEntityInterface::class, TRUE); }); // Filter out entire entity types specified in the exclude option. $excluded = $this->optionsProvider->getExcluded($options); if ($excluded) { $definitions = array_filter($definitions, function (EntityTypeInterface $definition) use ($excluded) { return !in_array($definition->id(), $excluded['entity_type']); }); } $destination = $this->getExportDestination($options); list($format, $context) = $this->optionsProvider->getFormatAndContext($options); $this->output()->writeln(dt("Exporting...")); foreach ($definitions as $entity_type_id => $definition) { // Filter out bundles specified in the exclude option. $bundles = NULL; if (!empty($excluded['bundle'][$entity_type_id])) { $all_bundles = array_keys($this->bundleInfo->getBundleInfo($entity_type_id)); $bundles = array_diff($all_bundles, $excluded['bundle'][$entity_type_id]); } // @todo Make batch size configurable. $entities = $this->bulkLoader->byEntityType(50, $entity_type_id, $bundles); $destination->saveMultiple($this->exporter->exportMultiple($entities, $format, $context)); $this->output()->writeln(' - ' . (string) $definition->getLabel()); } $this->output()->writeln(dt("Completed")); } /** * Imports content from a folder. * * @param array $options An associative array of options whose values come from cli, aliases, config, etc. * @option source * Folder(s) to import from in a comma-separated list; you can also use the environment variable CONTENTSERIALIZE_IMPORT_SOURCE; defaults to the current directory * @usage drush csi --source=/tmp/import * Import all content in /tmp/import. * * @command contentserialize:import * @aliases csi,contentserialize-import * * @throws \Exception * On import errors. */ public function import(array $options = ['source' => self::REQ]) { $sources = $this->getImportSources($options); // Ensure the same entity isn't returned twice and that earlier entities // take priority over later ones. $merged = Traversables::uniqueByKey(Traversables::merge(...$sources)); $result = $this->importer->import($merged); if ($result->getFailures()) { throw new \Exception(dt("There were errors on import.")); } else { $this->io()->writeln(dt("Import completed successfully.")); } } /** * Try to load the specified content entity. * * @param string $entity_type_id * The entity type ID. * @param int|string $entity_id * The entity ID. * * @return \Drupal\Core\Entity\ContentEntityInterface|false * The loaded content entity, or FALSE on failure. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * If the entity type isn't valid. * @throws \Exception * On error */ protected function loadContentEntity($entity_type_id, $entity_id) { $storage = $this->entityTypeManager->getStorage($entity_type_id); $class = $storage->getEntityType()->getClass(); if (!is_subclass_of($class, ContentEntityInterface::class, TRUE)) { throw new \Exception(dt("Content serialization can only export content entities.")); } /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $entity = $storage->load($entity_id); if (!$entity) { throw new \Exception(dt("Couldn't load @entity_type with ID @entity_id", ['@entity_type' => $entity_type_id, '@entity_id' => $entity_id])); } return $entity; } /** * Get the export destination. * * @param array $options * The command's options array; the 'format' key will be used if present. * * @return \Drupal\contentserialize\Destination\DestinationInterface * * @see \Drupal\contentserialize\Commands\ContentSerializeOptionsProvider::getExportFolder() */ protected function getExportDestination($options) { return new FileDestination($this->optionsProvider->getExportFolder($options)); } /** * Get the import sources. * * @param array $options * The command's options array; the 'source' key will be used if present. * * @return \Drupal\contentserialize\Source\SourceInterface[] * An array of import sources in priority order (an entity will only be * imported the first time it's encountered). * * @see \Drupal\contentserialize\Commands\ContentSerializeOptionsProvider::getImportFolders() */ protected function getImportSources(array $options) { return array_map(function ($source) { return new FileSource($source); }, $this->optionsProvider->getImportFolders($options)); } }