diff --git a/core/modules/field/tests/src/Kernel/FieldTypePluginManagerTest.php b/core/modules/field/tests/src/Kernel/FieldTypePluginManagerTest.php index 80653074a60a5f2e8649a7f7b9631a355b9e8959..d8cb5110ffad416fe76fa4771086db1bedffb0c8 100644 --- a/core/modules/field/tests/src/Kernel/FieldTypePluginManagerTest.php +++ b/core/modules/field/tests/src/Kernel/FieldTypePluginManagerTest.php @@ -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)); diff --git a/core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php b/core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php index f3afb429927ea88762d5dfa3bd48a39c69df1835..9409aab03e6088195f312d8df1e98125b96d5391 100644 --- a/core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php +++ b/core/modules/jsonapi/src/Normalizer/FieldItemNormalizer.php @@ -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(); diff --git a/core/modules/jsonapi/tests/src/Kernel/Normalizer/FieldItemNormalizerTest.php b/core/modules/jsonapi/tests/src/Kernel/Normalizer/FieldItemNormalizerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cb40e712b5d5f92bf075a006901a6be19bf6d645 --- /dev/null +++ b/core/modules/jsonapi/tests/src/Kernel/Normalizer/FieldItemNormalizerTest.php @@ -0,0 +1,129 @@ +<?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()); + } + +} diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SingleInternalPropertyTestFieldItem.php b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SingleInternalPropertyTestFieldItem.php new file mode 100644 index 0000000000000000000000000000000000000000..30add62df1d0a04b3a48d5448e9ac2f2f0e8df3f --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SingleInternalPropertyTestFieldItem.php @@ -0,0 +1,45 @@ +<?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; + } + +}