You are here

class CreateStubs in Acquia Content Hub 8.2

Class CreateStubs.

Creates stub content entities from field sample values for required fields in order to setup entities with circular dependencies on each other. Once these stubs are all created they'll be saved over with real values and any stub which are inadvertently created during this process will be deleted as the final step of import.

@package Drupal\acquia_contenthub\EventSubscriber\ImportFailure

Hierarchy

  • class \Drupal\acquia_contenthub\EventSubscriber\ImportFailure\CreateStubs implements \Symfony\Component\EventDispatcher\EventSubscriberInterface

Expanded class hierarchy of CreateStubs

1 string reference to 'CreateStubs'
acquia_contenthub.services.yml in ./acquia_contenthub.services.yml
acquia_contenthub.services.yml
1 service uses CreateStubs
acquia_contenthub.import_failure.create_stubs in ./acquia_contenthub.services.yml
Drupal\acquia_contenthub\EventSubscriber\ImportFailure\CreateStubs

File

src/EventSubscriber/ImportFailure/CreateStubs.php, line 34

Namespace

Drupal\acquia_contenthub\EventSubscriber\ImportFailure
View source
class CreateStubs implements EventSubscriberInterface {

  /**
   * The processed dependency count to prevent infinite loops.
   *
   * @var int
   */
  protected static $count = 0;

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[AcquiaContentHubEvents::IMPORT_FAILURE][] = [
      'onImportFailure',
      100,
    ];
    return $events;
  }

  /**
   * Generate stub entities for all remaining content entities and reimports.
   *
   * @param \Drupal\acquia_contenthub\Event\FailedImportEvent $event
   *   The failure event.
   * @param string $event_name
   *   The event name.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
   *   The event dispatcher.
   */
  public function onImportFailure(FailedImportEvent $event, string $event_name, EventDispatcherInterface $dispatcher) {
    if (static::$count === $event
      ->getCount()) {
      $exception = new \Exception("Potential infinite recursion call interrupted in CreateStubs event subscriber.");
      $event
        ->setException($exception);
      return;
    }
    static::$count = $event
      ->getCount();
    $unprocessed = array_diff(array_keys($event
      ->getCdf()
      ->getEntities()), array_keys($event
      ->getStack()
      ->getDependencies()));
    if (!$unprocessed) {
      $event
        ->stopPropagation();
      return;
    }
    if (!$event
      ->getSerializer()
      ->getTracker()
      ->isTracking()) {
      $event
        ->getSerializer()
        ->getTracker()
        ->setStack($event
        ->getStack());
    }
    $this
      ->handleEntityProcessing($unprocessed, $event, $dispatcher);
    static::$count = 0;
  }

  /**
   * Process entities and handle exceptions.
   *
   * @param array $unprocessed
   *   The unprocessed array.
   * @param \Drupal\acquia_contenthub\Event\FailedImportEvent $event
   *   The event object.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
   *   The dispatcher.
   */
  protected function handleEntityProcessing(array $unprocessed, FailedImportEvent $event, EventDispatcherInterface $dispatcher) {
    try {
      $cdfs = $this
        ->processConfigEntities($unprocessed, $event) + $this
        ->processContentEntities($unprocessed, $event, $dispatcher);
      $document = new CDFDocument(...$cdfs);
      $event
        ->getSerializer()
        ->unserializeEntities($document, $event
        ->getStack());
      $event
        ->stopPropagation();
    } catch (\Exception $e) {
      $event
        ->setException($e);
    }
  }

  /**
   * Process config entities.
   *
   * @param array $unprocessed
   *   The unprocessed array.
   * @param \Drupal\acquia_contenthub\Event\FailedImportEvent $event
   *   The event object.
   *
   * @return array
   *   The CDFs.
   */
  protected function processConfigEntities(array &$unprocessed, FailedImportEvent $event) : array {
    $cdfs = [];
    foreach ($unprocessed as $key => $uuid) {
      $cdf = $event
        ->getCdf()
        ->getCdfEntity($uuid);
      if ($cdf
        ->getType() !== 'drupal8_config_entity') {
        continue;
      }
      unset($unprocessed[$key]);
      $cdfs[] = $cdf;
    }
    return $cdfs;
  }

  /**
   * Process content entities.
   *
   * @param array $unprocessed
   *   The unprocessed array.
   * @param \Drupal\acquia_contenthub\Event\FailedImportEvent $event
   *   The event object.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
   *   The dispatcher.
   *
   * @return array
   *   The CDFs.
   *
   * @throws \Exception
   */
  protected function processContentEntities(array $unprocessed, FailedImportEvent $event, EventDispatcherInterface $dispatcher) : array {
    $cdfs = [];
    foreach ($unprocessed as $key => $uuid) {
      $cdf = $event
        ->getCdf()
        ->getCdfEntity($uuid);
      $stack = $event
        ->getStack();
      $load_event = new LoadLocalEntityEvent($cdf, $stack);
      $dispatcher
        ->dispatch(AcquiaContentHubEvents::LOAD_LOCAL_ENTITY, $load_event);
      $entity = $load_event
        ->getEntity() ?? $this
        ->createStub($cdf, $uuid, $stack, $dispatcher);
      $wrapper = new DependentEntityWrapper($entity, TRUE);
      $wrapper
        ->setRemoteUuid($uuid);
      $stack
        ->addDependency($wrapper);
      $cdfs[] = $cdf;
    }
    return $cdfs;
  }

  /**
   * Create stub entity.
   *
   * @param \Acquia\ContentHubClient\CDF\CDFObject $cdf
   *   The CDF object.
   * @param string $uuid
   *   The incoming UUID.
   * @param \Drupal\Depcalc\DependencyStack $stack
   *   The dependency stack.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
   *   The dispatcher.
   *
   * @return \Drupal\Core\Entity\EntityInterface
   *   The stub entity.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function createStub(CDFObject $cdf, string $uuid, DependencyStack $stack, EventDispatcherInterface $dispatcher) : EntityInterface {
    $entity_type = $cdf
      ->getAttribute('entity_type')
      ->getValue()[CDFObject::LANGUAGE_UNDETERMINED];
    $manager = $this
      ->getEntityTypeManager();
    $definition = $manager
      ->getDefinition($entity_type);
    $storage = $manager
      ->getStorage($entity_type);
    $keys = $definition
      ->getKeys();
    $values = $this
      ->getEntityValues($keys, $uuid, $entity_type, $cdf);

    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $entity = $storage
      ->create($values);
    $this
      ->generateRequiredSampleItems($entity);
    $pre_entity_save_event = new PreEntitySaveEvent($entity, $stack, $cdf);
    $dispatcher
      ->dispatch(AcquiaContentHubEvents::PRE_ENTITY_SAVE, $pre_entity_save_event);
    $entity = $pre_entity_save_event
      ->getEntity();

    // Added to avoid creating new revisions with stubbed data.
    // See \Drupal\content_moderation\Entity\Handler\ModerationHandler.
    if ($entity instanceof SynchronizableInterface) {
      $entity
        ->setSyncing(TRUE);
    }
    $entity
      ->save();
    return $entity;
  }

  /**
   * Creates array with the basic values for the stub based on incoming CDF.
   *
   * @param array $keys
   *   Entity keys.
   * @param string $uuid
   *   Incoming UUID.
   * @param string $entity_type
   *   The entity type.
   * @param \Acquia\ContentHubClient\CDF\CDFObject $cdf
   *   The CDF object.
   *
   * @return array
   *   The basic values for the stub entity.
   */
  protected function getEntityValues(array $keys, string $uuid, string $entity_type, CDFObject $cdf) : array {
    $values = [
      $keys['uuid'] => $uuid,
      // Get the language key from entity keys.
      $keys['langcode'] => $cdf
        ->getMetadata()['default_language'],
    ];
    if (!empty($keys['bundle'])) {
      $values[$keys['bundle']] = $cdf
        ->getAttribute('bundle')
        ->getValue()[CDFObject::LANGUAGE_UNDETERMINED];
    }
    if (!empty($keys['label'])) {
      $field_definitions = !empty($keys['bundle']) ? $this
        ->getEntityFieldManager()
        ->getFieldDefinitions($entity_type, $keys['bundle']) : NULL;
      $field_settings = isset($field_definitions) ? $field_definitions[$keys['label']]
        ->getItemDefinition()
        ->getSettings() : [];
      $size = $field_settings['max_length'] ?? 255;
      $values[$keys['label']] = mb_substr($cdf
        ->getAttribute('label')
        ->getValue()[CDFObject::LANGUAGE_UNDETERMINED], 0, $size);
    }
    return $values;
  }

  /**
   * Generate sample items for fields that require it.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The stub entity.
   */
  protected function generateRequiredSampleItems(ContentEntityInterface $entity) {

    // Fields that either don't require samples.
    $skip_fields = [
      $entity
        ->getEntityType()
        ->getKey('id'),
      $entity
        ->getEntityType()
        ->getKey('revision'),
      'revision_log',
    ];

    /** @var \Drupal\Core\Field\FieldItemListInterface $field */
    foreach ($entity as $field_name => $field) {
      if (in_array($field_name, $skip_fields)) {
        continue;
      }
      if ($field
        ->isEmpty() && $this
        ->fieldIsRequired($field)) {
        $field
          ->generateSampleItems();
      }
    }
  }

  /**
   * Determines if a field or field property is required for the entity.
   *
   * @param \Drupal\Core\Field\FieldItemListInterface $field
   *   The field to evaluate.
   *
   * @return bool
   *   Whether or not the field will require sample value generation.
   */
  protected function fieldIsRequired(FieldItemListInterface $field) : bool {
    if (!$field
      ->getFieldDefinition() instanceof BaseFieldDefinition) {
      return FALSE;
    }
    if ($field
      ->getFieldDefinition()
      ->isComputed()) {
      return FALSE;
    }
    if ($field
      ->getFieldDefinition()
      ->isRequired()) {
      return TRUE;
    }

    // Check each field property for its own requirement settings.
    foreach ($field
      ->getFieldDefinition()
      ->getFieldStorageDefinition()
      ->getPropertyDefinitions() as $propertyDefinition) {
      if ($propertyDefinition
        ->isRequired()) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Returns uncached entity type manager.
   *
   * @return \Drupal\Core\Entity\EntityTypeManagerInterface
   *   The entity type manager.
   */
  protected function getEntityTypeManager() : EntityTypeManagerInterface {
    return \Drupal::entityTypeManager();
  }

  /**
   * Returns uncached entity field manager.
   *
   * @return \Drupal\Core\Entity\EntityFieldManagerInterface
   *   The entity field manager.
   */
  protected function getEntityFieldManager() : EntityFieldManagerInterface {
    return \Drupal::service('entity_field.manager');
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CreateStubs::$count protected static property The processed dependency count to prevent infinite loops.
CreateStubs::createStub protected function Create stub entity.
CreateStubs::fieldIsRequired protected function Determines if a field or field property is required for the entity.
CreateStubs::generateRequiredSampleItems protected function Generate sample items for fields that require it.
CreateStubs::getEntityFieldManager protected function Returns uncached entity field manager.
CreateStubs::getEntityTypeManager protected function Returns uncached entity type manager.
CreateStubs::getEntityValues protected function Creates array with the basic values for the stub based on incoming CDF.
CreateStubs::getSubscribedEvents public static function Returns an array of event names this subscriber wants to listen to.
CreateStubs::handleEntityProcessing protected function Process entities and handle exceptions.
CreateStubs::onImportFailure public function Generate stub entities for all remaining content entities and reimports.
CreateStubs::processConfigEntities protected function Process config entities.
CreateStubs::processContentEntities protected function Process content entities.