Unverified Commit 4a32768c authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3300404 by alexpott, akoe, wranvaud, holo96, quietone, smustgrave,...

Issue #3300404 by alexpott, akoe, wranvaud, holo96, quietone, smustgrave, catch, donquixote, krystalcode, rosk0, jungle, spokje, eduardo morales alberti, taras.suliatitskiy, p-neyens, dcam: Handle nullable serialized field columns
parent 66c9856e
Loading
Loading
Loading
Loading
Loading
+23 −5
Original line number Diff line number Diff line
@@ -471,7 +471,7 @@ protected function mapFromStorageRecords(array $records, $load_from_revision = F
          $definition_columns = $this->fieldStorageDefinitions[$field_name]->getColumns();
          foreach ($field_columns as $property_name => $column_name) {
            if (property_exists($record, $column_name)) {
              $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = !empty($definition_columns[$property_name]['serialize']) ? unserialize($record->{$column_name}) : $record->{$column_name};
              $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = !empty($definition_columns[$property_name]['serialize']) ? $this->handleNullableFieldUnserialize($record->{$column_name}) : $record->{$column_name};
              unset($record->{$column_name});
            }
          }
@@ -482,7 +482,7 @@ protected function mapFromStorageRecords(array $records, $load_from_revision = F
          if (property_exists($record, $column_name)) {
            $columns = $this->fieldStorageDefinitions[$field_name]->getColumns();
            $column = reset($columns);
            $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT] = !empty($column['serialize']) ? unserialize($record->{$column_name}) : $record->{$column_name};
            $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT] = !empty($column['serialize']) ? $this->handleNullableFieldUnserialize($record->{$column_name}) : $record->{$column_name};
            unset($record->{$column_name});
          }
        }
@@ -596,12 +596,12 @@ protected function loadFromSharedTables(array &$values, array &$translations, $l
          if (count($columns) == 1) {
            $column_name = reset($columns);
            $column_attributes = $definition_columns[key($columns)];
            $values[$id][$field_name][$langcode] = (!empty($column_attributes['serialize'])) ? unserialize($row[$column_name]) : $row[$column_name];
            $values[$id][$field_name][$langcode] = (!empty($column_attributes['serialize'])) ? $this->handleNullableFieldUnserialize($row[$column_name]) : $row[$column_name];
          }
          else {
            foreach ($columns as $property_name => $column_name) {
              $column_attributes = $definition_columns[$property_name];
              $values[$id][$field_name][$langcode][$property_name] = (!empty($column_attributes['serialize'])) ? unserialize($row[$column_name]) : $row[$column_name];
              $values[$id][$field_name][$langcode][$property_name] = (!empty($column_attributes['serialize'])) ? $this->handleNullableFieldUnserialize($row[$column_name]) : $row[$column_name];
            }
          }
        }
@@ -1261,7 +1261,7 @@ protected function loadFromDedicatedTables(array &$values, $load_from_revision)
            foreach ($storage_definition->getColumns() as $column => $attributes) {
              $column_name = $table_mapping->getFieldColumnName($storage_definition, $column);
              // Unserialize the value if specified in the column schema.
              $item[$column] = (!empty($attributes['serialize'])) ? unserialize($row->$column_name) : $row->$column_name;
              $item[$column] = (!empty($attributes['serialize'])) ? $this->handleNullableFieldUnserialize($row->$column_name) : $row->$column_name;
            }

            // Add the item to the field values for the entity.
@@ -1786,4 +1786,22 @@ public function countFieldData($storage_definition, $as_bool = FALSE) {
    return $as_bool ? (bool) $count : (int) $count;
  }

  /**
   * Handles NULL values before passing data to unserialize().
   *
   * @param mixed|null $value
   *   The serialized value.
   *
   * @return mixed|null
   *   The unserialized data, or NULL if the original value is NULL.
   */
  protected function handleNullableFieldUnserialize(mixed $value): mixed {
    // Ensure NULL values aren't passed to unserialize().
    if ($value === NULL) {
      return NULL;
    }

    return unserialize($value);
  }

}
+48 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\entity_test\Plugin\Field\FieldType;

use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;

/**
 * Defines the 'nullable_serialized_item_test' entity field type.
 */
#[FieldType(
  id: "nullable_serialized_item_test",
  label: new TranslatableMarkup("Test nullable serialized field item"),
  description: new TranslatableMarkup("A field containing a nullable serialized string value."),
)]
class NullableSerializedItem extends SerializedItem {

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties['value'] = DataDefinition::create('string')
      ->setLabel(new TranslatableMarkup('Test serialized value'))
      ->setRequired(FALSE);

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return [
      'columns' => [
        'value' => [
          'type' => 'blob',
          'size' => 'big',
          'serialize' => TRUE,
        ],
      ],
    ];
  }

}
+54 −0
Original line number Diff line number Diff line
@@ -576,4 +576,58 @@ public function testTableNames(): void {
    $this->assertEquals($table_mapping->getDedicatedDataTableName($field_storage), $table_mapping->getFieldTableName('some_field_name'));
  }

  /**
   * Tests loading base and configurable serialized fields with NULL values.
   *
   * Note that this tests loading field values that are literally NULL. It
   * doesn't test that NULL values are serialized and unserialized properly.
   */
  public function testNullSerializedFieldLoad(): void {
    $connection = Database::getConnection();
    $entity_type = $bundle = 'entity_test_serialized_field';
    $this->installEntitySchema($entity_type);
    $storage = $this->container->get('entity_type.manager')->getStorage($entity_type);

    // The serialized base field is already part of the test entity type, but
    // a configurable field has to be created.
    $serialized_field = $this->randomMachineName();
    $field_storage = FieldStorageConfig::create([
      'field_name' => $serialized_field,
      'entity_type' => $entity_type,
      'type' => 'nullable_serialized_item_test',
      'cardinality' => 1,
      'required' => FALSE,
    ]);
    $field_storage->save();
    $field = FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => $bundle,
    ]);
    $field->save();

    $entity = $storage->create();
    $entity->save();

    // Manually set the field values to NULL with database queries.
    $connection->update('entity_test_serialized_fields')
      ->fields(['serialized' => NULL])
      ->condition('id', $entity->id())
      ->execute();
    $connection->insert($entity_type . '__' . $serialized_field)
      ->fields([
        'bundle' => $bundle,
        'deleted' => 0,
        'entity_id' => $entity->id(),
        'revision_id' => $entity->id(),
        'langcode' => $entity->language()->getId(),
        'delta' => 0,
        $serialized_field . '_value' => NULL,
      ])
      ->execute();

    $storage->load($entity->id());
    $this->assertNull($entity->serialized->value);
    $this->assertNull($entity->{$serialized_field}->value);
  }

}