Commit 6b14845b authored by catch's avatar catch

Issue #3004456 by mikelutz, phenaproxima, alexpott, heddn, maxocub, quietone:...

Issue #3004456 by mikelutz, phenaproxima, alexpott, heddn, maxocub, quietone: Add a weighting option to MigrateField plugin's definition and lower weight of deprecated 'date' plugin so it isn't used
parent 971c9460
......@@ -32,7 +32,8 @@ public function getProcess() {
$field_type = $row->getSourceProperty('type');
if ($this->fieldPluginManager->hasDefinition($field_type)) {
if (!isset($this->fieldPluginCache[$field_type])) {
$this->fieldPluginCache[$field_type] = $this->fieldPluginManager->createInstance($field_type, [], $this);
$plugin_id = $this->fieldPluginManager->getPluginIdFromFieldType($field_type, [], $this);
$this->fieldPluginCache[$field_type] = $this->fieldPluginManager->createInstance($plugin_id, [], $this);
}
$info = $row->getSource();
$this->fieldPluginCache[$field_type]->defineValueProcessPipeline($this, $field_name, $info);
......
......@@ -2,8 +2,6 @@
namespace Drupal\datetime\Plugin\migrate\field\d6;
@trigger_error('DateField is deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.x. Use \Drupal\datetime\Plugin\migrate\field\DateField instead.', E_USER_DEPRECATED);
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
......@@ -18,7 +16,8 @@
* },
* core = {6},
* source_module = "date",
* destination_module = "datetime"
* destination_module = "datetime",
* weight = 9999999,
* )
*
* @deprecated in Drupal 8.4.x, to be removed before Drupal 9.0.x. Use
......@@ -26,6 +25,14 @@
*/
class DateField extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
@trigger_error('DateField is deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.x. Use \Drupal\datetime\Plugin\migrate\field\DateField instead.', E_USER_DEPRECATED);
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
......
......@@ -10,7 +10,7 @@
* id = "link",
* core = {6},
* type_map = {
* "link_field" = "link"
* "link" = "link",
* },
* source_module = "link",
* destination_module = "link"
......
<?php
/**
* @file
* Post update functions for migrate_drupal.
*/
/**
* Force MigrateField plugin definitions to be cleared.
*
* @see https://www.drupal.org/node/3006470
*/
function drupal_migrate_post_update_clear_migrate_field_plugin_cache() {
// Empty post-update hook.
}
......@@ -71,4 +71,15 @@ public function __construct($values) {
*/
public $destination_module;
/**
* The weight of this plugin relative to other plugins.
*
* The weight of this plugin relative to other plugins servicing the same
* field type and core version. The lowest weighted applicable plugin will be
* used for each field.
*
* @var int
*/
public $weight = 0;
}
......@@ -28,7 +28,27 @@ class MigrateFieldPluginManager extends MigratePluginManager implements MigrateF
const DEFAULT_CORE_VERSION = 6;
/**
* {@inheritdoc}
* Get the plugin ID from the field type.
*
* This method determines which field plugin should be used for a given field
* type and Drupal core version, returning the lowest weighted plugin
* supporting the provided core version, and which matches the field type
* either by plugin ID, or in the type_map annotation keys.
*
* @param string $field_type
* The field type being migrated.
* @param array $configuration
* (optional) An array of configuration relevant to the plugin instance.
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* (optional) The current migration instance.
*
* @return string
* The ID of the plugin for the field type if available.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* If the plugin cannot be determined, such as if the field type is invalid.
*
* @see \Drupal\migrate_drupal\Annotation\MigrateField
*/
public function getPluginIdFromFieldType($field_type, array $configuration = [], MigrationInterface $migration = NULL) {
$core = static::DEFAULT_CORE_VERSION;
......@@ -67,4 +87,27 @@ public function processDefinition(&$definition, $plugin_id) {
}
}
/**
* {@inheritdoc}
*/
protected function findDefinitions() {
$definitions = parent::findDefinitions();
$this->sortDefinitions($definitions);
return $definitions;
}
/**
* Sorts a definitions array.
*
* This sorts the definitions array first by the weight column, and then by
* the plugin ID, ensuring a stable, deterministic, and testable ordering of
* plugins.
*
* @param array $definitions
* The definitions array to sort.
*/
protected function sortDefinitions(array &$definitions) {
array_multisort(array_column($definitions, 'weight'), SORT_ASC, SORT_NUMERIC, array_keys($definitions), SORT_ASC, SORT_NATURAL, $definitions);
}
}
......@@ -8,49 +8,132 @@
* Tests the field plugin manager.
*
* @group migrate_drupal
* @coversDefaultClass \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManager
*/
class MigrateFieldPluginManagerTest extends MigrateDrupalTestBase {
/**
* The field plugin manager.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface
*/
protected $pluginManager;
/**
* {@inheritdoc}
*/
public static $modules = [
'datetime',
'system',
'user',
'field',
'migrate_drupal',
'options',
'file',
'image',
'text',
'link',
'migrate_field_plugin_manager_test',
];
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'user', 'field', 'migrate_drupal', 'options', 'file', 'image', 'text', 'link', 'migrate_field_plugin_manager_test'];
public function setUp() {
parent::setUp();
$this->pluginManager = $this->container->get('plugin.manager.migrate.field');
}
/**
* Tests that the correct MigrateField plugins are used.
*
* @covers ::getPluginIdFromFieldType
*/
public function testPluginSelection() {
$plugin_manager = $this->container->get('plugin.manager.migrate.field');
try {
// If this test passes, getPluginIdFromFieldType will raise a
// PluginNotFoundException and we'll never reach fail().
$plugin_manager->getPluginIdFromFieldType('filefield', ['core' => 7]);
$this->fail('Expected Drupal\Component\Plugin\Exception\PluginNotFoundException.');
}
catch (PluginNotFoundException $e) {
$this->assertIdentical($e->getMessage(), "Plugin ID 'filefield' was not found.");
}
$this->assertSame('link', $this->pluginManager->getPluginIdFromFieldType('link', ['core' => 6]));
$this->assertSame('link_field', $this->pluginManager->getPluginIdFromFieldType('link_field', ['core' => 7]));
$this->assertSame('image', $this->pluginManager->getPluginIdFromFieldType('image', ['core' => 7]));
$this->assertSame('file', $this->pluginManager->getPluginIdFromFieldType('file', ['core' => 7]));
$this->assertSame('d6_file', $this->pluginManager->getPluginIdFromFieldType('file', ['core' => 6]));
$this->assertSame('d6_text', $this->pluginManager->getPluginIdFromFieldType('text', ['core' => 6]));
$this->assertSame('d7_text', $this->pluginManager->getPluginIdFromFieldType('text', ['core' => 7]));
$this->assertIdentical('link', $plugin_manager->getPluginIdFromFieldType('link', ['core' => 6]));
$this->assertIdentical('link_field', $plugin_manager->getPluginIdFromFieldType('link_field', ['core' => 7]));
$this->assertIdentical('image', $plugin_manager->getPluginIdFromFieldType('image', ['core' => 7]));
$this->assertIdentical('file', $plugin_manager->getPluginIdFromFieldType('file', ['core' => 7]));
$this->assertIdentical('d6_file', $plugin_manager->getPluginIdFromFieldType('file', ['core' => 6]));
$this->assertIdentical('d6_text', $plugin_manager->getPluginIdFromFieldType('text', ['core' => 6]));
$this->assertIdentical('d7_text', $plugin_manager->getPluginIdFromFieldType('text', ['core' => 7]));
// Test that the deprecated d6 'date' plugin is not returned.
$this->assertSame('datetime', $this->pluginManager->getPluginIdFromFieldType('date', ['core' => 6]));
// Test fallback when no core version is specified.
$this->assertIdentical('d6_no_core_version_specified', $plugin_manager->getPluginIdFromFieldType('d6_no_core_version_specified', ['core' => 6]));
$this->assertSame('d6_no_core_version_specified', $this->pluginManager->getPluginIdFromFieldType('d6_no_core_version_specified', ['core' => 6]));
}
try {
// If this test passes, getPluginIdFromFieldType will raise a
// PluginNotFoundException and we'll never reach fail().
$plugin_manager->getPluginIdFromFieldType('d6_no_core_version_specified', ['core' => 7]);
$this->fail('Expected Drupal\Component\Plugin\Exception\PluginNotFoundException.');
}
catch (PluginNotFoundException $e) {
$this->assertIdentical($e->getMessage(), "Plugin ID 'd6_no_core_version_specified' was not found.");
/**
* Tests that a PluginNotFoundException is thrown when a plugin isn't found.
*
* @covers ::getPluginIdFromFieldType
* @dataProvider nonExistentPluginExceptionsData
*/
public function testNonExistentPluginExceptions($core, $field_type) {
$this->setExpectedException(PluginNotFoundException::class, sprintf("Plugin ID '%s' was not found.", $field_type));
$this->pluginManager->getPluginIdFromFieldType($field_type, ['core' => $core]);
}
/**
* Provides data for testNonExistentPluginExceptions.
*
* @return array
* The data.
*/
public function nonExistentPluginExceptionsData() {
return [
'D7 Filefield' => [
'core' => 7,
'field_type' => 'filefield',
],
'D6 linkfield' => [
'core' => 6,
'field_type' => 'link_field',
],
'D7 link' => [
'core' => 7,
'field_type' => 'link',
],
'D7 no core version' => [
'core' => 7,
'field_type' => 'd6_no_core_version_specified',
],
];
}
/**
* Tests that deprecated plugins can still be directly created.
*
* Tests that a deprecated plugin triggers an error on instantiation. This
* test has an implicit assertion that the deprecation error will be triggered
* and does not need an explicit assertion to pass.
*
* @covers ::createInstance
* @group legacy
* @expectedDeprecation DateField is deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.x. Use \Drupal\datetime\Plugin\migrate\field\DateField instead.
*/
public function testDeprecatedPluginDirectAccess() {
$this->pluginManager->createInstance('date');
}
/**
* Tests that plugins with no explicit weight are given a weight of 0.
*/
public function testDefaultWeight() {
$definitions = $this->pluginManager->getDefinitions();
$deprecated_plugins = [
'date',
];
foreach ($definitions as $id => $definition) {
$this->assertArrayHasKey('weight', $definition);
if (in_array($id, $deprecated_plugins, TRUE)) {
$this->assertSame(9999999, $definition['weight']);
}
else {
$this->assertSame(0, $definition['weight']);
}
}
}
......
<?php
namespace Drupal\Tests\migrate_drupal\Unit;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
use Drupal\migrate_drupal\Annotation\MigrateField;
use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManager;
use Drupal\Tests\UnitTestCase;
/**
* Tests the MigrateFieldPluginManager class.
*
* @group migrate_drupal
* @coversDefaultClass \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManager
*/
class MigrateFieldPluginManagerTest extends UnitTestCase {
/**
* Tests the plugin weighting system.
*
* @covers ::getPluginIdFromFieldType
* @covers ::sortDefinitions
* @covers ::findDefinitions
* @dataProvider weightsData
*/
public function testWeights($field_type, $core, $expected_plugin_id) {
/* @var \Drupal\Core\Cache\CacheBackendInterface $cache */
$cache = $this->prophesize(CacheBackendInterface::class)->reveal();
/* @var \Drupal\Core\Extension\ModuleHandlerInterfaceModuleHandlerInterface $module_handler */
$module_handler = $this->prophesize(ModuleHandlerInterface::class)->reveal();
$discovery = $this->prophesize(AnnotatedClassDiscovery::class);
$discovery->getDefinitions()->willReturn($this->pluginFixtureData());
$manager = new MigrateFieldPluginManagerTestClass('field', new \ArrayObject(), $cache, $module_handler, MigrateField::class, $discovery->reveal());
if (!$expected_plugin_id) {
$this->setExpectedException(PluginNotFoundException::class, sprintf("Plugin ID '%s' was not found.", $field_type));
}
$actual_plugin_id = $manager->getPluginIdFromFieldType($field_type, ['core' => $core]);
$this->assertSame($expected_plugin_id, $actual_plugin_id);
}
/**
* Provides data for testWeights().
*
* @return array
* The data.
*/
public function weightsData() {
return [
'Field 1, D6' => [
'field_type' => 'field_1',
'core' => 6,
'expected_plugin_id' => 'core_replacement_plugin',
],
'Field 2, D6' => [
'field_type' => 'field_2',
'core' => 6,
'expected_plugin_id' => 'field_1',
],
'Field 3, D6' => [
'field_type' => 'field_3',
'core' => 6,
'expected_plugin_id' => 'field_3',
],
'Field 4, D6' => [
'field_type' => 'field_4',
'core' => 6,
'expected_plugin_id' => 'field_4',
],
'Field 5, D6' => [
'field_type' => 'field_5',
'core' => 6,
'expected_plugin_id' => 'alphabetically_second',
],
'Field 1, D7' => [
'field_type' => 'field_1',
'core' => 7,
'expected_plugin_id' => 'core_replacement_plugin',
],
'Field 2, D7' => [
'field_type' => 'field_2',
'core' => 7,
'expected_plugin_id' => FALSE,
],
'Field 3, D7' => [
'field_type' => 'field_3',
'core' => 7,
'expected_plugin_id' => 'field_3',
],
'Field 4, D7' => [
'field_type' => 'field_4',
'core' => 7,
'expected_plugin_id' => 'contrib_override_plugin',
],
'Field 5, D7' => [
'field_type' => 'field_5',
'core' => 7,
'expected_plugin_id' => 'alphabetically_first',
],
];
}
/**
* Returns test plugin data for the test class to use.
*
* @return array
* The test plugin data.
*/
protected function pluginFixtureData() {
return [
// Represents a deprecated core field plugin that applied to field_1
// and field_2 for Drupal 6.
'field_1' => [
'weight' => 99999999,
'core' => [6],
'type_map' => [
'field_1' => 'field_1',
'field_2' => 'field_2',
],
'source_module' => 'system',
'destination_module' => 'system',
],
// Replacement for deprecated plugin for field_1 in Drupal 6 and 7.
// Does not provide replacement for field_2.
'core_replacement_plugin' => [
'weight' => 0,
'core' => [6, 7],
'type_map' => [
'field_1' => 'field_1',
],
'source_module' => 'system',
'destination_module' => 'system',
],
// Represents a core plugin with no type_map, applies to field_3 due to
// plugin id.
'field_3' => [
'core' => [6, 7],
'type_map' => [],
'weight' => 0,
'source_module' => 'system',
'destination_module' => 'system',
],
// Represents a core plugin with no type_map, applies to field_4 due to
// plugin id.
'field_4' => [
'core' => [6, 7],
'type_map' => [],
'weight' => 0,
'source_module' => 'system',
'destination_module' => 'system',
],
// Represents a contrib plugin overriding field_4 for Drupal 7 only.
'contrib_override_plugin' => [
'weight' => -100,
'core' => [7],
'type_map' => [
'field_4' => 'field_4',
],
'source_module' => 'system',
'destination_module' => 'system',
],
// field_5 is served by alphabetically_second in Drupal 6 and
// alphabetically_first and alphabetically_second in Drupal 7. It should
// be served by the alphabetically_first in Drupal 7 regardless of the
// order they appear here.
'alphabetically_second' => [
'weight' => 0,
'core' => [6, 7],
'type_map' => [
'field_5' => 'field_5',
],
'source_module' => 'system',
'destination_module' => 'system',
],
'alphabetically_first' => [
'weight' => 0,
'core' => [7],
'type_map' => [
'field_5' => 'field_5',
],
'source_module' => 'system',
'destination_module' => 'system',
],
];
}
}
/**
* Class to test MigrateFieldPluginManager.
*
* Overrides the constructor to inject a mock discovery class to provide a test
* list of plugins.
*/
class MigrateFieldPluginManagerTestClass extends MigrateFieldPluginManager {
/**
* Constructs a MigratePluginManagerTestClass object.
*
* @param string $type
* The type of the plugin: row, source, process, destination, entity_field,
* id_map.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
* @param string $annotation
* The annotation class name.
* @param \Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery $discovery
* A mock plugin discovery object for the test class to use.
*/
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, $annotation, AnnotatedClassDiscovery $discovery) {
parent::__construct($type, $namespaces, $cache_backend, $module_handler, $annotation);
$this->discovery = $discovery;
}
}
......@@ -33,7 +33,8 @@ public function getProcess() {
}
if ($this->fieldPluginManager->hasDefinition($field_type)) {
if (!isset($this->fieldPluginCache[$field_type])) {
$this->fieldPluginCache[$field_type] = $this->fieldPluginManager->createInstance($field_type, [], $this);
$plugin_id = $this->fieldPluginManager->getPluginIdFromFieldType($field_type, [], $this);
$this->fieldPluginCache[$field_type] = $this->fieldPluginManager->createInstance($plugin_id, [], $this);
}
$info = $row->getSource();
$this->fieldPluginCache[$field_type]
......
......@@ -119,7 +119,6 @@ public static function getSkippedDeprecations() {
'Drupal\system\Plugin\views\field\BulkForm is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\views\Plugin\views\field\BulkForm instead. See https://www.drupal.org/node/2916716.',
'The numeric plugin for watchdog.wid field is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Must use standard plugin instead. See https://www.drupal.org/node/2876378.',
'Passing in arguments the legacy way is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Provide the right parameter names in the method, similar to controllers. See https://www.drupal.org/node/2894819',
'DateField is deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.x. Use \Drupal\datetime\Plugin\migrate\field\DateField instead.',
'The Drupal\editor\Plugin\EditorBase::settingsFormSubmit method is deprecated since version 8.3.x and will be removed in 9.0.0.',
'The "serializer.normalizer.file_entity.hal" normalizer service is deprecated: it is obsolete, it only remains available for backwards compatibility.',
'The Symfony\Component\ClassLoader\ApcClassLoader class is deprecated since Symfony 3.3 and will be removed in 4.0. Use `composer install --apcu-autoloader` instead.',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment