In order to have dynamic URLs but keep all configuration in the YML file rather that scattered in alter hooks, we can use a custom source plugin to replace placeholders in the URL, and use standard Migrate processors to provide the value for the URL.
For example to import a list of persons that have their birthday today:
source:
plugin: processable_url
data_fetcher_plugin: http
data_parser_plugin: json
urls:
- 'https://mysource.com/api/persons?birthdate={{ today }}'
url_processors:
- key: today
process:
plugin: default_value
default_value: 'today'
- key: today
process:
plugin: strtotime
- key: today
process:
plugin: format_date
from_format: 'U'
to_format: 'Y-m-d'
ProcessableUrl
migrate source plugin:
<?php
declare(strict_types = 1);
namespace Drupal\mymodule\Plugin\migrate\source;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\Plugin\MigratePluginManagerInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_plus\DataParserPluginInterface;
use Drupal\migrate_plus\Plugin\migrate\source\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a migration source for a URL with processable placeholders.
*
* Replaces Jinja style {{ placeholders }}. Useful for REST APIs that require
* dynamic values in the URL.
*
* Make sure to start with a `default_value` processor, or create your own
* processor plugin that provides the required value.
*
* @MigrateSource(
* id = "processable_url"
* )
*/
class ProcessableUrl extends Url implements ContainerFactoryPluginInterface {
/**
* The URL processors.
*
* @var array
*/
protected array $urlProcessors = [];
/**
* Constructs a new ProcessableUrl migrate source plugin.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, protected MigratePluginManagerInterface $migratePluginManager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->urlProcessors = $configuration['url_processors'] ?? [];
}
/**
* {@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('plugin.manager.migrate.process'),
);
}
/**
* {@inheritdoc}
*/
public function getDataParserPlugin(): DataParserPluginInterface {
if (!isset($this->dataParserPlugin)) {
$configuration = ['urls' => $this->processUrls()] + $this->configuration;
$this->dataParserPlugin = \Drupal::service('plugin.manager.migrate_plus.data_parser')->createInstance($this->configuration['data_parser_plugin'], $configuration);
}
return $this->dataParserPlugin;
}
/**
* Processes the source URLs.
*
* @return array
* The processed URLs.
*/
protected function processUrls(): array {
$placeholders = $this->processPlaceholders();
return array_map(function (string $url) use ($placeholders): string {
foreach ($placeholders as $placeholder => $value) {
$url = preg_replace('/\{\{\s*' . $placeholder . '\s*\}\}/', $value, $url);
}
return $url;
}, $this->sourceUrls);
}
/**
* Processes the placeholders.
*
* @return array
* The processed placeholders.
*/
protected function processPlaceholders(): array {
// @todo Doesn't support row based processors.
$executable = new MigrateExecutable($this->migration);
$row = new Row();
$placeholders = [];
foreach ($this->urlProcessors as $definition) {
$key = $definition['key'];
$plugin_definition = $definition['process'];
$plugin = $this->migratePluginManager->createInstance($plugin_definition['plugin'], $plugin_definition);
$placeholders[$key] = $plugin->transform($placeholders[$key] ?? NULL, $executable, $row, $key);
}
return $placeholders;
}
}
And a simple StringToTime
processor:
<?php
declare(strict_types = 1);
namespace Drupal\mymodule\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Converts a string representation of a date to a Unix timestamp.
*
* @MigrateProcessPlugin(
* id = "strtotime"
* )
*/
class StringToTime extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
return strtotime($value);
}
}