From 577b6900b9e1471615ec6ba2beeae808ee476b27 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Mon, 5 Feb 2024 11:38:05 +0000
Subject: [PATCH] Issue #839444 by mkalkbrenner, andypost, gnunes, hchonov,
 voleger, claudiu.cristea, keichee, gease, kfritsche, wells, sokru,
 tstoeckler, quietone, ravi.shankar, Damien Tournoud, Berdir, Fabianx, catch,
 Wim Leers, larowlan, kristiaanvandeneynde, twistor, daffie, alexpott, sun:
 Make serializer customizable for Cache\DatabaseBackend

---
 core/core.services.yml                        |  3 ++-
 .../ObjectAwareSerializationInterface.php     | 18 +++++++++++++
 .../Component/Serialization/PhpSerialize.php  |  2 +-
 .../lib/Drupal/Core/Cache/DatabaseBackend.php | 26 ++++++++++++++++---
 .../Core/Cache/DatabaseBackendFactory.php     | 23 +++++++++-------
 core/lib/Drupal/Core/DrupalKernel.php         | 12 ++++++++-
 .../tests/src/Kernel/mysql/DbDumpTest.php     |  3 ++-
 .../Core/Cache/ChainedFastBackendTest.php     |  2 +-
 .../Core/Cache/DatabaseBackendTest.php        |  2 +-
 .../Cache/EndOfTransactionQueriesTest.php     |  5 +++-
 .../Core/Cache/DatabaseBackendFactoryTest.php |  4 ++-
 11 files changed, 79 insertions(+), 21 deletions(-)
 create mode 100644 core/lib/Drupal/Component/Serialization/ObjectAwareSerializationInterface.php

diff --git a/core/core.services.yml b/core/core.services.yml
index ef3aefb9d076..476b1443fbf5 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -229,7 +229,7 @@ services:
       - [setContainer, ['@service_container']]
   cache.backend.database:
     class: Drupal\Core\Cache\DatabaseBackendFactory
-    arguments: ['@database', '@cache_tags.invalidator.checksum', '@settings']
+    arguments: ['@database', '@cache_tags.invalidator.checksum', '@settings', '@serialization.phpserialize']
     tags:
       - { name: backend_overridable }
   cache.backend.apcu:
@@ -541,6 +541,7 @@ services:
     class: Drupal\Component\Serialization\Json
   serialization.phpserialize:
     class: Drupal\Component\Serialization\PhpSerialize
+  Drupal\Component\Serialization\ObjectAwareSerializationInterface: '@serialization.phpserialize'
   serialization.yaml:
     class: Drupal\Component\Serialization\Yaml
 
diff --git a/core/lib/Drupal/Component/Serialization/ObjectAwareSerializationInterface.php b/core/lib/Drupal/Component/Serialization/ObjectAwareSerializationInterface.php
new file mode 100644
index 000000000000..6177550363da
--- /dev/null
+++ b/core/lib/Drupal/Component/Serialization/ObjectAwareSerializationInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\Component\Serialization;
+
+// cspell:ignore serializers igbinary
+
+/**
+ * Ensures that a serializer is usable for serializing PHP objects.
+ *
+ * Other Serializers that implement the SerializationInterface, for example
+ * serializers that use JSON or YAML, are suitable for different PHP types
+ * except objects. Serializers that implement the
+ * ObjectAwareSerializationInterface instead are clearly indicating that they're
+ * suitable for PHP objects, for example using the PHP string serialization
+ * format or the igbinary format.
+ */
+interface ObjectAwareSerializationInterface extends SerializationInterface {
+}
diff --git a/core/lib/Drupal/Component/Serialization/PhpSerialize.php b/core/lib/Drupal/Component/Serialization/PhpSerialize.php
index 1f7e03d75b44..a5e47220cc57 100644
--- a/core/lib/Drupal/Component/Serialization/PhpSerialize.php
+++ b/core/lib/Drupal/Component/Serialization/PhpSerialize.php
@@ -5,7 +5,7 @@
 /**
  * Default serialization for serialized PHP.
  */
-class PhpSerialize implements SerializationInterface {
+class PhpSerialize implements ObjectAwareSerializationInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
index 29e4fe5c7b3e..7a36c5612c91 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Cache;
 
+use Drupal\Component\Serialization\ObjectAwareSerializationInterface;
 use Drupal\Component\Assertion\Inspector;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Database\Connection;
@@ -66,6 +67,13 @@ class DatabaseBackend implements CacheBackendInterface {
    */
   protected $checksumProvider;
 
+  /**
+   * The serializer to use.
+   *
+   * @var \Drupal\Component\Serialization\ObjectAwareSerializationInterface
+   */
+  protected ObjectAwareSerializationInterface $serializer;
+
   /**
    * Constructs a DatabaseBackend object.
    *
@@ -75,17 +83,29 @@ class DatabaseBackend implements CacheBackendInterface {
    *   The cache tags checksum provider.
    * @param string $bin
    *   The cache bin for which the object is created.
+   * @param \Drupal\Component\Serialization\ObjectAwareSerializationInterface|int|null $serializer
+   *   (optional) The serializer to use.
    * @param int $max_rows
    *   (optional) The maximum number of rows that are allowed in this cache bin
    *   table.
    */
-  public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, $bin, $max_rows = NULL) {
+  public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, $bin, ObjectAwareSerializationInterface|int $serializer = NULL, $max_rows = NULL) {
     // All cache tables should be prefixed with 'cache_'.
     $bin = 'cache_' . $bin;
 
     $this->bin = $bin;
     $this->connection = $connection;
     $this->checksumProvider = $checksum_provider;
+    if (is_int($serializer)) {
+      @trigger_error('Calling ' . __METHOD__ . ' with the $max_rows as 3rd argument is deprecated in drupal:10.3.0 and it will be the 4th argument in drupal:11.0.0. See https://www.drupal.org/node/3014684', E_USER_DEPRECATED);
+      $max_rows = $serializer;
+      $serializer = \Drupal::service('serialization.phpserialize');
+    }
+    elseif ($serializer === NULL) {
+      @trigger_error('Calling ' . __METHOD__ . ' without the $serializer argument is deprecated in drupal:10.3.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3014684', E_USER_DEPRECATED);
+      $serializer = \Drupal::service('serialization.phpserialize');
+    }
+    $this->serializer = $serializer;
     $this->maxRows = $max_rows === NULL ? static::DEFAULT_MAX_ROWS : $max_rows;
   }
 
@@ -169,7 +189,7 @@ protected function prepareItem($cache, $allow_invalid) {
 
     // Unserialize and return the cached data.
     if ($cache->serialized) {
-      $cache->data = unserialize($cache->data);
+      $cache->data = $this->serializer->decode($cache->data);
     }
 
     return $cache;
@@ -252,7 +272,7 @@ protected function doSetMultiple(array $items) {
         }
 
         if (!is_string($item['data'])) {
-          $fields['data'] = serialize($item['data']);
+          $fields['data'] = $this->serializer->encode($item['data']);
           $fields['serialized'] = 1;
         }
         else {
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
index 175639444c26..fd176e045ebe 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Cache;
 
+use Drupal\Component\Serialization\ObjectAwareSerializationInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Site\Settings;
 
@@ -21,13 +22,6 @@ class DatabaseBackendFactory implements CacheFactoryInterface {
    */
   protected $checksumProvider;
 
-  /**
-   * The site settings.
-   *
-   * @var \Drupal\Core\Site\Settings
-   */
-  protected $settings;
-
   /**
    * Constructs the DatabaseBackendFactory object.
    *
@@ -37,13 +31,22 @@ class DatabaseBackendFactory implements CacheFactoryInterface {
    *   The cache tags checksum provider.
    * @param \Drupal\Core\Site\Settings $settings
    *   (optional) The site settings.
+   * @param \Drupal\Component\Serialization\ObjectAwareSerializationInterface|null $serializer
+   *   (optional) The serializer to use.
    *
    * @throws \BadMethodCallException
    */
-  public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, Settings $settings = NULL) {
+  public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, protected ?Settings $settings = NULL, protected ?ObjectAwareSerializationInterface $serializer = NULL) {
     $this->connection = $connection;
     $this->checksumProvider = $checksum_provider;
-    $this->settings = $settings ?: Settings::getInstance();
+    if ($this->settings === NULL) {
+      @trigger_error('Calling ' . __METHOD__ . ' without the $settings argument is deprecated in drupal:10.3.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3014684', E_USER_DEPRECATED);
+      $this->settings = Settings::getInstance();
+    }
+    if ($this->serializer === NULL) {
+      @trigger_error('Calling ' . __METHOD__ . ' without the $serializer argument is deprecated in drupal:10.3.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3014684', E_USER_DEPRECATED);
+      $this->serializer = \Drupal::service('serialization.phpserialize');
+    }
   }
 
   /**
@@ -57,7 +60,7 @@ public function __construct(Connection $connection, CacheTagsChecksumInterface $
    */
   public function get($bin) {
     $max_rows = $this->getMaxRowsForBin($bin);
-    return new DatabaseBackend($this->connection, $this->checksumProvider, $bin, $max_rows);
+    return new DatabaseBackend($this->connection, $this->checksumProvider, $bin, $this->serializer, $max_rows);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 9a9a14f1ca31..c0bdc552582e 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -5,6 +5,7 @@
 use Composer\Autoload\ClassLoader;
 use Drupal\Component\EventDispatcher\Event;
 use Drupal\Component\FileCache\FileCacheFactory;
+use Drupal\Component\Serialization\PhpSerialize;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Cache\DatabaseBackend;
 use Drupal\Core\Config\BootstrapConfigStorageFactory;
@@ -76,12 +77,21 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
       ],
       'cache.container' => [
         'class' => 'Drupal\Core\Cache\DatabaseBackend',
-        'arguments' => ['@database', '@cache_tags_provider.container', 'container', DatabaseBackend::MAXIMUM_NONE],
+        'arguments' => [
+          '@database',
+          '@cache_tags_provider.container',
+          'container',
+          '@serialization.phpserialize',
+          DatabaseBackend::MAXIMUM_NONE,
+        ],
       ],
       'cache_tags_provider.container' => [
         'class' => 'Drupal\Core\Cache\DatabaseCacheTagsChecksum',
         'arguments' => ['@database'],
       ],
+      'serialization.phpserialize' => [
+        'class' => PhpSerialize::class,
+      ],
     ],
   ];
 
diff --git a/core/modules/mysql/tests/src/Kernel/mysql/DbDumpTest.php b/core/modules/mysql/tests/src/Kernel/mysql/DbDumpTest.php
index 2ff0f6a6a645..7e5c18a41b5c 100644
--- a/core/modules/mysql/tests/src/Kernel/mysql/DbDumpTest.php
+++ b/core/modules/mysql/tests/src/Kernel/mysql/DbDumpTest.php
@@ -74,7 +74,8 @@ public function register(ContainerBuilder $container) {
     $container->register('cache_factory', 'Drupal\Core\Cache\DatabaseBackendFactory')
       ->addArgument(new Reference('database'))
       ->addArgument(new Reference('cache_tags.invalidator.checksum'))
-      ->addArgument(new Reference('settings'));
+      ->addArgument(new Reference('settings'))
+      ->addArgument(new Reference('serialization.phpserialize'));
   }
 
   /**
diff --git a/core/tests/Drupal/KernelTests/Core/Cache/ChainedFastBackendTest.php b/core/tests/Drupal/KernelTests/Core/Cache/ChainedFastBackendTest.php
index a93b674d5cf0..1a84ddee0809 100644
--- a/core/tests/Drupal/KernelTests/Core/Cache/ChainedFastBackendTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Cache/ChainedFastBackendTest.php
@@ -20,7 +20,7 @@ class ChainedFastBackendTest extends GenericCacheBackendUnitTestBase {
    *   A new ChainedFastBackend object.
    */
   protected function createCacheBackend($bin) {
-    $consistent_backend = new DatabaseBackend(\Drupal::service('database'), \Drupal::service('cache_tags.invalidator.checksum'), $bin, 100);
+    $consistent_backend = new DatabaseBackend(\Drupal::service('database'), \Drupal::service('cache_tags.invalidator.checksum'), $bin, \Drupal::service('serialization.phpserialize'), 100);
     $fast_backend = new PhpBackend($bin, \Drupal::service('cache_tags.invalidator.checksum'));
     $backend = new ChainedFastBackend($consistent_backend, $fast_backend, $bin);
     // Explicitly register the cache bin as it can not work through the
diff --git a/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php b/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php
index 79d32d37167d..8a40e909a5fb 100644
--- a/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php
@@ -32,7 +32,7 @@ class DatabaseBackendTest extends GenericCacheBackendUnitTestBase {
    *   A new DatabaseBackend object.
    */
   protected function createCacheBackend($bin) {
-    return new DatabaseBackend($this->container->get('database'), $this->container->get('cache_tags.invalidator.checksum'), $bin, static::$maxRows);
+    return new DatabaseBackend($this->container->get('database'), $this->container->get('cache_tags.invalidator.checksum'), $bin, $this->container->get('serialization.phpserialize'), static::$maxRows);
   }
 
   /**
diff --git a/core/tests/Drupal/KernelTests/Core/Cache/EndOfTransactionQueriesTest.php b/core/tests/Drupal/KernelTests/Core/Cache/EndOfTransactionQueriesTest.php
index 83ea69e10f17..63ecff81f2e0 100644
--- a/core/tests/Drupal/KernelTests/Core/Cache/EndOfTransactionQueriesTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Cache/EndOfTransactionQueriesTest.php
@@ -10,6 +10,7 @@
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\user\Entity\User;
 use Symfony\Component\DependencyInjection\Reference;
+use Drupal\Component\Serialization\PhpSerialize;
 
 /**
  * Tests delaying of cache tag invalidation queries to the end of transactions.
@@ -47,11 +48,13 @@ protected function setUp(): void {
   public function register(ContainerBuilder $container) {
     parent::register($container);
 
+    $container->register('serializer', PhpSerialize::class);
     // Register a database cache backend rather than memory-based.
     $container->register('cache_factory', DatabaseBackendFactory::class)
       ->addArgument(new Reference('database'))
       ->addArgument(new Reference('cache_tags.invalidator.checksum'))
-      ->addArgument(new Reference('settings'));
+      ->addArgument(new Reference('settings'))
+      ->addArgument(new Reference('serializer'));
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Cache/DatabaseBackendFactoryTest.php b/core/tests/Drupal/Tests/Core/Cache/DatabaseBackendFactoryTest.php
index bf94caa009ea..a0ea25e7823a 100644
--- a/core/tests/Drupal/Tests/Core/Cache/DatabaseBackendFactoryTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/DatabaseBackendFactoryTest.php
@@ -4,6 +4,7 @@
 
 namespace Drupal\Tests\Core\Cache;
 
+use Drupal\Component\Serialization\PhpSerialize;
 use Drupal\Core\Cache\CacheTagsChecksumInterface;
 use Drupal\Core\Cache\DatabaseBackend;
 use Drupal\Core\Cache\DatabaseBackendFactory;
@@ -26,7 +27,8 @@ public function testGet(array $settings, $expected_max_rows_foo, $expected_max_r
     $database_backend_factory = new DatabaseBackendFactory(
       $this->prophesize(Connection::class)->reveal(),
       $this->prophesize(CacheTagsChecksumInterface::class)->reveal(),
-      new Settings($settings)
+      new Settings($settings),
+      new PhpSerialize()
     );
 
     $this->assertSame($expected_max_rows_foo, $database_backend_factory->get('foo')->getMaxRows());
-- 
GitLab