Skip to content
Snippets Groups Projects

Add the snippet process plugin

4 unresolved threads
Files
11
+ 191
0
<?php
declare(strict_types=1);
namespace Drupal\migrate_plus\Plugin\migrate\process;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigratePluginManagerInterface;
use Drupal\migrate\Plugin\MigrateProcessInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
/**
* Applies process pipelines described in YAML snippets.
*
* Available configuration keys:
* - module: the module providing the snippet
* - path: path of the YAML file, relative to module/migrations/process and
* without the .yml extension.
*
* Examples:
*
* Suppose the file my_module/migrations/process/clean_whitespace.yml contains
    • @benjifisher I think it would be worth documenting that one is not allowed to use source anywhere within a snippet. That's different from a typical process pipeline where there are cases that one might want to redefine source, particularly following a skip_on_empty. (Or maybe if they know that one weird trick!)

      • This does NOT WORK:

        - plugin: default_value
          default_value: default value
          source: some_field

        but this does:

        - plugin: get
          source: some_field
        - plugin: default_value
          default_value: default value

        I added test coverage to prove that, and I added those examples to the doc block.

      • That's amazing! That hadn't occurred to me that you can explictly call get in this case. Very cool solution and a nice doc!

      • Please register or sign in to reply
Please register or sign in to reply
* the following:
*
* @code
* -
* plugin: callback
* callable: htmlentities
* -
* plugin: str_replace
* search:
* - '&#160;'
* - '&nbsp;'
* replace: ' '
* -
* plugin: str_replace
* regex: true
* search: '@\s+@'
* replace: ' '
* -
* plugin: callback
* callable: trim
* @endcode
*
* Then that process pipeline can be used as follows:
*
* @code
* process:
* field_formatted_text/value:
* plugin: snippet
* module: my_module
* path: clean_whitespace
* source: html_string
* field_formatted_text/format:
* plugin: default_value
* default_value: full_html
* @endcode
*
* Normally, any process plugin can specify the source key, which implicitly
* adds the get process plugin to the pipeline. This does NOT WORK inside a
* snippet:
*
* @code
* # The source will be ignored. The source of the snippet plugin is always
* # used.
* - plugin: default_value
* default_value: default value
* source: some_field
* @endcode
*
* Instead, explicitly add the get plugin to the pipeline:
*
* @code
* - plugin: get
* source: some_field
* - plugin: default_value
* default_value: default value
* @endcode
*
* @MigrateProcessPlugin(
* id = "snippet"
* )
*/
class Snippet extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* The process pipeline.
*
* @var Drupal\migrate\Plugin\MigrateProcessInterface[]
*/
protected $pipeline = [];
/**
* Constructs a snippet process plugin.
*
* @param array $configuration
* The plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param array $plugin_definition
* The plugin definition.
* @param \Drupal\migrate\Plugin\MigrationInterface|null $migration
* The migration entity.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* The module handler.
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $plugin_manager
* The process plugin manager.
*/
public function __construct(
array $configuration,
$plugin_id,
array $plugin_definition,
$migration,
protected ModuleHandlerInterface $moduleHandler,
MigratePluginManagerInterface $plugin_manager,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$module = $this->configuration['module'] ?? NULL;
if (empty($module)) {
throw new \InvalidArgumentException("The 'module' parameter is required.");
Please register or sign in to reply
}
if (!$this->moduleHandler->moduleExists($module)) {
throw new \InvalidArgumentException("The '$module' module is not installed.");
}
if (empty($this->configuration['path'] ?? NULL)) {
throw new \InvalidArgumentException("The 'path' parameter is required.");
}
$snippet_file = implode('/', [
$this->moduleHandler->getModule($module)->getPath(),
'migrations/process',
$this->configuration['path'],
]) . '.yml';
try {
$steps = Yaml::parseFile($snippet_file);
}
catch (ParseException $e) {
throw new \InvalidArgumentException($e->getMessage());
}
foreach ($steps as $step_configuration) {
$step_id = $step_configuration['plugin'];
unset($step_configuration['plugin']);
$this->pipeline[] = $plugin_manager->createInstance($step_id, $step_configuration, $migration);
}
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('module_handler'),
$container->get('plugin.manager.migrate.process'),
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
// Starting with Drupal 10.3, the interface includes isPipelineStopped() and
// reset().
$can_reset = method_exists(MigrateProcessInterface::class, 'reset');
/** @var MigrateProcessInterface $step */
foreach ($this->pipeline as $step) {
if ($can_reset) {
$step->reset();
}
$value = $step->transform($value, $migrate_executable, $row, $destination_property);
if ($can_reset && $step->isPipelineStopped()) {
$this->stopPipeline();
return $value;
}
}
return $value;
}
}
Loading