|
@@ -4,10 +4,13 @@ namespace Drupal\reinstall;
|
|
|
|
|
|
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
|
|
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
|
|
+use Drupal\Core\Entity\EntityStorageInterface;
|
|
|
use Drupal\Core\Entity\EntityTypeBundleInfo;
|
|
|
+use Drupal\Core\Entity\EntityTypeInterface;
|
|
|
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
|
|
use Drupal\Core\Session\AccountSwitcherInterface;
|
|
|
use Drupal\Core\Session\UserSession;
|
|
|
+use Psr\Log\LoggerInterface;
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
|
|
use Symfony\Component\Serializer\Serializer;
|
|
|
use Symfony\Component\Yaml\Yaml;
|
|
@@ -19,6 +22,14 @@ use Symfony\Component\Yaml\Yaml;
|
|
|
*/
|
|
|
class Dumper {
|
|
|
|
|
|
+
|
|
|
+ * Should probably be a service parameter in the future.
|
|
|
+ */
|
|
|
+ const BATCH_SIZE = 50;
|
|
|
+
|
|
|
+
|
|
|
+ * The interface used to ensure the dump request is for content entities.
|
|
|
+ */
|
|
|
const CONTENT_INTERFACE = 'Drupal\Core\Entity\ContentEntityInterface';
|
|
|
|
|
|
|
|
@@ -69,6 +80,13 @@ class Dumper {
|
|
|
*/
|
|
|
protected $importPath = self::IMPORT_PATH;
|
|
|
|
|
|
+
|
|
|
+ * The reinstall logger channel service.
|
|
|
+ *
|
|
|
+ * @var \Psr\Log\LoggerInterface
|
|
|
+ */
|
|
|
+ protected $logger;
|
|
|
+
|
|
|
|
|
|
* The app.root parameter service.
|
|
|
*
|
|
@@ -100,6 +118,8 @@ class Dumper {
|
|
|
* The event_dispatcher service.
|
|
|
* @param string $path
|
|
|
* The import path.
|
|
|
+ * @param \Psr\Log\LoggerInterface $logger
|
|
|
+ * The reinstall logger channel service.
|
|
|
*/
|
|
|
public function __construct(
|
|
|
AccountSwitcherInterface $accountSwitcher,
|
|
@@ -108,11 +128,13 @@ class Dumper {
|
|
|
string $root,
|
|
|
Serializer $serializer,
|
|
|
EventDispatcherInterface $eventDispatcher,
|
|
|
- string $path
|
|
|
+ string $path,
|
|
|
+ LoggerInterface $logger
|
|
|
) {
|
|
|
$this->accountSwitcher = $accountSwitcher;
|
|
|
$this->entityTypeBundleInfo = $bundleInfo;
|
|
|
$this->entityTypeManager = $entityTypeManager;
|
|
|
+ $this->logger = $logger;
|
|
|
$this->root = $root;
|
|
|
$this->serializer = $serializer;
|
|
|
$this->eventDispatcher = $eventDispatcher;
|
|
@@ -148,6 +170,95 @@ class Dumper {
|
|
|
return $entityTypes;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ * Dump the entities in the selected bundle of the entity type.
|
|
|
+ *
|
|
|
+ * @param string $typeName
|
|
|
+ * The name of the entity type from which to dump a bundle.
|
|
|
+ * @param \Drupal\Core\Entity\EntityTypeInterface $entityType
|
|
|
+ * The entity type instance.
|
|
|
+ * @param string $bundle
|
|
|
+ * The name of the bundle to dump. Assumed to be valid.
|
|
|
+ *
|
|
|
+ * @see \Drupal\reinstall\Dumper::validateBundles()
|
|
|
+ */
|
|
|
+ protected function dumpEntitiesBundle($typeName, EntityTypeInterface $entityType, string $bundle) {
|
|
|
+ $t0 = microtime(TRUE);
|
|
|
+ $storage = $this->entityTypeManager->getStorage($typeName);
|
|
|
+ $bundleKey = $this->entityTypeManager->getDefinition($typeName)->getKey('bundle');
|
|
|
+ $countQuery = $storage->getQuery();
|
|
|
+ if ($bundleKey) {
|
|
|
+ $countQuery = $countQuery->condition($bundleKey, $bundle);
|
|
|
+ }
|
|
|
+ $count = $countQuery
|
|
|
+ ->count()
|
|
|
+ ->execute();
|
|
|
+
|
|
|
+ $query = $storage->getQuery();
|
|
|
+ if ($bundleKey) {
|
|
|
+ $query = $query->condition($bundleKey, $bundle);
|
|
|
+ }
|
|
|
+ $ids = $query->execute();
|
|
|
+ $chunks = array_chunk($ids, static::BATCH_SIZE);
|
|
|
+
|
|
|
+ $path = $this->prepareDestination($typeName, $bundle);
|
|
|
+ $fp = fopen($path, "w");
|
|
|
+
|
|
|
+ foreach ($chunks as $chunk) {
|
|
|
+ $this->dumpEntitiesChunk($fp, $typeName, $bundle, $storage, $chunk);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (empty($chunks)) {
|
|
|
+ fwrite($fp, '{ }');
|
|
|
+ }
|
|
|
+
|
|
|
+ fclose($fp);
|
|
|
+ $t1 = microtime(TRUE);
|
|
|
+
|
|
|
+ $this->logger->info('Dumped @count entities for @type/@bundle in @sec seconds.', [
|
|
|
+ '@count' => $count,
|
|
|
+ '@type' => $typeName,
|
|
|
+ '@bundle' => $bundle,
|
|
|
+ '@sec' => sprintf('%.1f', $t1 - $t0),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * Dump a chunk of entities from a common bundle in an existing opened file.
|
|
|
+ *
|
|
|
+ * @param resource $fp
|
|
|
+ * A file pointer to which to write.
|
|
|
+ * @param string $typeName
|
|
|
+ * The name of the entity type from which to dump a chunk of entities.
|
|
|
+ * @param string $bundle
|
|
|
+ * The bundle name.
|
|
|
+ * @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
|
|
+ * The entity storage for the entity type.
|
|
|
+ * @param array $chunk
|
|
|
+ * An array of entities to dump.
|
|
|
+ */
|
|
|
+ protected function dumpEntitiesChunk(
|
|
|
+ $fp,
|
|
|
+ string $typeName,
|
|
|
+ string $bundle,
|
|
|
+ EntityStorageInterface $storage,
|
|
|
+ array $chunk
|
|
|
+ ) {
|
|
|
+ $entities = $storage->loadMultiple($chunk);
|
|
|
+
|
|
|
+
|
|
|
+ $eventPre = new DumperEvent($storage, $bundle, $entities);
|
|
|
+ $this->eventDispatcher->dispatch(ReinstallEvents::PRE_DUMP, $eventPre);
|
|
|
+
|
|
|
+ $this->dumpEntities($fp, $typeName, $bundle, $entities);
|
|
|
+
|
|
|
+
|
|
|
+ $eventPost = new DumperEvent($storage, $bundle, $entities);
|
|
|
+ $this->eventDispatcher->dispatch(ReinstallEvents::POST_DUMP, $eventPost);
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
* Load entities for a given entity bundle.
|
|
|
*
|
|
@@ -177,40 +288,32 @@ class Dumper {
|
|
|
|
|
|
* Dump entities to YAML files.
|
|
|
*
|
|
|
- * @param string $requestedTypeName
|
|
|
- * Optional. The name of the entity type for which to export entities. If
|
|
|
- * absent, all entities in all types are dumped.
|
|
|
+ * @param string $typeName
|
|
|
+ * The name of the entity type for which to export entities.
|
|
|
+ * @param array $bundles
|
|
|
+ * Optional. An array of bundles to export. Only used if an entity type is
|
|
|
+ * specified, since $requestedTypeName is itself optional.
|
|
|
*
|
|
|
* @see https:
|
|
|
*/
|
|
|
- public function dump($requestedTypeName = NULL) {
|
|
|
+ public function dump($typeName, array $bundles = []) {
|
|
|
$this->accountSwitcher->switchTo(new UserSession(['uid' => 1]));
|
|
|
|
|
|
- $entityTypes = $this->contentEntityTypes($requestedTypeName);
|
|
|
- foreach ($entityTypes as $entityTypeName => $entityType) {
|
|
|
- $storage = $this->entityTypeManager->getStorage($entityTypeName);
|
|
|
- $entities = $entityType->hasKey('bundle')
|
|
|
- ? $this->loadMultiBundleEntities($entityType, $storage)
|
|
|
- : [$entityTypeName => $storage->loadMultiple()];
|
|
|
-
|
|
|
- foreach ($entities as $bundleName => $bundleEntities) {
|
|
|
-
|
|
|
- $eventPre = new DumperEvent($storage, $bundleName, $bundleEntities);
|
|
|
- $this->eventDispatcher->dispatch(ReinstallEvents::PRE_DUMP, $eventPre);
|
|
|
+ $entityType = current($this->contentEntityTypes($typeName));
|
|
|
+ $validBundles = $this->validateBundles($typeName, $bundles);
|
|
|
|
|
|
- $this->dumpEntities($entityTypeName, $bundleName, $bundleEntities);
|
|
|
-
|
|
|
-
|
|
|
- $eventPost = new DumperEvent($storage, $bundleName, $bundleEntities);
|
|
|
- $this->eventDispatcher->dispatch(ReinstallEvents::POST_DUMP, $eventPost);
|
|
|
- }
|
|
|
+ foreach ($validBundles as $bundle) {
|
|
|
+ $this->dumpEntitiesBundle($typeName, $entityType, $bundle);
|
|
|
}
|
|
|
+
|
|
|
$this->accountSwitcher->switchBack();
|
|
|
}
|
|
|
|
|
|
|
|
|
* Generate import YAML for entities.
|
|
|
*
|
|
|
+ * @param resource $fp
|
|
|
+ * The file into which to write.
|
|
|
* @param string $entityTypeName
|
|
|
* The entity type.
|
|
|
* @param string $bundleName
|
|
@@ -218,10 +321,10 @@ class Dumper {
|
|
|
* @param array $entities
|
|
|
* The entities.
|
|
|
*/
|
|
|
- public function dumpEntities(string $entityTypeName, string $bundleName, array $entities) {
|
|
|
+ public function dumpEntities($fp, string $entityTypeName, string $bundleName, array $entities) {
|
|
|
$array = $this->toArray($entities);
|
|
|
- $path = $this->prepareDestination($entityTypeName, $bundleName);
|
|
|
- file_put_contents($path, Yaml::dump($array, static::INLINE_DEPTH, 2));
|
|
|
+ fwrite($fp, Yaml::dump($array, static::INLINE_DEPTH, 2));
|
|
|
+ fflush($fp);
|
|
|
}
|
|
|
|
|
|
|
|
@@ -250,6 +353,7 @@ class Dumper {
|
|
|
* Store the absolute path to the import directory.
|
|
|
*
|
|
|
* @param string $path
|
|
|
+ *
|
|
|
* The Drupal-root-relative import path.
|
|
|
*
|
|
|
* @throws \InvalidArgumentException
|
|
@@ -283,4 +387,39 @@ class Dumper {
|
|
|
return $hash;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ * Deduplicate the bundles list and remove invalid bundle names.
|
|
|
+ *
|
|
|
+ * @param string $entityTypeName
|
|
|
+ * The name of the entity type for which to validate bundle names.
|
|
|
+ * @param array $bundles
|
|
|
+ * Bundle names to be validated.
|
|
|
+ *
|
|
|
+ * @return array
|
|
|
+ * Valid bundles to dump.
|
|
|
+ */
|
|
|
+ protected function validateBundles(string $entityTypeName, array $bundles) {
|
|
|
+ sort($bundles);
|
|
|
+ $bundleInfo = $this->entityTypeBundleInfo->getBundleInfo($entityTypeName);
|
|
|
+
|
|
|
+
|
|
|
+ $flippedBundles = array_flip($bundles);
|
|
|
+
|
|
|
+ $validFlippedBundles = array_intersect_key($flippedBundles, $bundleInfo);
|
|
|
+ $uniqueValidBundles = array_flip($validFlippedBundles);
|
|
|
+
|
|
|
+ sort($uniqueValidBundles);
|
|
|
+ if ($bundles !== $uniqueValidBundles) {
|
|
|
+ throw new \InvalidArgumentException(
|
|
|
+ "\nRequested bundles: " . implode(', ', $bundles)
|
|
|
+ . "\nValid bundles: " . implode(', ', $uniqueValidBundles) . "\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($uniqueValidBundles)) {
|
|
|
+ $uniqueValidBundles = array_keys($bundleInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $uniqueValidBundles;
|
|
|
+ }
|
|
|
+
|
|
|
}
|