Commit 4884fb15 authored by catch's avatar catch

Issue #2849674 by Lendude, mxh, tacituseu, podarok, andypost, pingwin4eg,...

Issue #2849674 by Lendude, mxh, tacituseu, podarok, andypost, pingwin4eg, axel.rutz, catch: Complex job in ViewExecutable::unserialize() causes data corruption
parent 98c68fdb
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Tags; use Drupal\Component\Utility\Tags;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\views\display\DisplayRouterInterface; use Drupal\views\Plugin\views\display\DisplayRouterInterface;
...@@ -17,9 +16,14 @@ ...@@ -17,9 +16,14 @@
* *
* An object to contain all of the data to generate a view, plus the member * 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. * functions to build the view query, execute the query and render the output.
*
* This class does not implement the Serializable interface since problems
* occurred when using the serialize method.
*
* @see https://www.drupal.org/node/2849674
* @see https://bugs.php.net/bug.php?id=66052
*/ */
class ViewExecutable implements \Serializable { class ViewExecutable {
use DependencySerializationTrait;
/** /**
* The config entity in which the view is stored. * The config entity in which the view is stored.
...@@ -434,6 +438,13 @@ class ViewExecutable implements \Serializable { ...@@ -434,6 +438,13 @@ class ViewExecutable implements \Serializable {
*/ */
protected $baseEntityType; protected $baseEntityType;
/**
* Holds all necessary data for proper unserialization.
*
* @var array
*/
protected $serializationData;
/** /**
* Constructs a new ViewExecutable object. * Constructs a new ViewExecutable object.
* *
...@@ -2466,52 +2477,68 @@ public function getDependencies() { ...@@ -2466,52 +2477,68 @@ public function getDependencies() {
} }
/** /**
* {@inheritdoc} * Magic method implementation to serialize the view executable.
*/ *
public function serialize() { * @return array
return serialize([ * The names of all variables that should be serialized.
// Only serialize the storage entity ID. */
$this->storage->id(), public function __sleep() {
$this->current_display, // Limit to only the required data which is needed to properly restore the
$this->args, // state during unserialization.
$this->current_page, $this->serializationData = [
$this->exposed_input, 'storage' => $this->storage->id(),
$this->exposed_raw_input, 'views_data' => $this->viewsData->_serviceId,
$this->exposed_data, 'route_provider' => $this->routeProvider->_serviceId,
$this->dom_id, 'current_display' => $this->current_display,
$this->executed, 'args' => $this->args,
]); 'current_page' => $this->current_page,
'exposed_input' => $this->exposed_input,
'exposed_raw_input' => $this->exposed_raw_input,
'exposed_data' => $this->exposed_data,
'dom_id' => $this->dom_id,
'executed' => $this->executed,
];
return ['serializationData'];
} }
/** /**
* {@inheritdoc} * Magic method implementation to unserialize the view executable.
*/ */
public function unserialize($serialized) { public function __wakeup() {
list($storage, $current_display, $args, $current_page, $exposed_input, $exposed_raw_input, $exposed_data, $dom_id, $executed) = unserialize($serialized); // There are cases, like in testing where we don't have a container
// There are cases, like in testing, where we don't have a container
// available. // available.
if (\Drupal::hasContainer()) { if (\Drupal::hasContainer() && !empty($this->serializationData)) {
$this->setRequest(\Drupal::request()); // Load and reference the storage.
$this->user = \Drupal::currentUser(); $this->storage = \Drupal::entityTypeManager()->getStorage('view')
->load($this->serializationData['storage']);
$this->storage->set('executable', $this);
$this->storage = \Drupal::entityManager()->getStorage('view')->load($storage); // Attach all necessary services.
$this->user = \Drupal::currentUser();
$this->viewsData = \Drupal::service($this->serializationData['views_data']);
$this->routeProvider = \Drupal::service($this->serializationData['route_provider']);
$this->setDisplay($current_display); // Restore the state of this executable.
$this->setArguments($args); if ($request = \Drupal::request()) {
$this->setCurrentPage($current_page); $this->setRequest($request);
$this->setExposedInput($exposed_input); }
$this->exposed_data = $exposed_data; $this->setDisplay($this->serializationData['current_display']);
$this->exposed_raw_input = $exposed_raw_input; $this->setArguments($this->serializationData['args']);
$this->dom_id = $dom_id; $this->setCurrentPage($this->serializationData['current_page']);
$this->setExposedInput($this->serializationData['exposed_input']);
$this->exposed_data = $this->serializationData['exposed_data'];
$this->exposed_raw_input = $this->serializationData['exposed_raw_input'];
$this->dom_id = $this->serializationData['dom_id'];
$this->initHandlers(); $this->initHandlers();
// If the display was previously executed, execute it now. // If the display was previously executed, execute it now.
if ($executed) { if ($this->serializationData['executed']) {
$this->execute($this->current_display); $this->execute($this->current_display);
} }
} }
// Unset serializationData since it serves no further purpose.
unset($this->serializationData);
} }
} }
...@@ -487,6 +487,37 @@ public function testSerialization() { ...@@ -487,6 +487,37 @@ public function testSerialization() {
$this->assertIdentical($unserialized->current_display, 'page_1', 'The expected display was set on the unserialized view.'); $this->assertIdentical($unserialized->current_display, 'page_1', 'The expected display was set on the unserialized view.');
$this->assertIdentical($unserialized->args, ['test'], 'The expected argument was set on the unserialized view.'); $this->assertIdentical($unserialized->args, ['test'], 'The expected argument was set on the unserialized view.');
$this->assertIdentical($unserialized->getCurrentPage(), 2, 'The expected current page was set on the unserialized view.'); $this->assertIdentical($unserialized->getCurrentPage(), 2, 'The expected current page was set on the unserialized view.');
// Get the definition of node's nid field, for example. Only get it not from
// the field manager directly, but from the item data definition. It should
// be the same base field definition object (the field and item definitions
// refer to each other).
// See https://bugs.php.net/bug.php?id=66052
$field_manager = $this->container->get('entity_field.manager');
$nid_definition_before = $field_manager->getBaseFieldDefinitions('node')['nid']
->getItemDefinition()
->getFieldDefinition();
// Load and execute a view.
$view_entity = View::load('content');
$view_executable = $view_entity->getExecutable();
$view_executable->execute('page_1');
// Reset the static cache. Don't use clearCachedFieldDefinitions() since
// that clears the persistent cache and we need to get the serialized cache
// data.
$field_manager->useCaches(FALSE);
$field_manager->useCaches(TRUE);
// Serialize the ViewExecutable as part of other data.
unserialize(serialize(['SOMETHING UNEXPECTED', $view_executable]));
// Make sure the serialisation of the ViewExecutable didn't influence the
// field definitions.
$nid_definition_after = $field_manager->getBaseFieldDefinitions('node')['nid']
->getItemDefinition()
->getFieldDefinition();
$this->assertEquals($nid_definition_before->getPropertyDefinitions(), $nid_definition_after->getPropertyDefinitions());
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment