.../Value/RelationshipNormalizerValue.php | 70 ++++++++++------------ .../Value/RelationshipNormalizerValueTest.php | 12 ++-- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/Normalizer/Value/RelationshipNormalizerValue.php b/src/Normalizer/Value/RelationshipNormalizerValue.php index e260de3..3f44258 100644 --- a/src/Normalizer/Value/RelationshipNormalizerValue.php +++ b/src/Normalizer/Value/RelationshipNormalizerValue.php @@ -81,68 +81,62 @@ class RelationshipNormalizerValue extends FieldNormalizerValue { : ['data' => $data, 'links' => $links]; } else { - return ['data' => static::ensureUniqueResourceLinkage($data), 'links' => $links]; + return ['data' => static::ensureUniqueResourceIdentifierObjects($data), 'links' => $links]; } } /** - * Ensures unique resource linkage. + * Ensures each resource identifier object is unique. + * + * The official JSON API JSON-Schema document requires that no two resource + * identifier objects are duplicated. + * + * This adds an @code arity @endcode member to each object's + * @code meta @endcode member. The value of this member is an integer that is + * incremented by 1 (starting from 0) for each repeated resource identifier + * sharing a common @code type @endcode and @code id @endcode. * * @param array $resource_identifier_objects * A list of JSON API resource identifier objects. + * * @return array * A set of JSON API resource identifier objects, with those having multiple * occurrences getting [meta][arity]. * * @see http://jsonapi.org/format/#document-resource-object-relationships * @see https://github.com/json-api/json-api/pull/1156#issuecomment-325377995 + * @see https://www.drupal.org/project/jsonapi/issues/2864680 */ - protected static function ensureUniqueResourceLinkage(array $resource_identifier_objects) { + protected static function ensureUniqueResourceIdentifierObjects(array $resource_identifier_objects) { if (count($resource_identifier_objects) <= 1) { return $resource_identifier_objects; } - $resource_identifier_arity = static::computeResourceLinkageArity($resource_identifier_objects); + // Count each repeated resource identifier and track their array indices. + $analysis = []; + foreach ($resource_identifier_objects as $index => $rio) { + $composite_key = $rio['type'] . ':' . $rio['id']; - $assigned_arities = []; - foreach ($resource_identifier_objects as $index => $resource_identifier_object) { - $id = $resource_identifier_object['id']; - - // If arity === 1, don't set [meta][arity]. - if ($resource_identifier_arity[$id] === 1) { - continue; - } - - // Determine arity of current resource identifier and assign it. - $arity = isset($assigned_arities[$id]) - ? ++$assigned_arities[$id] + $analysis[$composite_key]['count'] = isset($analysis[$composite_key]) + ? $analysis[$composite_key]['count'] + 1 : 0; - $resource_identifier_objects[$index]['meta']['arity'] = $arity; - // Track assigned arities. - $assigned_arities[$id] = $arity; + // The index will later be used to assign an arity to repeated resource + // identifier objects. Doing this in two phases prevents adding an arity + // to objects which only occur once. + $analysis[$composite_key]['indices'][] = $index; } - return $resource_identifier_objects; - } - - /** - * Computes the arity of each resource link. - * - * @param array $resource_identifier_objects - * A list of JSON API resource identifier objects. - * @return array - * Resource identifiers as keys, arities as values. - */ - protected static function computeResourceLinkageArity(array $resource_identifier_objects) { - return array_reduce($resource_identifier_objects, function ($carry, $resource_identifier_object) { - $id = $resource_identifier_object['id']; - if (!isset($carry[$id])) { - $carry[$id] = 0; + // Assign an arity to objects whose type + ID pair occurred more than once. + foreach ($analysis as $computed) { + if ($computed['count'] > 0) { + foreach ($computed['indices'] as $arity => $index) { + $resource_identifier_objects[$index]['meta']['arity'] = $arity; + } } - $carry[$id]++; - return $carry; - }, []); + } + + return $resource_identifier_objects; } /** diff --git a/tests/src/Unit/Normalizer/Value/RelationshipNormalizerValueTest.php b/tests/src/Unit/Normalizer/Value/RelationshipNormalizerValueTest.php index eb4f87c..855abfd 100644 --- a/tests/src/Unit/Normalizer/Value/RelationshipNormalizerValueTest.php +++ b/tests/src/Unit/Normalizer/Value/RelationshipNormalizerValueTest.php @@ -91,13 +91,13 @@ class RelationshipNormalizerValueTest extends UnitTestCase { $img1->rasterizeValue()->willReturn(['type' => 'file--file', 'id' => $img_id, 'meta' => ['alt' => 'Cute llama', 'title' => 'My spirit animal']]); $img1->getInclude()->willReturn(NULL); $img1->getCacheContexts()->willReturn(['ccimg1']); - $img1->getCacheTags()->willReturn(['ccimg1']); + $img1->getCacheTags()->willReturn(['ctimg1']); $img1->getCacheMaxAge()->willReturn(100); $img2 = $this->prophesize(RelationshipItemNormalizerValue::class); $img2->rasterizeValue()->willReturn(['type' => 'file--file', 'id' => $img_id, 'meta' => ['alt' => 'Adorable llama', 'title' => 'My spirit animal 😍']]); $img2->getInclude()->willReturn(NULL); $img2->getCacheContexts()->willReturn(['ccimg2']); - $img2->getCacheTags()->willReturn(['ccimg2']); + $img2->getCacheTags()->willReturn(['ctimg2']); $img2->getCacheMaxAge()->willReturn(50); $links = [ @@ -160,7 +160,7 @@ class RelationshipNormalizerValueTest extends UnitTestCase { ], (new CacheableMetadata()) ->setCacheContexts(['ccimg1', 'user']) - ->setCacheTags(['ccimg1', 'relationship:foo']) + ->setCacheTags(['ctimg1', 'relationship:foo']) ->setCacheMaxAge(100), ], 'multiple cardinality, all same values, with meta' => [ @@ -173,7 +173,7 @@ class RelationshipNormalizerValueTest extends UnitTestCase { ], (new CacheableMetadata()) ->setCacheContexts(['ccimg1', 'user']) - ->setCacheTags(['ccimg1', 'relationship:foo']) + ->setCacheTags(['ctimg1', 'relationship:foo']) ->setCacheMaxAge(100), ], 'multiple cardinality, some same values with same values but different meta' => [ @@ -187,7 +187,7 @@ class RelationshipNormalizerValueTest extends UnitTestCase { ], (new CacheableMetadata()) ->setCacheContexts(['ccimg1', 'ccimg2', 'user']) - ->setCacheTags(['ccimg1', 'ccimg2', 'relationship:foo']) + ->setCacheTags(['ctimg1', 'ctimg2', 'relationship:foo']) ->setCacheMaxAge(50), ], 'all the edge cases!' => [ @@ -204,7 +204,7 @@ class RelationshipNormalizerValueTest extends UnitTestCase { ], (new CacheableMetadata()) ->setCacheContexts(['ccbar', 'ccfoo', 'ccimg1', 'ccimg2', 'user']) - ->setCacheTags(['ccimg1', 'ccimg2', 'ctbar', 'ctfoo', 'relationship:foo']) + ->setCacheTags(['ctbar', 'ctfoo', 'ctimg1', 'ctimg2', 'relationship:foo']) ->setCacheMaxAge(10), ], ];