Skip to content
Snippets Groups Projects
Commit fc0aa24e authored by catch's avatar catch
Browse files

Issue #3414208 by kim.pepper, longwave, alexpott: Add support for tagged_iterator to YamlFileLoader

parent a65bbd5d
No related branches found
No related tags found
29 merge requests!11131[10.4.x-only-DO-NOT-MERGE]: Issue ##2842525 Ajax attached to Views exposed filter form does not trigger callbacks,!9470[10.3.x-only-DO-NOT-MERGE]: #3331771 Fix file_get_contents(): Passing null to parameter,!8540Issue #3457061: Bootstrap Modal dialog Not closing after 10.3.0 Update,!8528Issue #3456871 by Tim Bozeman: Support NULL services,!8373Issue #3427374 by danflanagan8, Vighneshh: taxonomy_tid ViewsArgumentDefault...,!7526Expose roles in response,!7352Draft: Resolve #3203489 "Set filename as",!3878Removed unused condition head title for views,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3133core/modules/system/css/components/hidden.module.css,!2964Issue #2865710 : Dependencies from only one instance of a widget are used in display modes,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2794Issue #3100732: Allow specifying `meta` data on JSON:API objects,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2062Issue #3246454: Add weekly granularity to views date sort,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!877Issue #2708101: Default value for link text is not saved,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493
Pipeline #135169 canceled
Pipeline: drupal

#135176

    Showing with 121 additions and 10 deletions
    ...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
    namespace Drupal\Component\DependencyInjection; namespace Drupal\Component\DependencyInjection;
    use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
    use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\LogicException;
    use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
    use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\RuntimeException;
    ...@@ -455,6 +456,16 @@ protected function resolveServicesAndParameters($arguments) { ...@@ -455,6 +456,16 @@ protected function resolveServicesAndParameters($arguments) {
    continue; continue;
    } }
    elseif ($type == 'iterator') {
    $services = $argument->value;
    $arguments[$key] = new RewindableGenerator(function () use ($services) {
    foreach ($services as $key => $service) {
    yield $key => $this->resolveServicesAndParameters([$service])[0];
    }
    }, count($services));
    continue;
    }
    // Check for collection. // Check for collection.
    elseif ($type == 'collection') { elseif ($type == 'collection') {
    $arguments[$key] = $this->resolveServicesAndParameters($argument->value); $arguments[$key] = $this->resolveServicesAndParameters($argument->value);
    ......
    ...@@ -306,9 +306,6 @@ protected function dumpCollection($collection, &$resolve = FALSE) { ...@@ -306,9 +306,6 @@ protected function dumpCollection($collection, &$resolve = FALSE) {
    $code = []; $code = [];
    foreach ($collection as $key => $value) { foreach ($collection as $key => $value) {
    if ($value instanceof IteratorArgument) {
    $value = $value->getValues();
    }
    if (is_array($value)) { if (is_array($value)) {
    $resolve_collection = FALSE; $resolve_collection = FALSE;
    $code[$key] = $this->dumpCollection($value, $resolve_collection); $code[$key] = $this->dumpCollection($value, $resolve_collection);
    ...@@ -438,6 +435,9 @@ protected function dumpValue($value) { ...@@ -438,6 +435,9 @@ protected function dumpValue($value) {
    return $this->getServiceClosureCall((string) $reference, $reference->getInvalidBehavior()); return $this->getServiceClosureCall((string) $reference, $reference->getInvalidBehavior());
    } }
    elseif ($value instanceof IteratorArgument) {
    return $this->getIterator($value);
    }
    elseif (is_object($value)) { elseif (is_object($value)) {
    throw new RuntimeException('Unable to dump a service container if a parameter is an object.'); throw new RuntimeException('Unable to dump a service container if a parameter is an object.');
    } }
    ...@@ -550,4 +550,20 @@ protected function getServiceClosureCall(string $id, int $invalid_behavior = Con ...@@ -550,4 +550,20 @@ protected function getServiceClosureCall(string $id, int $invalid_behavior = Con
    ]; ];
    } }
    /**
    * Gets a service iterator in a suitable PHP array format.
    *
    * @param \Symfony\Component\DependencyInjection\Argument\IteratorArgument $iterator
    * The iterator.
    *
    * @return object
    * The PHP array representation of the iterator.
    */
    protected function getIterator(IteratorArgument $iterator) {
    return (object) [
    'type' => 'iterator',
    'value' => array_map($this->dumpValue(...), $iterator->getValues()),
    ];
    }
    } }
    ...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
    namespace Drupal\Component\DependencyInjection\Dumper; namespace Drupal\Component\DependencyInjection\Dumper;
    use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
    use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
    /** /**
    ...@@ -34,9 +33,6 @@ protected function dumpCollection($collection, &$resolve = FALSE) { ...@@ -34,9 +33,6 @@ protected function dumpCollection($collection, &$resolve = FALSE) {
    $code = []; $code = [];
    foreach ($collection as $key => $value) { foreach ($collection as $key => $value) {
    if ($value instanceof IteratorArgument) {
    $value = $value->getValues();
    }
    if (is_array($value)) { if (is_array($value)) {
    $code[$key] = $this->dumpCollection($value); $code[$key] = $this->dumpCollection($value);
    } }
    ......
    ...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
    namespace Drupal\Component\DependencyInjection; namespace Drupal\Component\DependencyInjection;
    use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
    use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
    use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
    use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\RuntimeException;
    ...@@ -174,6 +175,15 @@ protected function resolveServicesAndParameters($arguments) { ...@@ -174,6 +175,15 @@ protected function resolveServicesAndParameters($arguments) {
    continue; continue;
    } }
    elseif ($type == 'iterator') {
    $services = $argument->value;
    $arguments[$key] = new RewindableGenerator(function () use ($services) {
    foreach ($services as $key => $service) {
    yield $key => $this->resolveServicesAndParameters([$service])[0];
    }
    }, count($services));
    continue;
    }
    if ($type !== NULL) { if ($type !== NULL) {
    throw new InvalidArgumentException("Undefined type '$type' while resolving parameters and services."); throw new InvalidArgumentException("Undefined type '$type' while resolving parameters and services.");
    ......
    ...@@ -34,7 +34,10 @@ public static function decode($raw) { ...@@ -34,7 +34,10 @@ public static function decode($raw) {
    $yaml = new Parser(); $yaml = new Parser();
    // Make sure we have a single trailing newline. A very simple config like // Make sure we have a single trailing newline. A very simple config like
    // 'foo: bar' with no newline will fail to parse otherwise. // 'foo: bar' with no newline will fail to parse otherwise.
    return $yaml->parse($raw, SymfonyYaml::PARSE_EXCEPTION_ON_INVALID_TYPE); return $yaml->parse(
    $raw,
    SymfonyYaml::PARSE_EXCEPTION_ON_INVALID_TYPE | SymfonyYaml::PARSE_CUSTOM_TAGS
    );
    } }
    catch (\Exception $e) { catch (\Exception $e) {
    throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e); throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e);
    ......
    ...@@ -9,11 +9,14 @@ ...@@ -9,11 +9,14 @@
    use Drupal\Component\Serialization\Exception\InvalidDataTypeException; use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
    use Drupal\Core\Serialization\Yaml; use Drupal\Core\Serialization\Yaml;
    use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
    use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
    use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
    use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
    use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
    use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ChildDefinition;
    use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
    use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
    use Symfony\Component\Yaml\Tag\TaggedValue;
    /** /**
    * YamlFileLoader loads YAML files service definitions. * YamlFileLoader loads YAML files service definitions.
    ...@@ -464,8 +467,32 @@ private function validate($content, $file) ...@@ -464,8 +467,32 @@ private function validate($content, $file)
    * *
    * @return array|string|Reference * @return array|string|Reference
    */ */
    private function resolveServices($value) private function resolveServices(mixed $value): mixed
    { {
    if ($value instanceof TaggedValue) {
    $argument = $value->getValue();
    if (\in_array($value->getTag(), ['tagged', 'tagged_iterator', 'tagged_locator'], true)) {
    $forLocator = 'tagged_locator' === $value->getTag();
    if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) {
    if ($diff = array_diff(array_keys($argument), $supportedKeys = ['tag', 'index_by', 'default_index_method', 'default_priority_method', 'exclude', 'exclude_self'])) {
    throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "%s".', $value->getTag(), implode('", "', $diff), implode('", "', $supportedKeys)));
    }
    $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null, (array) ($argument['exclude'] ?? null), $argument['exclude_self'] ?? true);
    } elseif (\is_string($argument) && $argument) {
    $argument = new TaggedIteratorArgument($argument, null, null, $forLocator);
    } else {
    throw new InvalidArgumentException(sprintf('"!%s" tags only accept a non empty string or an array with a key "tag"".', $value->getTag()));
    }
    if ($forLocator) {
    $argument = new ServiceLocatorArgument($argument);
    }
    return $argument;
    }
    }
    if (is_array($value)) { if (is_array($value)) {
    $value = array_map(array($this, 'resolveServices'), $value); $value = array_map(array($this, 'resolveServices'), $value);
    } elseif (is_string($value) && str_starts_with($value, '@=')) { } elseif (is_string($value) && str_starts_with($value, '@=')) {
    ......
    ...@@ -697,6 +697,19 @@ public function testResolveServicesAndParametersForRawArgument() { ...@@ -697,6 +697,19 @@ public function testResolveServicesAndParametersForRawArgument() {
    $this->assertEquals(['ccc'], $this->container->get('service_with_raw_argument')->getArguments()); $this->assertEquals(['ccc'], $this->container->get('service_with_raw_argument')->getArguments());
    } }
    /**
    * Tests that service iterators are lazily instantiated.
    */
    public function testIterator() {
    $iterator = $this->container->get('service_iterator')->getArguments()[0];
    $this->assertIsIterable($iterator);
    $this->assertFalse($this->container->initialized('other.service'));
    foreach ($iterator as $service) {
    $this->assertIsObject($service);
    }
    $this->assertTrue($this->container->initialized('other.service'));
    }
    /** /**
    * Tests Container::reset(). * Tests Container::reset().
    * *
    ...@@ -975,6 +988,16 @@ protected function getMockContainerDefinition() { ...@@ -975,6 +988,16 @@ protected function getMockContainerDefinition() {
    'arguments' => $this->getCollection([$this->getRaw('ccc')]), 'arguments' => $this->getCollection([$this->getRaw('ccc')]),
    ]; ];
    // Iterator argument.
    $services['service_iterator'] = [
    'class' => '\Drupal\Tests\Component\DependencyInjection\MockInstantiationService',
    'arguments' => $this->getCollection([
    $this->getIterator([
    $this->getServiceCall('other.service'),
    ]),
    ]),
    ];
    $aliases = []; $aliases = [];
    $aliases['service.provider_alias'] = 'service.provider'; $aliases['service.provider_alias'] = 'service.provider';
    $aliases['late.service_alias'] = 'late.service'; $aliases['late.service_alias'] = 'late.service';
    ...@@ -1010,6 +1033,16 @@ protected function getServiceClosureCall($id, $invalid_behavior = ContainerInter ...@@ -1010,6 +1033,16 @@ protected function getServiceClosureCall($id, $invalid_behavior = ContainerInter
    ]; ];
    } }
    /**
    * Helper function to return a service iterator.
    */
    protected function getIterator($iterator) {
    return (object) [
    'type' => 'iterator',
    'value' => $iterator,
    ];
    }
    /** /**
    * Helper function to return a parameter definition. * Helper function to return a parameter definition.
    */ */
    ......
    ...@@ -354,7 +354,7 @@ public static function getDefinitionsDataProvider() { ...@@ -354,7 +354,7 @@ public static function getDefinitionsDataProvider() {
    $service_definitions[] = [ $service_definitions[] = [
    'arguments' => [new IteratorArgument([new Reference('bar')])], 'arguments' => [new IteratorArgument([new Reference('bar')])],
    'arguments_count' => 1, 'arguments_count' => 1,
    'arguments_expected' => static::getCollection([static::getCollection([static::getServiceCall('bar')])]), 'arguments_expected' => static::getCollection([static::getIterator([static::getServiceCall('bar')])]),
    ] + $base_service_definition; ] + $base_service_definition;
    // Test a collection with a variable to resolve. // Test a collection with a variable to resolve.
    ...@@ -695,6 +695,16 @@ protected static function getCollection($collection) { ...@@ -695,6 +695,16 @@ protected static function getCollection($collection) {
    ]; ];
    } }
    /**
    * Helper function to return a machine-optimized iterator.
    */
    protected static function getIterator($collection) {
    return (object) [
    'type' => 'iterator',
    'value' => $collection,
    ];
    }
    /** /**
    * Helper function to return a parameter definition. * Helper function to return a parameter definition.
    */ */
    ......
    ...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
    use Drupal\Core\DependencyInjection\YamlFileLoader; use Drupal\Core\DependencyInjection\YamlFileLoader;
    use Drupal\Tests\UnitTestCase; use Drupal\Tests\UnitTestCase;
    use org\bovigo\vfs\vfsStream; use org\bovigo\vfs\vfsStream;
    use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
    /** /**
    * @coversDefaultClass \Drupal\Core\DependencyInjection\YamlFileLoader * @coversDefaultClass \Drupal\Core\DependencyInjection\YamlFileLoader
    ...@@ -35,6 +36,9 @@ class: \Drupal\Core\ExampleClass ...@@ -35,6 +36,9 @@ class: \Drupal\Core\ExampleClass
    class: \Drupal\Core\ExampleClass class: \Drupal\Core\ExampleClass
    public: false public: false
    Drupal\Core\ExampleClass: ~ Drupal\Core\ExampleClass: ~
    example_tagged_iterator:
    class: \Drupal\Core\ExampleClass
    arguments: [!tagged_iterator foo.bar]"
    YAML; YAML;
    vfsStream::setup('drupal', NULL, [ vfsStream::setup('drupal', NULL, [
    ...@@ -58,6 +62,7 @@ class: \Drupal\Core\ExampleClass ...@@ -58,6 +62,7 @@ class: \Drupal\Core\ExampleClass
    $this->assertFalse($builder->has('example_private_service')); $this->assertFalse($builder->has('example_private_service'));
    $this->assertTrue($builder->has('Drupal\Core\ExampleClass')); $this->assertTrue($builder->has('Drupal\Core\ExampleClass'));
    $this->assertSame('Drupal\Core\ExampleClass', $builder->getDefinition('Drupal\Core\ExampleClass')->getClass()); $this->assertSame('Drupal\Core\ExampleClass', $builder->getDefinition('Drupal\Core\ExampleClass')->getClass());
    $this->assertInstanceOf(TaggedIteratorArgument::class, $builder->getDefinition('example_tagged_iterator')->getArgument(0));
    } }
    /** /**
    ......
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment