Issue #2190643 by Berdir, amateescu, Xano, yched: Serializing the container is a very very bad idea, let's prevent it?.

8.0.x
Alex Pott 2014-02-11 09:42:30 +00:00
parent 42ea3f048f
commit 1cce8991af
11 changed files with 136 additions and 15 deletions

View File

@ -11,6 +11,7 @@ use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\String;
use Drupal\Core\Config\ConfigNameException;
use Drupal\Core\Config\Schema\SchemaIncompleteException;
use Drupal\Core\DependencyInjection\DependencySerialization;
use Drupal\Core\TypedData\PrimitiveInterface;
use Drupal\Core\TypedData\Type\FloatInterface;
use Drupal\Core\TypedData\Type\IntegerInterface;
@ -20,7 +21,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Defines the default configuration object.
*/
class Config {
class Config extends DependencySerialization {
/**
* The maximum length of a configuration object name.

View File

@ -7,6 +7,7 @@
namespace Drupal\Core\Controller;
use Drupal\Core\DependencyInjection\DependencySerialization;
use Drupal\Core\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
@ -15,7 +16,7 @@ use Symfony\Component\HttpFoundation\Request;
*
* @todo Make this a trait in PHP 5.4.
*/
abstract class FormController {
abstract class FormController extends DependencySerialization {
/**
* The form definition. The format may vary depending on the child class.

View File

@ -27,4 +27,12 @@ class Container extends SymfonyContainer {
return $service;
}
/**
* {@inheritdoc}
*/
public function __sleep() {
trigger_error('The container was serialized.', E_USER_ERROR);
return array_keys(get_object_vars($this));
}
}

View File

@ -28,6 +28,19 @@ class ContainerBuilder extends SymfonyContainerBuilder {
public function addObjectResource($object) {
}
/**
* {@inheritdoc}
*/
public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) {
$service = parent::get($id, $invalidBehavior);
// Some services are called but do not exist, so the parent returns nothing.
if (is_object($service)) {
$service->_serviceId = $id;
}
return $service;
}
/**
* Overrides Symfony\Component\DependencyInjection\ContainerBuilder::set().
*
@ -89,4 +102,12 @@ class ContainerBuilder extends SymfonyContainerBuilder {
call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->resolveValue($call[1])));
}
/**
* {@inheritdoc}
*/
public function __sleep() {
trigger_error('The container was serialized.', E_USER_ERROR);
return array_keys(get_object_vars($this));
}
}

View File

@ -6,6 +6,7 @@
*/
namespace Drupal\Core\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a dependency injection friendly methods for serialization.
@ -33,6 +34,11 @@ abstract class DependencySerialization {
$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]);
}
}
return array_keys($vars);

View File

@ -7,6 +7,7 @@
namespace Drupal\Core\Entity;
use Drupal\Core\DependencyInjection\DependencySerialization;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
@ -15,7 +16,7 @@ use Drupal\Core\StringTranslation\TranslationInterface;
*
* @todo Convert this to a trait.
*/
abstract class EntityControllerBase {
abstract class EntityControllerBase extends DependencySerialization {
/**
* The translation manager service.

View File

@ -8,12 +8,13 @@
namespace Drupal\Core\Language;
use Drupal\Component\Utility\String;
use Drupal\Core\DependencyInjection\DependencySerialization;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Class responsible for providing language support on language-unaware sites.
*/
class LanguageManager implements LanguageManagerInterface {
class LanguageManager extends DependencySerialization implements LanguageManagerInterface {
/**
* The string translation service.

View File

@ -9,12 +9,23 @@ namespace Drupal\Core\Plugin;
use Drupal\Component\Plugin\PluginBase as ComponentPluginBase;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for plugins supporting metadata inspection and translation.
*/
abstract class PluginBase extends ComponentPluginBase {
/**
* An array of service IDs keyed by property name used for serialization.
*
* @todo Remove when Drupal\Core\DependencyInjection\DependencySerialization
* is converted to a trait.
*
* @var array
*/
protected $_serviceIds = array();
/**
* The translation manager service.
*
@ -58,4 +69,45 @@ abstract class PluginBase extends ComponentPluginBase {
return $this;
}
/**
* {@inheritdoc}
*
* @todo Remove when Drupal\Core\DependencyInjection\DependencySerialization
* is converted to a trait.
*/
public function __sleep() {
$this->_serviceIds = array();
$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]);
}
}
return array_keys($vars);
}
/**
* {@inheritdoc}
*
* @todo Remove when Drupal\Core\DependencyInjection\DependencySerialization
* is converted to a trait.
*/
public function __wakeup() {
$container = \Drupal::getContainer();
foreach ($this->_serviceIds as $key => $service_id) {
$this->$key = $container->get($service_id);
}
unset($this->_serviceIds);
}
}

View File

@ -7,6 +7,7 @@
namespace Drupal\views;
use Drupal\Core\DependencyInjection\DependencySerialization;
use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ViewStorageInterface;
@ -25,7 +26,7 @@ use Symfony\Component\HttpFoundation\Response;
* An object to contain all of the data to generate a view, plus the member
* functions to build the view query, execute the query and render the output.
*/
class ViewExecutable {
class ViewExecutable extends DependencySerialization {
/**
* The config entity in which the view is stored.

View File

@ -14,7 +14,7 @@ use Symfony\Component\DependencyInjection\Reference;
require_once __DIR__ . '../../../../../../vendor/symfony/dependency-injection/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php';
/**
* Dependency injection container builder.
* Tests the dependency injection container builder overrides of Drupal.
*
* @see \Drupal\Core\DependencyInjection\ContainerBuilder
*/
@ -48,4 +48,18 @@ class ContainerBuilderTest extends UnitTestCase {
$this->assertSame($baz, $container->get('bar')->getBaz());
}
/**
* Tests the get method.
*
* @see \Drupal\Core\DependencyInjection\Container::get()
*/
public function testGet() {
$container = new ContainerBuilder();
$container->register('bar', 'BarClass');
$result = $container->get('bar');
$this->assertTrue($result instanceof \BarClass);
$this->assertEquals('bar', $result->_serviceId);
}
}

View File

@ -7,9 +7,11 @@
namespace Drupal\Tests\Core\DependencyInjection;
use Drupal\Core\DependencyInjection\Container;
use Drupal\Core\DependencyInjection\DependencySerialization;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Tests the dependency serialization base class.
@ -36,20 +38,19 @@ class DependencySerializationTest extends UnitTestCase {
// Create a pseudo service and dependency injected object.
$service = new \stdClass();
$service->_serviceId = 'test_service';
$container = $this->getMock('Drupal\Core\DependencyInjection\Container');
$container->expects($this->exactly(1))
->method('get')
->with('test_service')
->will($this->returnValue($service));
$container = new Container();
$container->set('test_service', $service);
$container->set('service_container', $container);
\Drupal::setContainer($container);
$dependencySerialization = new TestClass($service);
$dependencySerialization->setContainer($container);
$string = serialize($dependencySerialization);
$object = unserialize($string);
// The original object got _serviceIds added so let's remove it to check
// equality
// The original object got _serviceIds added so removing it to check
// equality.
unset($dependencySerialization->_serviceIds);
// Ensure dependency injected object remains the same after serialization.
@ -61,6 +62,7 @@ class DependencySerializationTest extends UnitTestCase {
// Ensure that both the service and the variable are in the unserialized
// object.
$this->assertSame($service, $object->service);
$this->assertSame($container, $object->container);
}
}
@ -68,7 +70,7 @@ class DependencySerializationTest extends UnitTestCase {
/**
* Defines a test class which has a single service as dependency.
*/
class TestClass extends DependencySerialization {
class TestClass extends DependencySerialization implements ContainerAwareInterface {
/**
* A test service.
@ -77,6 +79,13 @@ class TestClass extends DependencySerialization {
*/
public $service;
/**
* The container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
public $container;
/**
* {@inheritdoc}
*
@ -94,4 +103,10 @@ class TestClass extends DependencySerialization {
$this->service = $service;
}
/**
* {@inheritdoc}
*/
public function setContainer(ContainerInterface $container = NULL) {
$this->container = $container;
}
}