You are here

ProcessedContentLoader.php in YAML Content 8.2

File

src/ContentLoader/ProcessedContentLoader.php
View source
<?php

namespace Drupal\yaml_content\ContentLoader;

use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Serialization\SerializationInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\yaml_content\ImportProcessorInterface;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * A ContentLoader supporting processing of content through plugins.
 *
 * @todo Extend this class as an EntityLoader to support later support options.
 */
class ProcessedContentLoader extends ContentLoaderBase {

  /**
   * A plugin manager to load processor plugins from.
   *
   * @var \Drupal\yaml_content\ContentProcessorPluginManager
   */
  protected $processorPluginManager;

  /**
   * ProcessedContentLoader constructor.
   *
   * The parent constructor is overridden to pull in the content processor
   * plugin manager.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager service to manage dynamic entity operations.
   * @param \Drupal\Component\Serialization\SerializationInterface $parser
   *   The content parser to use for loading content from content files.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
   *   An event dispatcher service to publish events throughout the process.
   * @param \Drupal\Component\Plugin\PluginManagerInterface $processorPluginManager
   *   The plugin manager to use for loading content processor plugins.
   */
  public function __construct(EntityTypeManagerInterface $entityTypeManager, SerializationInterface $parser, EventDispatcherInterface $dispatcher, PluginManagerInterface $processorPluginManager) {
    parent::__construct($entityTypeManager, $parser, $dispatcher);
    $this->processorPluginManager = $processorPluginManager;
  }

  /**
   * {@inheritdoc}
   */
  public function loadContent($save = TRUE) {
    $content_context = [];
    $this
      ->preprocessData($this->parsedContent, $content_context);
    $loaded = parent::loadContent($save);
    $this
      ->postprocessData($this->parsedContent, $loaded, $content_context);
    return $loaded;
  }

  /**
   * {@inheritdoc}
   */
  public function importEntity(array $content_data) {

    // Preprocess the content data.
    $entity_context = [];
    $this
      ->preprocessData($content_data, $entity_context);
    $entity = parent::importEntity($content_data);

    // Postprocess loaded entity object.
    $this
      ->postprocessData($content_data, $entity, $entity_context);
    return $entity;
  }

  /**
   * {@inheritdoc}
   */
  public function importEntityField(array $field_data, EntityInterface $entity, FieldDefinitionInterface $field_definition) {

    // Preprocess field data.
    $field_context['entity'] = $entity;
    $field_context['field'] = $field_definition;
    $this
      ->preprocessData($field_data, $field_context);
    parent::importEntityField($field_data, $entity, $field_definition);

    // Postprocess loaded field data.
    $this
      ->postprocessData($field_data, $entity, $field_context);
  }

  /**
   * {@inheritdoc}
   */
  public function importFieldItem($field_item_data, EntityInterface $entity, FieldDefinitionInterface $field_definition) {

    // Preprocess field data.
    $field_context['entity'] = $entity;
    $field_context['field'] = $field_definition;
    if (is_array($field_item_data)) {
      $this
        ->preprocessData($field_item_data, $field_context);
    }
    $item_value = parent::importFieldItem($field_item_data, $entity, $field_definition);

    // Postprocess loaded field data.
    if (is_array($field_item_data)) {
      $this
        ->postprocessData($field_item_data, $item_value, $field_context);
    }
    return $item_value;
  }

  /**
   * Load the processor plugin for use on the import content.
   *
   * Load the processor plugin and configure any relevant context available in
   * the provided `$context` parameter.
   *
   * @param string $processor_id
   *   The machine name identifier of the processor to be loaded.
   * @param array $context
   *   Contextual data to configure the loaded processor plugin.
   *
   * @return \Drupal\yaml_content\ImportProcessorInterface
   *   The loaded import processor object.
   *
   * @throws \Exception
   *
   * @todo Handle PluginNotFoundException.
   */
  public function loadProcessor($processor_id, array &$context) {
    $processor_definition = $this->processorPluginManager
      ->getDefinition($processor_id);

    // @todo Implement exception class for invalid processors.
    if (!$processor_definition['import']) {
      throw new \Exception(sprintf('The %s processor does not support import operations.', $processor_id));
    }

    // Instantiate the processor plugin with default config values.
    $processor = $this->processorPluginManager
      ->createInstance($processor_id, $context);

    // Set and validate context values.
    if (isset($processor_definition['context'])) {
      foreach ($processor_definition['context'] as $name => $definition) {
        if (isset($context[$name])) {

          // @todo Validate context types and values.
          $processor
            ->setContextValue($name, $context[$name]);
        }
        else {

          // Handle missing required contexts.
          if ($definition
            ->isRequired()) {

            // @todo Handle this exception.
          }
        }
      }
    }
    return $processor;
  }

  /**
   * Evaluate the current import data array and run any preprocessing needed.
   *
   * Any data keys starting with '#' indicate preprocessing instructions that
   * should be executed on the data prior to import. The data array is altered
   * directly and fully prepared for import.
   *
   * @param array $import_data
   *   The current content data being evaluated for import. This array is
   *   altered directly and returned without the processing key.
   * @param array $context
   *   Contextual data passed by reference to preprocessing plugins.
   *
   * @throws Exception
   */
  public function preprocessData(array &$import_data, array &$context) {

    // Abort if there are no preprocessing instructions.
    if (!isset($import_data['#preprocess'])) {
      return;
    }
    if (!is_array($import_data['#preprocess'])) {
      throw new Exception('Preprocessing instructions must be provided as an array.');
    }

    // Execute all processing actions.
    foreach ($import_data['#preprocess'] as $key => $data) {

      // Execute preprocess actions.
      if (isset($data['#plugin'])) {

        // Expose preprocess configuration into context for the plugin.
        $processor_context = array_merge($context, $data);

        // Load the plugin.
        $processor = $this
          ->loadProcessor($data['#plugin'], $processor_context);
        assert($processor instanceof ImportProcessorInterface, 'Preprocess plugin [' . $data['#plugin'] . '] failed to load a valid ImportProcessor plugin.');

        // Execute plugin on $import_data.
        $processor
          ->preprocess($import_data);
      }
      else {
        throw new Exception('Preprocessing instructions require a defined "#plugin" identifier.');
      }
    }

    // Remove executed preprocess data.
    unset($import_data['#preprocess']);
  }

  /**
   * Recursively navigate the content hierarchy to apply processors.
   *
   * @param mixed $import_data
   *   Array or string data extracted from the loaded content file.
   */
  public function recursivelyPreprocessData(&$import_data) {
    if (!is_array($import_data)) {
      return;
    }
    foreach ($import_data as $key => &$value) {

      // Preprocess each array value recursively.
      if (is_array($value)) {
        $context = [];

        // Preprocess this branch.
        $this
          ->preprocessData($value, $context);

        // Recurse further for additional preprocessing.
        if (is_array($value)) {
          array_walk($value, [
            $this,
            'recursivelyPreprocessData',
          ]);
        }
      }
    }
  }

  /**
   * Evaluate the current import data array and run any preprocessing needed.
   *
   * Any data keys starting with '#' indicate preprocessing instructions that
   * should be executed on the data prior to import. The data array is altered
   * directly and fully prepared for import.
   *
   * @param array $import_data
   *   The current content data being evaluated for import. This array is
   *   altered directly and returned without the processing key.
   * @param mixed $loaded_content
   *   The loaded content object generated from the given import data.
   * @param array $context
   *   Contextual data passed by reference to preprocessing plugins.
   *
   * @throws Exception
   */
  public function postprocessData(array &$import_data, &$loaded_content, array &$context) {

    // Abort if there are no postprocessing instructions.
    if (!isset($import_data['#postprocess'])) {
      return;
    }
    if (!is_array($import_data['#postprocess'])) {
      throw new Exception('Postprocessing instructions must be provided as an array.');
    }

    // Execute all processing actions.
    foreach ($import_data['#postprocess'] as $key => $data) {

      // @todo Execute postprocess actions.
      if (isset($data['#plugin'])) {

        // Load the plugin.
        $processor = $this
          ->loadProcessor($data['#plugin'], $context);
        assert($processor instanceof ImportProcessorInterface, 'Postprocess plugin [' . $data['#plugin'] . '] failed to load a valid ImportProcessor plugin.');

        // @todo Provide required context as defined by plugin definition.
        // @todo Execute plugin on $loaded_content.
      }
      else {
        throw new Exception('Postprocessing instructions require a defined "#plugin" identifier.');
      }
    }
  }

}

Classes

Namesort descending Description
ProcessedContentLoader A ContentLoader supporting processing of content through plugins.