diff --git a/core/modules/migrate/src/Plugin/migrate/process/Explode.php b/core/modules/migrate/src/Plugin/migrate/process/Explode.php index ef7c94aabff6a6dcb10f12f82298b5f94262c64c..536a9e1b0ab89cede8dca74435e37b6ed733d25d 100644 --- a/core/modules/migrate/src/Plugin/migrate/process/Explode.php +++ b/core/modules/migrate/src/Plugin/migrate/process/Explode.php @@ -22,6 +22,11 @@ * returned. * - If the limit parameter is zero, then this is treated as 1. * - delimiter: The boundary string. + * - strict: (optional) When this boolean is TRUE, the source should be strictly + * a string. If FALSE is passed, the source value is casted to a string before + * being split. Also, in this case, the values casting to empty strings are + * converted to empty arrays, instead of an array with a single empty string + * item ['']. Defaults to TRUE. * * Example: * @@ -29,8 +34,8 @@ * process: * bar: * plugin: explode - * source: foo - * delimiter: / + * source: foo + * delimiter: / * @endcode * * If foo is "node/1", then bar will be ['node', '1']. The PHP equivalent of @@ -44,9 +49,9 @@ * process: * bar: * plugin: explode - * source: foo - * limit: 1 - * delimiter: / + * source: foo + * limit: 1 + * delimiter: / * @endcode * * If foo is "node/1/edit", then bar will be ['node', '1/edit']. The PHP @@ -56,6 +61,30 @@ * $bar = explode('/', $foo, 1); * @endcode * + * If the 'strict' configuration is set to FALSE, the input value is casted to a + * string before being spilt: + * + * @code + * process: + * bar: + * plugin: explode + * source: foo + * delimiter: / + * strict: false + * @endcode + * + * If foo is 123 (as integer), then bar will be ['123']. If foo is TRUE, then + * bar will be ['1']. The PHP equivalent of this would be: + * + * @code + * $bar = explode('/', (string) 123); + * $bar = explode('/', (string) TRUE); + * @endcode + * + * If the 'strict' configuration is set to FALSE, the source value casting to + * an empty string are converted to an empty array. For example, with the last + * configuration, if foo is '', NULL or FALSE, then bar will be []. + * * @see \Drupal\migrate\Plugin\MigrateProcessInterface * * @MigrateProcessPlugin( @@ -68,18 +97,29 @@ class Explode extends ProcessPluginBase { * {@inheritdoc} */ public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { - if (is_string($value)) { - if (!empty($this->configuration['delimiter'])) { - $limit = isset($this->configuration['limit']) ? $this->configuration['limit'] : PHP_INT_MAX; - return explode($this->configuration['delimiter'], $value, $limit); - } - else { - throw new MigrateException('delimiter is empty'); - } + if (empty($this->configuration['delimiter'])) { + throw new MigrateException('delimiter is empty'); } - else { + + $strict = array_key_exists('strict', $this->configuration) ? $this->configuration['strict'] : TRUE; + if ($strict && !is_string($value)) { throw new MigrateException(sprintf('%s is not a string', var_export($value, TRUE))); } + elseif (!$strict) { + // Check if the incoming value can cast to a string. + $original = $value; + if (!is_string($original) && ($original != ($value = @strval($value)))) { + throw new MigrateException(sprintf('%s cannot be casted to a string', var_export($original, TRUE))); + } + // Empty strings should be exploded to empty arrays. + if ($value === '') { + return []; + } + } + + $limit = isset($this->configuration['limit']) ? $this->configuration['limit'] : PHP_INT_MAX; + + return explode($this->configuration['delimiter'], $value, $limit); } /** diff --git a/core/modules/migrate/tests/src/Unit/process/ExplodeTest.php b/core/modules/migrate/tests/src/Unit/process/ExplodeTest.php index 3245e60d9c3345180cf085b5ed29596183be0fb3..20bf3a0311d1b9f9b124cb1c00f683a6c2a343a2 100644 --- a/core/modules/migrate/tests/src/Unit/process/ExplodeTest.php +++ b/core/modules/migrate/tests/src/Unit/process/ExplodeTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\migrate\Unit\process; +use Drupal\migrate\MigrateException; use Drupal\migrate\Plugin\migrate\process\Explode; use Drupal\migrate\Plugin\migrate\process\Concat; @@ -53,23 +54,65 @@ public function testChainedTransform() { /** * Test explode fails properly on non-strings. - * - * @expectedException \Drupal\migrate\MigrateException - * - * @expectedExceptionMessage is not a string */ public function testExplodeWithNonString() { + $this->setExpectedException(MigrateException::class, 'is not a string'); $this->plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty'); } /** - * Test explode fails with empty delimiter. + * Tests that explode works on non-strings but with strict set to FALSE. * - * @expectedException \Drupal\migrate\MigrateException - * - * @expectedExceptionMessage delimiter is empty + * @dataProvider providerExplodeWithNonStrictAndEmptySource + */ + public function testExplodeWithNonStrictAndEmptySource($value, $expected) { + $plugin = new Explode(['delimiter' => '|', 'strict' => FALSE], 'map', []); + + $processed = $plugin->transform($value, $this->migrateExecutable, $this->row, 'destinationproperty'); + $this->assertSame($expected, $processed); + } + + /** + * Data provider for ::testExplodeWithNonStrictAndEmptySource(). + */ + public function providerExplodeWithNonStrictAndEmptySource() { + return [ + 'normal_string' => ['a|b|c', ['a', 'b', 'c']], + 'integer_cast_to_string' => [123, ['123']], + 'zero_integer_cast_to_string' => [0, ['0']], + 'true_cast_to_string' => [TRUE, ['1']], + 'null_empty_array' => [NULL, []], + 'false_empty_array' => [FALSE, []], + 'empty_string_empty_array' => ['', []], + ]; + } + + /** + * Tests that explode raises an exception when the value cannot be casted to + * string. + */ + public function testExplodeWithNonStrictAndNonCastable() { + $plugin = new Explode(['delimiter' => '|', 'strict' => FALSE], 'map', []); + $this->setExpectedException(MigrateException::class, 'cannot be casted to a string'); + $processed = $plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty'); + $this->assertSame(['foo'], $processed); + } + + /** + * Tests that explode with an empty string and strict check returns a + * non-empty array. + */ + public function testExplodeWithStrictAndEmptyString() { + $plugin = new Explode(['delimiter' => '|'], 'map', []); + $processed = $plugin->transform('', $this->migrateExecutable, $this->row, 'destinationproperty'); + $this->assertSame([''], $processed); + } + + /** + * Test explode fails with empty delimiter. */ public function testExplodeWithEmptyDelimiter() { + $this->setExpectedException(MigrateException::class, 'delimiter is empty'); $plugin = new Explode(['delimiter' => ''], 'map', []); $plugin->transform('foo,bar', $this->migrateExecutable, $this->row, 'destinationproperty'); }