From 92e703a69ac5153864d9b9047221a5b415b79fe5 Mon Sep 17 00:00:00 2001 From: Dave Long <dave@longwaveconsulting.com> Date: Fri, 21 Jul 2023 12:23:08 +0200 Subject: [PATCH] Issue #3108020 by bonrita, Cyberwolf, sagarchauhan, znerol, borisson_: Support ServiceClosureArgument in \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper::dumpValue --- .../DependencyInjection/Container.php | 7 ++++ .../Dumper/OptimizedPhpArrayDumper.php | 27 +++++++++++++++ .../DependencyInjection/PhpArrayContainer.php | 7 ++++ .../DependencyInjection/ContainerTest.php | 34 +++++++++++++++++++ .../Dumper/OptimizedPhpArrayDumperTest.php | 32 +++++++++++++++++ 5 files changed, 107 insertions(+) diff --git a/core/lib/Drupal/Component/DependencyInjection/Container.php b/core/lib/Drupal/Component/DependencyInjection/Container.php index 4432b096721a..104da1276d95 100644 --- a/core/lib/Drupal/Component/DependencyInjection/Container.php +++ b/core/lib/Drupal/Component/DependencyInjection/Container.php @@ -459,6 +459,13 @@ protected function resolveServicesAndParameters($arguments) { continue; } + elseif ($type == 'service_closure') { + $arguments[$key] = function () use ($argument) { + return $this->get($argument->id, $argument->invalidBehavior); + }; + + continue; + } // Check for collection. elseif ($type == 'collection') { $value = $argument->value; diff --git a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php index c5ce59b448fc..e56b890c71e6 100644 --- a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php +++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php @@ -3,6 +3,7 @@ namespace Drupal\Component\DependencyInjection\Dumper; use Drupal\Component\Utility\Crypt; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Parameter; @@ -427,6 +428,13 @@ protected function dumpValue($value) { elseif ($value instanceof Expression) { throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } + elseif ($value instanceof ServiceClosureArgument) { + $reference = $value->getValues(); + /** @var \Symfony\Component\DependencyInjection\Reference $reference */ + $reference = reset($reference); + + return $this->getServiceClosureCall((string) $reference, $reference->getInvalidBehavior()); + } elseif (is_object($value)) { // Drupal specific: Instantiated objects have a _serviceId parameter. if (isset($value->_serviceId)) { @@ -525,4 +533,23 @@ protected function supportsMachineFormat() { return TRUE; } + /** + * Gets a service closure reference in a suitable PHP array format. + * + * @param string $id + * The ID of the service to get a reference for. + * @param int $invalid_behavior + * (optional) The invalid behavior of the service. + * + * @return string|object + * A suitable representation of the service closure reference. + */ + protected function getServiceClosureCall(string $id, int $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { + return (object) [ + 'type' => 'service_closure', + 'id' => $id, + 'invalidBehavior' => $invalid_behavior, + ]; + } + } diff --git a/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php b/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php index 95c4e34d0c7a..2b72dfb5d484 100644 --- a/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php +++ b/core/lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php @@ -165,6 +165,13 @@ protected function resolveServicesAndParameters($arguments) { continue; } + elseif ($type == 'service_closure') { + $arguments[$key] = function () use ($argument) { + return $this->get($argument->id, $argument->invalidBehavior); + }; + + continue; + } elseif ($type == 'raw') { $arguments[$key] = $argument->value; diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php index 8150946071bb..e8b5fd56787e 100644 --- a/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php +++ b/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php @@ -593,6 +593,21 @@ public function testResolveServicesAndParametersForOptionalServiceDependencies() $this->assertNull($service->getSomeOtherService(), 'other service was NULL was expected.'); } + /** + * Tests that services wrapped in a closure work correctly. + * + * @covers ::get + * @covers ::createService + * @covers ::resolveServicesAndParameters + */ + public function testResolveServicesAndParametersForServiceReferencedViaServiceClosure() { + $service = $this->container->get('service_within_service_closure'); + $other_service = $this->container->get('other.service'); + $factory_function = $service->getSomeOtherService(); + $this->assertInstanceOf(\Closure::class, $factory_function); + $this->assertEquals($other_service, call_user_func($factory_function)); + } + /** * Tests that an invalid argument throw an Exception. * @@ -845,6 +860,14 @@ protected function getMockContainerDefinition() { ]; + $services['service_within_service_closure'] = [ + 'class' => '\Drupal\Tests\Component\DependencyInjection\MockService', + 'arguments' => $this->getCollection([ + $this->getServiceClosureCall('other.service', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE), + $this->getParameterCall('some_private_config'), + ]), + ]; + $services['factory_service'] = [ 'class' => '\Drupal\service_container\ServiceContainer\ControllerInterface', 'factory' => [ @@ -981,6 +1004,17 @@ protected function getServiceCall($id, $invalid_behavior = ContainerInterface::E ]; } + /** + * Helper function to return a service closure definition. + */ + protected function getServiceClosureCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { + return (object) [ + 'type' => 'service_closure', + 'id' => $id, + 'invalidBehavior' => $invalid_behavior, + ]; + } + /** * Helper function to return a parameter definition. */ diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php index 5cd26aa6fff1..367bf005a534 100644 --- a/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php +++ b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php @@ -12,6 +12,7 @@ use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophet; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; + use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Parameter; @@ -205,6 +206,7 @@ public function getParametersDataProvider() { * @covers ::getPrivateServiceCall * @covers ::getReferenceCall * @covers ::getServiceCall + * @covers ::getServiceClosureCall * @covers ::getParameterCall * * @dataProvider getDefinitionsDataProvider @@ -305,6 +307,25 @@ public static function getDefinitionsDataProvider() { ]), ] + $base_service_definition; + // Test a service closure. + $service_definitions[] = [ + 'arguments' => [ + 'foo', + [ + 'alias-1' => new ServiceClosureArgument(new Reference('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE)), + 'alias-2' => new ServiceClosureArgument(new Reference('bar', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)), + ], + ], + 'arguments_count' => 2, + 'arguments_expected' => static::getCollection([ + 'foo', + static::getCollection([ + 'alias-1' => static::getServiceClosureCall('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE), + 'alias-2' => static::getServiceClosureCall('bar', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE), + ]), + ]), + ] + $base_service_definition; + // Test a private non-shared service, denoted by having a Definition. $private_definition_object = new Definition('\stdClass'); $private_definition_object->setPublic(FALSE); @@ -458,6 +479,17 @@ protected static function getServiceCall($id, $invalid_behavior = ContainerInter ]; } + /** + * Helper function to return a service closure definition. + */ + protected static function getServiceClosureCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { + return (object) [ + 'type' => 'service_closure', + 'id' => $id, + 'invalidBehavior' => $invalid_behavior, + ]; + } + /** * Tests that references to aliases work correctly. * -- GitLab