Issue #2531564 by neclimdul, andypost, chx, cburschka, Spokje, pdenooijer, Charlie ChX Negyesi, znerol, dawehner, longwave, Fabianx, geek-merlin, catch, alexpott: Fix leaky and brittle container serialization solution

merge-requests/2760/head
catch 2022-09-13 09:52:09 +01:00
parent 35ba1d5ce7
commit 667d8b008f
28 changed files with 359 additions and 205 deletions

View File

@ -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
*/

View File

@ -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);

View File

@ -0,0 +1,42 @@
<?php
namespace Drupal\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerInterface as BaseContainerInterface;
/**
* The interface for Drupal service container classes.
*
* This interface extends Symfony's ContainerInterface and adds methods for
* managing mappings of instantiated services to its IDs.
*/
interface ContainerInterface extends BaseContainerInterface {
/**
* Gets all defined service IDs.
*
* @return array
* An array of all defined service IDs.
*/
public function getServiceIds();
/**
* Collect a mapping between service to ids.
*
* @return array
* Service ids keyed by a unique hash.
*/
public function getServiceIdMappings(): array;
/**
* Generate a unique hash for a service object.
*
* @param object $object
* Object needing a unique hash.
*
* @return string
* A unique hash identifying the object.
*/
public function generateServiceIdHash(object $object): string;
}

View File

@ -433,6 +433,7 @@ class OptimizedPhpArrayDumper extends Dumper {
elseif (is_object($value)) {
// Drupal specific: Instantiated objects have a _serviceId parameter.
if (isset($value->_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.');

View File

@ -0,0 +1,37 @@
<?php
namespace Drupal\Component\DependencyInjection;
/**
* A trait for service id hashing implementations.
*
* Handles delayed cache tag invalidations.
*/
trait ServiceIdHashTrait {
/**
* Implements \Drupal\Component\DependencyInjection\ContainerInterface::getServiceIdMappings()
*/
public function getServiceIdMappings(): array {
$mapping = [];
foreach ($this->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));
}
}

View File

@ -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();
}
}

View File

@ -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());
}

View File

@ -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);
}
}
}
}

View File

@ -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}
*/

View File

@ -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;
}
}
/**

View File

@ -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);
}

View File

@ -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.
*

View File

@ -137,4 +137,9 @@ interface DrupalKernelInterface extends HttpKernelInterface, ContainerAwareInter
*/
public function loadLegacyIncludes();
/**
* Get a mapping from service hashes to service IDs.
*/
public function getServiceIdMapping();
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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()

View File

@ -0,0 +1,64 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Entity\EntityType;
use Drupal\Core\StringTranslation\TranslationManager;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests general features of entity types.
*
* @group Entity
*/
class EntityTypeTest extends KernelTestBase {
/**
* Sets up an EntityType object for a given set of values.
*
* @param array $definition
* An array of values to use for the EntityType.
*
* @return \Drupal\Core\Entity\EntityTypeInterface
*/
protected function setUpEntityType($definition) {
$definition += [
'id' => '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());
}
}

View File

@ -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.
*

View File

@ -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.
*

View File

@ -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");
}

View File

@ -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);

View File

@ -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
*/

View File

@ -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);
}
}

View File

@ -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);
}
}
/**

View File

@ -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');
}
}
/**

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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);