diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 4088176563f..37aa8a70b3e 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -136,7 +136,7 @@ class Drupal { /** * The currently active container object, or NULL if not initialized yet. * - * @var \Symfony\Component\DependencyInjection\ContainerInterface|null + * @var \Drupal\Component\DependencyInjection\ContainerInterface|null */ protected static $container; @@ -160,7 +160,7 @@ class Drupal { /** * Returns the currently active global container. * - * @return \Symfony\Component\DependencyInjection\ContainerInterface + * @return \Drupal\Component\DependencyInjection\ContainerInterface * * @throws \Drupal\Core\DependencyInjection\ContainerNotInitializedException */ diff --git a/core/lib/Drupal/Component/DependencyInjection/Container.php b/core/lib/Drupal/Component/DependencyInjection/Container.php index 127656539ad..344bb3258b0 100644 --- a/core/lib/Drupal/Component/DependencyInjection/Container.php +++ b/core/lib/Drupal/Component/DependencyInjection/Container.php @@ -2,7 +2,6 @@ namespace Drupal\Component\DependencyInjection; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -46,6 +45,8 @@ use Symfony\Contracts\Service\ResetInterface; */ class Container implements ContainerInterface, ResetInterface { + use ServiceIdHashTrait; + /** * The parameters of the container. * @@ -536,10 +537,7 @@ class Container implements ContainerInterface, ResetInterface { } /** - * Gets all defined service IDs. - * - * @return array - * An array of all defined service IDs. + * {@inheritdoc} */ public function getServiceIds() { return array_keys($this->serviceDefinitions + $this->services); diff --git a/core/lib/Drupal/Component/DependencyInjection/ContainerInterface.php b/core/lib/Drupal/Component/DependencyInjection/ContainerInterface.php new file mode 100644 index 00000000000..e6c5ea72d45 --- /dev/null +++ b/core/lib/Drupal/Component/DependencyInjection/ContainerInterface.php @@ -0,0 +1,42 @@ +_serviceId)) { + @trigger_error('_serviceId is deprecated in drupal:9.5.0 and is removed from drupal:11.0.0. Use \Drupal\Core\DrupalKernelInterface::getServiceIdMapping() instead. See https://www.drupal.org/node/3292540', E_USER_DEPRECATED); return $this->getReferenceCall($value->_serviceId); } throw new RuntimeException('Unable to dump a service container if a parameter is an object without _serviceId.'); diff --git a/core/lib/Drupal/Component/DependencyInjection/ServiceIdHashTrait.php b/core/lib/Drupal/Component/DependencyInjection/ServiceIdHashTrait.php new file mode 100644 index 00000000000..d53f9cb650d --- /dev/null +++ b/core/lib/Drupal/Component/DependencyInjection/ServiceIdHashTrait.php @@ -0,0 +1,37 @@ +getServiceIds() as $service_id) { + if ($this->initialized($service_id) && $service_id !== 'service_container') { + $mapping[$this->generateServiceIdHash($this->get($service_id))] = $service_id; + } + } + return $mapping; + } + + /** + * Implements \Drupal\Component\DependencyInjection\ContainerInterface::generateServiceIdHash() + */ + public function generateServiceIdHash(object $object): string { + // Include class name as an additional namespace for the hash since + // spl_object_hash's return can be recycled. This still is not a 100% + // guarantee to be unique but makes collisions incredibly difficult and even + // then the interface would be preserved. + // @see https://php.net/spl_object_hash#refsect1-function.spl-object-hash-notes + return hash('sha256', get_class($object) . spl_object_hash($object)); + } + +} diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index 58cc8b9844c..e86cc052cba 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -1064,13 +1064,13 @@ class ConfigImporter { * keep the services used by the importer in sync. */ protected function reInjectMe() { - $this->_serviceIds = []; - $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if (is_object($value) && isset($value->_serviceId)) { - $this->$key = \Drupal::service($value->_serviceId); - } - } + // When rebuilding the container, + // \Drupal\Core\DrupalKernel::initializeContainer() saves the hashes of the + // old container and passes them to the new one. So __sleep() will + // recognize the old services and then __wakeup() will restore them from + // the new container. + $this->__sleep(); + $this->__wakeup(); } } diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 2c90f6fbc82..dd94f4e5f00 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -10,7 +10,6 @@ use Drupal\Core\DependencyInjection\Compiler\CorsCompilerPass; use Drupal\Core\DependencyInjection\Compiler\DeprecatedServicePass; use Drupal\Core\DependencyInjection\Compiler\ContextProvidersPass; use Drupal\Core\DependencyInjection\Compiler\ProxyServicesPass; -use Drupal\Core\DependencyInjection\Compiler\DependencySerializationTraitPass; use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass; use Drupal\Core\DependencyInjection\Compiler\StackedSessionHandlerPass; use Drupal\Core\DependencyInjection\Compiler\RegisterStreamWrappersPass; @@ -95,7 +94,6 @@ class CoreServiceProvider implements ServiceProviderInterface, ServiceModifierIn // Register plugin managers. $container->addCompilerPass(new PluginManagerPass()); - $container->addCompilerPass(new DependencySerializationTraitPass()); $container->addCompilerPass(new DeprecatedServicePass()); } diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php index a0ba7f0225e..08a2b46e653 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/DependencySerializationTraitPass.php @@ -8,6 +8,11 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; /** * Sets the _serviceId property on all services. * + * @deprecated in drupal:9.5.0 and is removed from drupal:11.0.0. The _serviceId + * property is no longer part of the container. Use + * \Drupal\Core\DrupalKernelInterface::getServiceIdMapping() instead. + * + * @see https://www.drupal.org/node/3292540 * @see \Drupal\Core\DependencyInjection\DependencySerializationTrait */ class DependencySerializationTraitPass implements CompilerPassInterface { @@ -16,35 +21,6 @@ class DependencySerializationTraitPass implements CompilerPassInterface { * {@inheritdoc} */ public function process(ContainerBuilder $container) { - $decorations = new \SplPriorityQueue(); - $order = PHP_INT_MAX; - - foreach ($container->getDefinitions() as $service_id => $definition) { - // Only add the property to services that are public (as private services - // can not be reloaded through Container::get()) and are objects. - if (!$definition->hasTag('parameter_service') && $definition->isPublic()) { - $definition->setProperty('_serviceId', $service_id); - } - - if ($decorated = $definition->getDecoratedService()) { - $decorations->insert([$service_id, $definition], [$decorated[2], --$order]); - } - } - - foreach ($decorations as list($service_id, $definition)) { - list($inner, $renamedId) = $definition->getDecoratedService(); - if (!$renamedId) { - $renamedId = $service_id . '.inner'; - } - - $original = $container->getDefinition($inner); - if ($original->isPublic()) { - // The old service is renamed. - $original->setProperty('_serviceId', $renamedId); - // The decorating service takes over the old ID. - $definition->setProperty('_serviceId', $inner); - } - } } } diff --git a/core/lib/Drupal/Core/DependencyInjection/Container.php b/core/lib/Drupal/Core/DependencyInjection/Container.php index 56433633169..349261052d2 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Container.php +++ b/core/lib/Drupal/Core/DependencyInjection/Container.php @@ -9,18 +9,6 @@ use Drupal\Component\DependencyInjection\Container as DrupalContainer; */ class Container extends DrupalContainer { - /** - * {@inheritdoc} - */ - public function set($id, $service) { - parent::set($id, $service); - - // Ensure that the _serviceId property is set on synthetic services as well. - if (isset($this->services[$id]) && is_object($this->services[$id]) && !isset($this->services[$id]->_serviceId)) { - $this->services[$id]->_serviceId = $id; - } - } - /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php index 3afe4a207b1..51f5607a225 100644 --- a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php +++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php @@ -2,6 +2,8 @@ namespace Drupal\Core\DependencyInjection; +use Drupal\Component\DependencyInjection\ContainerInterface; +use Drupal\Component\DependencyInjection\ServiceIdHashTrait; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerBuilder as SymfonyContainerBuilder; use Symfony\Component\DependencyInjection\Container as SymfonyContainer; @@ -15,7 +17,9 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; * * @ingroup container */ -class ContainerBuilder extends SymfonyContainerBuilder { +class ContainerBuilder extends SymfonyContainerBuilder implements ContainerInterface { + + use ServiceIdHashTrait; /** * {@inheritdoc} @@ -41,11 +45,6 @@ class ContainerBuilder extends SymfonyContainerBuilder { throw new \InvalidArgumentException("Service ID names must be lowercase: $id"); } SymfonyContainer::set($id, $service); - - // Ensure that the _serviceId property is set on synthetic services as well. - if (isset($this->services[$id]) && is_object($this->services[$id]) && !isset($this->services[$id]->_serviceId)) { - $this->services[$id]->_serviceId = $id; - } } /** diff --git a/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php b/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php index c24598689e3..a23a2f487ab 100644 --- a/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php +++ b/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php @@ -3,7 +3,8 @@ namespace Drupal\Core\DependencyInjection; use Drupal\Core\Entity\EntityStorageInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; /** * Provides dependency injection friendly methods for serialization. @@ -30,33 +31,50 @@ trait DependencySerializationTrait { * {@inheritdoc} */ public function __sleep() { - $this->_serviceIds = []; $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if (is_object($value) && isset($value->_serviceId)) { - // If a class member was instantiated by the dependency injection - // container, only store its ID so it can be used to get a fresh object - // on unserialization. - $this->_serviceIds[$key] = $value->_serviceId; - unset($vars[$key]); - } - // Special case the container, which might not have a service ID. - elseif ($value instanceof ContainerInterface) { - $this->_serviceIds[$key] = 'service_container'; - unset($vars[$key]); - } - elseif ($value instanceof EntityStorageInterface) { - // If a class member is an entity storage, only store the entity type ID - // the storage is for so it can be used to get a fresh object on - // unserialization. By doing this we prevent possible memory leaks when - // the storage is serialized when it contains a static cache of entity - // objects and additionally we ensure that we'll not have multiple - // storage objects for the same entity type and therefore prevent - // returning different references for the same entity. - $this->_entityStorages[$key] = $value->getEntityTypeId(); - unset($vars[$key]); + try { + $container = \Drupal::getContainer(); + $mapping = \Drupal::service('kernel')->getServiceIdMapping(); + foreach ($vars as $key => $value) { + if ($value instanceof EntityStorageInterface) { + // If a class member is an entity storage, only store the entity type + // ID the storage is for, so it can be used to get a fresh object on + // unserialization. By doing this we prevent possible memory leaks + // when the storage is serialized and it contains a static cache of + // entity objects. Additionally we ensure that we'll not have multiple + // storage objects for the same entity type and therefore prevent + // returning different references for the same entity. + $this->_entityStorages[$key] = $value->getEntityTypeId(); + unset($vars[$key]); + } + elseif (is_object($value)) { + $service_id = FALSE; + // Special case the container. + if ($value instanceof SymfonyContainerInterface) { + $service_id = 'service_container'; + } + else { + $id = $container->generateServiceIdHash($value); + if (isset($mapping[$id])) { + $service_id = $mapping[$id]; + } + } + if ($service_id) { + // If a class member was instantiated by the dependency injection + // container, only store its ID so it can be used to get a fresh object + // on unserialization. + $this->_serviceIds[$key] = $service_id; + unset($vars[$key]); + } + } } } + catch (ContainerNotInitializedException $e) { + // No container, no problem. + } + catch (ServiceNotFoundException $e) { + // No kernel, very strange, but still no problem. + } return array_keys($vars); } diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index ffe445ffa60..635201593de 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -105,7 +105,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { /** * Holds the container instance. * - * @var \Symfony\Component\DependencyInjection\ContainerInterface + * @var \Drupal\Component\DependencyInjection\ContainerInterface */ protected $container; @@ -242,6 +242,11 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { */ protected $root; + /** + * A mapping from service classes to service IDs. + */ + protected $serviceIdMapping = []; + /** * Create a DrupalKernel object from a request. * @@ -815,6 +820,32 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { } } + /** + * Generate a unique hash for a service object. + * + * @param object $object + * A service object. + * + * @return string + * A unique hash value. + */ + public static function generateServiceIdHash($object) { + // Include class name as an additional namespace for the hash since + // spl_object_hash's return can be recycled. This still is not a 100% + // guarantee to be unique but makes collisions incredibly difficult and even + // then the interface would be preserved. + // @see https://php.net/spl_object_hash#refsect1-function.spl-object-hash-notes + return hash('sha256', get_class($object) . spl_object_hash($object)); + } + + /** + * {@inheritdoc} + */ + public function getServiceIdMapping() { + $this->collectServiceIdMapping(); + return $this->serviceIdMapping; + } + /** * Returns the container cache key based on the environment. * @@ -860,6 +891,8 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { if ($this->container->initialized('current_user')) { $current_user_id = $this->container->get('current_user')->id(); } + // Save the current services. + $this->collectServiceIdMapping(); // If there is a session, close and save it. if ($this->container->initialized('session')) { @@ -1567,6 +1600,17 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { $this->serviceYamls['site'] = array_filter($service_yamls, 'file_exists'); } + /** + * Collect a mapping between service to ids. + */ + protected function collectServiceIdMapping() { + if (isset($this->container)) { + foreach ($this->container->getServiceIdMappings() as $hash => $service_id) { + $this->serviceIdMapping[$hash] = $service_id; + } + } + } + /** * Gets the active install profile. * diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php index 28a24006dd4..0378dff3e7d 100644 --- a/core/lib/Drupal/Core/DrupalKernelInterface.php +++ b/core/lib/Drupal/Core/DrupalKernelInterface.php @@ -137,4 +137,9 @@ interface DrupalKernelInterface extends HttpKernelInterface, ContainerAwareInter */ public function loadLegacyIncludes(); + /** + * Get a mapping from service hashes to service IDs. + */ + public function getServiceIdMapping(); + } diff --git a/core/lib/Drupal/Core/Test/TestKernel.php b/core/lib/Drupal/Core/Test/TestKernel.php index 124dff402b2..cf7ff4e723f 100644 --- a/core/lib/Drupal/Core/Test/TestKernel.php +++ b/core/lib/Drupal/Core/Test/TestKernel.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Test; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DrupalKernel; /** @@ -22,4 +23,23 @@ class TestKernel extends DrupalKernel { parent::__construct($environment, $class_loader, $allow_dumping); } + /** + * Sets a container with a kernel service on the Drupal class. + * + * @return \Drupal\Component\DependencyInjection\ContainerInterface + * A container with the kernel service set. + */ + public static function setContainerWithKernel() { + $container = new ContainerBuilder(); + $kernel = new DrupalKernel('test', NULL); + // Objects of the same type will have access to each others private and + // protected members even though they are not the same instances. This is + // because the implementation specific details are already known when + // inside those objects. + $kernel->container = $container; + $container->set('kernel', $kernel); + \Drupal::setContainer($container); + return $container; + } + } diff --git a/core/modules/system/tests/src/Kernel/DecoratedServiceTest.php b/core/modules/system/tests/src/Kernel/DecoratedServiceTest.php index 854662df18b..099019da517 100644 --- a/core/modules/system/tests/src/Kernel/DecoratedServiceTest.php +++ b/core/modules/system/tests/src/Kernel/DecoratedServiceTest.php @@ -22,12 +22,16 @@ class DecoratedServiceTest extends KernelTestBase { public function testDecoratedServiceId() { // Service decorated once. $test_service = $this->container->get('test_service'); - $this->assertEquals('test_service', $test_service->_serviceId); + $hash = $this->container->generateServiceIdHash($test_service); + $mappings = $this->container->getServiceIdMappings(); + $this->assertEquals('test_service', $mappings[$hash]); $this->assertInstanceOf(TestServiceDecorator::class, $test_service); // Service decorated twice. $test_service2 = $this->container->get('test_service2'); - $this->assertEquals('test_service2', $test_service2->_serviceId); + $hash = $this->container->generateServiceIdHash($test_service2); + $mappings = $this->container->getServiceIdMappings(); + $this->assertEquals('test_service2', $mappings[$hash]); $this->assertInstanceOf(TestServiceDecorator::class, $test_service2); } diff --git a/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php b/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php index 480b679537d..17f93a4777d 100644 --- a/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php +++ b/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php @@ -114,10 +114,6 @@ class ViewUIObjectTest extends UnitTestCase { * Tests serialization of the ViewUI object. */ public function testSerialization() { - // Set a container so the DependencySerializationTrait has it. - $container = new ContainerBuilder(); - \Drupal::setContainer($container); - $storage = new View([], 'view'); $executable = $this->getMockBuilder('Drupal\views\ViewExecutable') ->disableOriginalConstructor() diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeTest.php new file mode 100644 index 00000000000..6b47489844f --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeTest.php @@ -0,0 +1,64 @@ + 'example_entity_type', + ]; + return new EntityType($definition); + } + + /** + * Tests that the EntityType object can be serialized. + */ + public function testIsSerializable() { + $entity_type = $this->setUpEntityType([]); + + $translation_service = new class () extends TranslationManager { + + /** + * Constructs a UnserializableTranslationManager object. + */ + public function __construct() { + } + + /** + * @return array + */ + public function __serialize(): array { + throw new \Exception(); + } + + }; + + $this->container->set('bar', $translation_service); + $entity_type->setStringTranslation($this->container->get('string_translation')); + + // This should not throw an exception. + $tmp = serialize($entity_type); + $entity_type = unserialize($tmp); + // And this should have the correct id. + $this->assertEquals('example_entity_type', $entity_type->id()); + } + +} diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php index eeb933e0d0a..05609018883 100644 --- a/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php +++ b/core/tests/Drupal/Tests/Component/DependencyInjection/ContainerTest.php @@ -30,7 +30,7 @@ class ContainerTest extends TestCase { /** * The tested container. * - * @var \Symfony\Component\DependencyInjection\ContainerInterface + * @var \Drupal\Component\DependencyInjection\ContainerInterface */ protected $container; @@ -685,6 +685,20 @@ class ContainerTest extends TestCase { $this->assertEquals(['ccc'], $this->container->get('service_with_raw_argument')->getArguments()); } + /** + * @covers \Drupal\Component\DependencyInjection\ServiceIdHashTrait::getServiceIdMappings + * @covers \Drupal\Component\DependencyInjection\ServiceIdHashTrait::generateServiceIdHash + */ + public function testGetServiceIdMappings() { + $this->assertEquals([], $this->container->getServiceIdMappings()); + $s1 = $this->container->get('other.service'); + $s2 = $this->container->get('late.service'); + $this->assertEquals([ + $this->container->generateServiceIdHash($s1) => 'other.service', + $this->container->generateServiceIdHash($s2) => 'late.service', + ], $this->container->getServiceIdMappings()); + } + /** * Gets a mock container definition. * diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php index 345f4401985..312dcb87d94 100644 --- a/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php +++ b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php @@ -10,6 +10,7 @@ namespace Drupal\Tests\Component\DependencyInjection\Dumper { use Drupal\Component\Utility\Crypt; use Drupal\Tests\PhpUnitCompatibilityTrait; use PHPUnit\Framework\TestCase; + use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Parameter; @@ -26,6 +27,7 @@ namespace Drupal\Tests\Component\DependencyInjection\Dumper { class OptimizedPhpArrayDumperTest extends TestCase { use PhpUnitCompatibilityTrait; + use ExpectDeprecationTrait; /** * The container builder instance. @@ -346,16 +348,6 @@ namespace Drupal\Tests\Component\DependencyInjection\Dumper { 'arguments_expected' => $this->getCollection([$this->getParameterCall('llama_parameter')]), ] + $base_service_definition; - // Test objects that have _serviceId property. - $drupal_service = new \stdClass(); - $drupal_service->_serviceId = 'bar'; - - $service_definitions[] = [ - 'arguments' => [$drupal_service], - 'arguments_count' => 1, - 'arguments_expected' => $this->getCollection([$this->getServiceCall('bar')]), - ] + $base_service_definition; - // Test getMethodCalls. $calls = [ ['method', $this->getCollection([])], @@ -589,6 +581,37 @@ namespace Drupal\Tests\Component\DependencyInjection\Dumper { $this->dumper->getArray(); } + /** + * Tests that the correct RuntimeException is thrown for dumping an object. + * + * @covers ::dumpValue + * @group legacy + */ + public function testGetServiceDefinitionForObjectServiceId() { + $service = new \stdClass(); + $service->_serviceId = 'foo'; + + $services['foo'] = new Definition('\stdClass'); + $services['bar'] = new Definition('\stdClass'); + $services['bar']->addArgument($service); + foreach ($services as $s) { + $s->setPublic(TRUE); + } + + $this->containerBuilder->getDefinitions()->willReturn($services); + $this->containerBuilder->getDefinition('foo')->willReturn($services['foo']); + $this->containerBuilder->getDefinition('bar')->willReturn($services['bar']); + $this->expectDeprecation('_serviceId is deprecated in drupal:9.5.0 and is removed from drupal:11.0.0. Use \Drupal\Core\DrupalKernelInterface::getServiceIdMapping() instead. See https://www.drupal.org/node/3292540'); + $a = $this->dumper->getArray(); + $this->assertEquals( + $this->serializeDefinition([ + 'class' => '\stdClass', + // Legacy code takes care of converting _serviceId into this. + 'arguments' => $this->getCollection([$this->getServiceCall('foo')]), + 'arguments_count' => 1, + ]), $a['services']['bar']); + } + /** * Tests that the correct RuntimeException is thrown for dumping a resource. * diff --git a/core/tests/Drupal/Tests/Component/DrupalComponentTest.php b/core/tests/Drupal/Tests/Component/DrupalComponentTest.php index 74cd216fe55..11855926a64 100644 --- a/core/tests/Drupal/Tests/Component/DrupalComponentTest.php +++ b/core/tests/Drupal/Tests/Component/DrupalComponentTest.php @@ -97,8 +97,8 @@ class DrupalComponentTest extends TestCase { $contents = file_get_contents($class_path); preg_match_all('/^.*Drupal\\\Core.*$/m', $contents, $matches); $matches = array_filter($matches[0], function ($line) { - // Filter references to @see as they don't really matter. - return strpos($line, '@see') === FALSE; + // Filter references that don't really matter. + return preg_match('/@see|E_USER_DEPRECATED|expectDeprecation/', $line) === 0; }); $this->assertEmpty($matches, "Checking for illegal reference to 'Drupal\\Core' namespace in $class_path"); } diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php index ccdd71975a3..b54150a0a98 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php @@ -15,6 +15,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Language\Language; use Drupal\Core\Plugin\DefaultLazyPluginCollection; +use Drupal\Core\Test\TestKernel; use Drupal\Tests\Core\Config\Entity\Fixtures\ConfigEntityBaseWithPluginCollections; use Drupal\Tests\Core\Plugin\Fixtures\TestConfigurablePlugin; use Drupal\Tests\UnitTestCase; @@ -357,9 +358,8 @@ class ConfigEntityBaseUnitTest extends UnitTestCase { // Also set up a container with the plugin manager so that we can assert // that the plugin manager itself is also not serialized. - $container = new ContainerBuilder(); - $container->set('plugin.manager.foo', $plugin_manager); - \Drupal::setContainer($container); + $container = TestKernel::setContainerWithKernel(); + $container->set('plugin.manager.foo', $plugin_manager->reveal()); $entity_values = ['the_plugin_collection_config' => [$instance_id => ['foo' => 'original_value']]]; $entity = new TestConfigEntityWithPluginCollections($entity_values, $this->entityTypeId); diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/ContainerBuilderTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/ContainerBuilderTest.php index f8b82e2fb78..6f61bbca0a6 100644 --- a/core/tests/Drupal/Tests/Core/DependencyInjection/ContainerBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/ContainerBuilderTest.php @@ -24,16 +24,6 @@ class ContainerBuilderTest extends UnitTestCase { $this->assertInstanceOf(BarClass::class, $result); } - /** - * @covers ::set - */ - public function testSet() { - $container = new ContainerBuilder(); - $class = new BarClass(); - $container->set('bar', $class); - $this->assertEquals('bar', $class->_serviceId); - } - /** * @covers ::set */ diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/ContainerTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/ContainerTest.php index df86cddc0f0..2d7bd078b16 100644 --- a/core/tests/Drupal/Tests/Core/DependencyInjection/ContainerTest.php +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/ContainerTest.php @@ -4,7 +4,6 @@ namespace Drupal\Tests\Core\DependencyInjection; use Drupal\Core\DependencyInjection\Container; use Drupal\Tests\UnitTestCase; -use Drupal\Tests\Core\DependencyInjection\Fixture\BarClass; /** * @coversDefaultClass \Drupal\Core\DependencyInjection\Container @@ -21,15 +20,4 @@ class ContainerTest extends UnitTestCase { serialize($container); } - /** - * @covers ::set - */ - public function testSet() { - $container = new Container(); - $class = new BarClass(); - $container->set('bar', $class); - // Ensure that _serviceId is set on the object. - $this->assertEquals('bar', $class->_serviceId); - } - } diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTest.php index 6c2446e8f55..22f4d53a96f 100644 --- a/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTest.php +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/DependencySerializationTest.php @@ -7,8 +7,8 @@ namespace Drupal\Tests\Core\DependencyInjection; -use Drupal\Core\DependencyInjection\Container; use Drupal\Core\DependencyInjection\DependencySerializationTrait; +use Drupal\Core\Test\TestKernel; use Drupal\Tests\UnitTestCase; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -26,11 +26,9 @@ class DependencySerializationTest extends UnitTestCase { public function testSerialization() { // Create a pseudo service and dependency injected object. $service = new \stdClass(); - $service->_serviceId = 'test_service'; - $container = new Container(); + $container = TestKernel::setContainerWithKernel(); $container->set('test_service', $service); - $container->set('service_container', $container); - \Drupal::setContainer($container); + $this->assertSame($container, $container->get('service_container')); $dependencySerialization = new DependencySerializationTestDummy($service); $dependencySerialization->setContainer($container); @@ -44,29 +42,6 @@ class DependencySerializationTest extends UnitTestCase { $this->assertEmpty($dependencySerialization->getServiceIds()); } - /** - * @covers ::__sleep - * @covers ::__wakeup - */ - public function testSerializationWithMissingService() { - // Create a pseudo service and dependency injected object. - $service = new \stdClass(); - $service->_serviceId = 'test_service_not_existing'; - $container = new Container(); - $container->set('test_service', $service); - $container->set('service_container', $container); - \Drupal::setContainer($container); - - $dependencySerialization = new DependencySerializationTestDummy($service); - $dependencySerialization->setContainer($container); - - $string = serialize($dependencySerialization); - /** @var \Drupal\Tests\Core\DependencyInjection\DependencySerializationTestDummy $dependencySerialization */ - $dependencySerialization = unserialize($string); - - $this->assertSame($container, $dependencySerialization->container); - } - } /** diff --git a/core/tests/Drupal/Tests/Core/DrupalKernel/DrupalKernelTest.php b/core/tests/Drupal/Tests/Core/DrupalKernel/DrupalKernelTest.php index ac30f40b6ff..b94cf7ec05c 100644 --- a/core/tests/Drupal/Tests/Core/DrupalKernel/DrupalKernelTest.php +++ b/core/tests/Drupal/Tests/Core/DrupalKernel/DrupalKernelTest.php @@ -3,6 +3,8 @@ namespace Drupal\Tests\Core\DrupalKernel; use Drupal\Core\DrupalKernel; +use Drupal\Core\Test\TestKernel; +use Drupal\Tests\Core\DependencyInjection\Fixture\BarClass; use Drupal\Tests\UnitTestCase; use org\bovigo\vfs\vfsStream; use Symfony\Component\HttpFoundation\Request; @@ -136,6 +138,16 @@ EOD; $this->assertEquals('sites/example', DrupalKernel::findSitePath($request, FALSE, $vfs_root->url('drupal_root'))); } + /** + * @covers ::getServiceIdMapping + */ + public function testGetServiceIdMapping() { + $service = new BarClass(); + $container = TestKernel::setContainerWithKernel(); + $container->set('bar', $service); + $this->assertEquals($container->get('kernel')->getServiceIdMapping()[$container->generateServiceIdHash($service)], 'bar'); + } + } /** diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php index 3ffe645995c..704679ba66d 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php @@ -7,7 +7,6 @@ use Drupal\Core\Entity\Entity\EntityFormMode; use Drupal\Core\Entity\EntityType; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; -use Drupal\Core\StringTranslation\TranslationManager; use Drupal\Tests\UnitTestCase; /** @@ -512,39 +511,4 @@ class EntityTypeTest extends UnitTestCase { $this->assertFalse($entity_type->isSubclassOf(\DateTimeInterface::class)); } - /** - * Tests that the EntityType object can be serialized. - */ - public function testIsSerializable() { - $entity_type = $this->setUpEntityType([]); - - $translation_service = new UnserializableTranslationManager(); - $translation_service->_serviceId = 'string_translation'; - - $entity_type->setStringTranslation($translation_service); - $entity_type = unserialize(serialize($entity_type)); - - $this->assertEquals('example_entity_type', $entity_type->id()); - } - -} - -/** - * Test class. - */ -class UnserializableTranslationManager extends TranslationManager { - - /** - * Constructs a UnserializableTranslationManager object. - */ - public function __construct() { - } - - /** - * @return array - */ - public function __serialize(): array { - throw new \Exception(); - } - } diff --git a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php index 075cd51d92f..0863c064af6 100644 --- a/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Plugin/ContextHandlerTest.php @@ -16,11 +16,11 @@ use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Component\Plugin\Exception\MissingValueContextException; use Drupal\Core\Cache\NullBackend; use Drupal\Core\DependencyInjection\ClassResolverInterface; -use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\ContextHandler; use Drupal\Core\Plugin\ContextAwarePluginInterface; +use Drupal\Core\Test\TestKernel; use Drupal\Core\TypedData\TypedDataManager; use Drupal\Core\Validation\ConstraintManager; use Drupal\Tests\UnitTestCase; @@ -63,7 +63,7 @@ class ContextHandlerTest extends UnitTestCase { new ConstraintManager($namespaces, $cache_backend, $module_handler->reveal()) ); - $container = new ContainerBuilder(); + $container = TestKernel::setContainerWithKernel(); $container->set('typed_data_manager', $type_data_manager); \Drupal::setContainer($container); } diff --git a/core/tests/Drupal/Tests/Core/TempStore/SharedTempStoreTest.php b/core/tests/Drupal/Tests/Core/TempStore/SharedTempStoreTest.php index 97be1742bc1..900bac87363 100644 --- a/core/tests/Drupal/Tests/Core/TempStore/SharedTempStoreTest.php +++ b/core/tests/Drupal/Tests/Core/TempStore/SharedTempStoreTest.php @@ -7,11 +7,11 @@ use Drupal\Core\Http\RequestStack; use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface; use Drupal\Core\Session\AccountProxyInterface; use Drupal\Core\TempStore\Lock; +use Drupal\Core\Test\TestKernel; use Drupal\Core\TempStore\SharedTempStoreFactory; use Drupal\Tests\UnitTestCase; use Drupal\Core\TempStore\SharedTempStore; use Drupal\Core\TempStore\TempStoreException; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; @@ -360,12 +360,10 @@ class SharedTempStoreTest extends UnitTestCase { $unserializable_request = new UnserializableRequest(); $this->requestStack->push($unserializable_request); - $this->requestStack->_serviceId = 'request_stack'; - $container = $this->prophesize(ContainerInterface::class); - $container->get('request_stack')->willReturn($this->requestStack); - $container->has('request_stack')->willReturn(TRUE); - \Drupal::setContainer($container->reveal()); + $container = TestKernel::setContainerWithKernel(); + $container->set('request_stack', $this->requestStack); + \Drupal::setContainer($container); $store = unserialize(serialize($this->tempStore)); $this->assertInstanceOf(SharedTempStore::class, $store);