Commit 2dc4a8ac authored by catch's avatar catch
Browse files

Issue #3112229 by mglaman, johnwebdev, jibran, Wim Leers: FieldItemNormalizer...

Issue #3112229 by mglaman, johnwebdev, jibran, Wim Leers: FieldItemNormalizer to not flatten if one property and getMainPropertyName is NULL

(cherry picked from commit 6b9f37f4)
parent fab2dc6a
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -98,6 +98,9 @@ public function testMainProperty() {
    foreach ($field_type_manager->getDefinitions() as $plugin_id => $definition) {
      $class = $definition['class'];
      $property = $class::mainPropertyName();
      if ($property === NULL) {
        continue;
      }
      $storage_definition = BaseFieldDefinition::create($plugin_id);
      $property_definitions = $class::propertyDefinitions($storage_definition);
      $properties = implode(', ', array_keys($property_definitions));
+3 −1
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager) {
   * catch it, and pass it to the value object that JSON:API uses.
   */
  public function normalize($field_item, $format = NULL, array $context = []) {
    assert($field_item instanceof FieldItemInterface);
    /** @var \Drupal\Core\TypedData\TypedDataInterface $property */
    $values = [];
    $context[CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY] = new CacheableMetadata();
@@ -71,7 +72,8 @@ public function normalize($field_item, $format = NULL, array $context = []) {
        $values[$property_name] = $this->serializer->normalize($property, $format, $context);
      }
      // Flatten if there is only a single property to normalize.
      $values = static::rasterizeValueRecursive(count($field_properties) == 1 ? reset($values) : $values);
      $flatten = count($field_properties) === 1 && $field_item::mainPropertyName() !== NULL;
      $values = static::rasterizeValueRecursive($flatten ? reset($values) : $values);
    }
    else {
      $values = $field_item->getValue();
+129 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\jsonapi\Kernel\Normalizer;

use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\jsonapi\Normalizer\FieldItemNormalizer;
use Drupal\jsonapi\Normalizer\Value\CacheableNormalization;
use Drupal\Tests\jsonapi\Kernel\JsonapiKernelTestBase;

/**
 * @coversDefaultClass \Drupal\jsonapi\Normalizer\FieldItemNormalizer
 * @group jsonapi
 *
 * @internal
 */
class FieldItemNormalizerTest extends JsonapiKernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'user',
    'link',
    'entity_test',
    'serialization',
  ];

  /**
   * The normalizer.
   *
   * @var \Drupal\jsonapi\Normalizer\FieldItemNormalizer
   */
  private $normalizer;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $etm = $this->container->get('entity_type.manager');
    $this->normalizer = new FieldItemNormalizer($etm);
    $this->normalizer->setSerializer($this->container->get('jsonapi.serializer'));

    $definitions = [];
    $definitions['links'] = BaseFieldDefinition::create('link')->setLabel('Links');
    $definitions['internal_property_value'] = BaseFieldDefinition::create('single_internal_property_test')->setLabel('Internal property');
    $definitions['no_main_property_value'] = BaseFieldDefinition::create('map')->setLabel('No main property');
    $this->container->get('state')->set('entity_test.additional_base_field_definitions', $definitions);
    $etm->clearCachedDefinitions();
  }

  /**
   * Tests a field item that has no properties.
   *
   * @covers ::normalize
   */
  public function testNormalizeFieldItemWithoutProperties(): void {
    $item = $this->prophesize(FieldItemInterface::class);
    $item->getProperties(TRUE)->willReturn([]);
    $item->getValue()->willReturn('Direct call to getValue');

    $result = $this->normalizer->normalize($item->reveal(), 'api_json');
    assert($result instanceof CacheableNormalization);
    $this->assertSame('Direct call to getValue', $result->getNormalization());
  }

  /**
   * Tests normalizing field item.
   */
  public function testNormalizeFieldItem(): void {
    $entity = EntityTest::create([
      'name' => 'Test entity',
      'links' => [
        [
          'uri' => 'https://www.drupal.org',
          'title' => 'Drupal.org',
          'options' => [
            'query' => 'foo=bar',
          ],
        ],
      ],
      'internal_property_value' => [
        [
          'value' => 'Internal property testing!',
        ],
      ],
      'no_main_property_value' => [
        [
          'value' => 'No main property testing!',
        ],
      ],
    ]);

    // Verify a field with one property is flattened.
    $result = $this->normalizer->normalize($entity->get('name')->first());
    assert($result instanceof CacheableNormalization);
    $this->assertEquals('Test entity', $result->getNormalization());

    // Verify a field with multiple public properties has all of them returned.
    $result = $this->normalizer->normalize($entity->get('links')->first());
    assert($result instanceof CacheableNormalization);
    $this->assertEquals([
      'uri' => 'https://www.drupal.org',
      'title' => 'Drupal.org',
      'options' => [
        'query' => 'foo=bar',
      ],
    ], $result->getNormalization());

    // Verify a field with one public property and one internal only returns the
    // public property, and is flattened.
    $result = $this->normalizer->normalize($entity->get('internal_property_value')->first());
    assert($result instanceof CacheableNormalization);
    // Property `internal_value` will not exist.
    $this->assertEquals('Internal property testing!', $result->getNormalization());

    // Verify a field with one public property but no main property is not
    // flattened.
    $result = $this->normalizer->normalize($entity->get('no_main_property_value')->first());
    assert($result instanceof CacheableNormalization);
    $this->assertEquals([
      'value' => 'No main property testing!',
    ], $result->getNormalization());
  }

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

namespace Drupal\entity_test\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\entity_test\TypedData\ComputedString;

/**
 * Defines the 'Single Internal Property' entity test field type.
 *
 * This is based off of the InternalPropertyTestFieldItem test field item type,
 * but only adds a single computed property. This tests that fields with a main
 * property name and one internal value are flattened.
 *
 * @see \Drupal\entity_test\Plugin\Field\FieldType\InternalPropertyTestFieldItem
 *
 * @FieldType(
 *   id = "single_internal_property_test",
 *   label = @Translation("Single Internal Property (test)"),
 *   description = @Translation("A field containing one string, from which one internal string is computed."),
 *   category = @Translation("Test"),
 *   default_widget = "string_textfield",
 *   default_formatter = "string"
 * )
 */
class SingleInternalPropertyTestFieldItem extends StringItem {

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties = parent::propertyDefinitions($field_definition);

    // Add a computed property that is internal.
    $properties['internal_value'] = DataDefinition::create('string')
      ->setLabel(new TranslatableMarkup('Computed string, internal property'))
      ->setComputed(TRUE)
      ->setClass(ComputedString::class);
    return $properties;
  }

}