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
parent
35ba1d5ce7
commit
667d8b008f
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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.');
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -137,4 +137,9 @@ interface DrupalKernelInterface extends HttpKernelInterface, ContainerAwareInter
|
|||
*/
|
||||
public function loadLegacyIncludes();
|
||||
|
||||
/**
|
||||
* Get a mapping from service hashes to service IDs.
|
||||
*/
|
||||
public function getServiceIdMapping();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue