Skip to content

Commit 630e109

Browse files
authored
Merge and deduplicate entity references so we don't get invalid Json-ld (#60)
* Fix @ids being an array, and duplicate @types * Deduplicate entity references
1 parent 52a939c commit 630e109

9 files changed

Lines changed: 399 additions & 132 deletions

.github/workflows/build-2.x.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ jobs:
2121
runs-on: ubuntu-latest
2222
strategy:
2323
matrix:
24-
php-versions: ["7.3", "7.4"]
25-
drupal-version: ["8.9.11", "9.1.5"]
24+
php-versions: ["7.4", "8.0", "8.1"]
25+
drupal-version: ["9.3.x", "9.4.x-dev"]
2626

2727
services:
2828
mysql:

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
"role": "Maintainer"
2020
}
2121
],
22+
"require" : {
23+
"php": ">=7.4"
24+
},
2225
"require-dev": {
2326
"phpunit/phpunit": "^8",
2427
"squizlabs/php_codesniffer": "^3",

src/Normalizer/ContentEntityNormalizer.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Drupal\jsonld\Normalizer;
44

5+
use Drupal\Component\Utility\NestedArray;
56
use Drupal\Core\Entity\EntityTypeManagerInterface;
67
use Drupal\Core\Extension\ModuleHandlerInterface;
78
use Drupal\hal\LinkManager\LinkManagerInterface;
@@ -163,7 +164,14 @@ public function normalize($entity, $format = NULL, array $context = []) {
163164
// but the interface (typehint) does not.
164165
// We could check if serializer implements normalizer interface
165166
// to avoid any possible errors in case someone swaps serializer.
166-
$normalized = array_merge_recursive($normalized, $normalized_property);
167+
$normalized = NestedArray::mergeDeepArray(
168+
[
169+
$normalized,
170+
$normalized_property,
171+
]
172+
);
173+
// Deduplicate the @type elements and arrays of entity references.
174+
$normalized = self::deduplicateTypesAndReferences($normalized);
167175
}
168176
}
169177
// Clean up @graph if this is the top-level entity

src/Normalizer/NormalizerBase.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,54 @@ public static function escapePrefix($predicate, array $namespaces) {
6464
return $namespaces[$exploded[0]] . $exploded[1];
6565
}
6666

67+
/**
68+
* Deduplicate lists of @types and predicate to entity references.
69+
*
70+
* @param array $array
71+
* The array to deduplicate.
72+
*
73+
* @return array
74+
* The deduplicated array.
75+
*/
76+
protected static function deduplicateTypesAndReferences(array $array): array {
77+
if (isset($array['@graph'])) {
78+
// Should only be run on a top level Jsonld array.
79+
foreach ($array['@graph'] as $object_key => $object_value) {
80+
foreach ($object_value as $key => $values) {
81+
if ($key == '@type' && is_array($values)) {
82+
$array['@graph'][$object_key]['@type'] = array_unique($values);
83+
}
84+
elseif ($key != '@id' && is_array($array['@graph'][$object_key][$key])
85+
&& count($array['@graph'][$object_key][$key]) > 1) {
86+
$array['@graph'][$object_key][$key] = self::deduplicateArrayOfIds($array['@graph'][$object_key][$key]);
87+
}
88+
}
89+
}
90+
}
91+
return $array;
92+
}
93+
94+
/**
95+
* Deduplicate multi-dimensional array based on the `@id` value.
96+
*
97+
* @param array $array
98+
* The multi-dimensional array.
99+
*
100+
* @return array
101+
* The deduplicated multi-dimensional array.
102+
*/
103+
private static function deduplicateArrayOfIds(array $array): array {
104+
$temp_array = [];
105+
if (!isset($array[0]['@id'])) {
106+
// No @id key, so just return the original array.
107+
return $array;
108+
}
109+
foreach ($array as $val) {
110+
if (array_search($val['@id'], array_column($temp_array, '@id')) === FALSE) {
111+
$temp_array[] = $val;
112+
}
113+
}
114+
return $temp_array;
115+
}
116+
67117
}

tests/src/Kernel/JsonldContextGeneratorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public function setUp() :void {
6363
];
6464

6565
// Save bundle mapping config.
66-
$rdfMapping = rdf_get_mapping('entity_test', 'rdf_source')
66+
rdf_get_mapping('entity_test', 'rdf_source')
6767
->setBundleMapping(['types' => $types])
6868
->setFieldMapping('created', $mapping)
6969
->save();

tests/src/Kernel/JsonldHookTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function setUp() : void {
3737
*/
3838
public function testAlterNormalizedJsonld() {
3939

40-
list($entity, $expected) = $this->generateTestEntity();
40+
list($entity, $expected) = JsonldTestEntityGenerator::create()->generateNewEntity();
4141
$expected['@graph'][] = [
4242
"@id" => "json_alter_normalize_hooks",
4343
"http://purl.org/dc/elements/1.1/title" => "The hook is tested.",

tests/src/Kernel/JsonldKernelTestBase.php

Lines changed: 20 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Drupal\Tests\jsonld\Kernel;
44

5-
use Drupal\entity_test\Entity\EntityTest;
65
use Drupal\field\Entity\FieldConfig;
76
use Drupal\field\Entity\FieldStorageConfig;
87
use Drupal\jsonld\Encoder\JsonldEncoder;
@@ -14,7 +13,6 @@
1413
use Drupal\KernelTests\KernelTestBase;
1514
use Drupal\serialization\EntityResolver\ChainEntityResolver;
1615
use Drupal\serialization\EntityResolver\TargetIdResolver;
17-
use Drupal\user\Entity\User;
1816
use Symfony\Component\Serializer\Serializer;
1917
use Drupal\language\Entity\ConfigurableLanguage;
2018

@@ -141,6 +139,9 @@ protected function setUp() : void {
141139
])->setFieldMapping('field_test_entity_reference', [
142140
'properties' => ['dc:references'],
143141
'datatype' => 'xsd:nonNegativeInteger',
142+
])->setFieldMapping('field_test_entity_reference2', [
143+
'properties' => ['dc:publisher'],
144+
'datatype' => 'xsd:nonNegativeInteger',
144145
])
145146
->save();
146147

@@ -174,6 +175,23 @@ protected function setUp() : void {
174175
'translatable' => FALSE,
175176
])->save();
176177

178+
// Create the a second test entity reference field.
179+
FieldStorageConfig::create([
180+
'field_name' => 'field_test_entity_reference2',
181+
'entity_type' => 'entity_test',
182+
'type' => 'entity_reference',
183+
'translatable' => FALSE,
184+
'settings' => [
185+
'target_type' => 'entity_test',
186+
],
187+
])->save();
188+
FieldConfig::create([
189+
'entity_type' => 'entity_test',
190+
'field_name' => 'field_test_entity_reference2',
191+
'bundle' => 'entity_test',
192+
'translatable' => FALSE,
193+
])->save();
194+
177195
$entity_manager = \Drupal::service('entity_type.manager');
178196
$link_manager = \Drupal::service('hal.link_manager');
179197
$uuid_resolver = \Drupal::service('serializer.entity_resolver.uuid');
@@ -199,127 +217,4 @@ protected function setUp() : void {
199217
$this->serializer = new Serializer($normalizers, $encoders);
200218
}
201219

202-
/**
203-
* Generate a test entity and the expected normalized array.
204-
*
205-
* @return array
206-
* with [ the entity, the normalized array ].
207-
*
208-
* @throws \Drupal\Core\Entity\EntityStorageException
209-
* Problem saving the entity.
210-
* @throws \Exception
211-
* Problem creating a DateTime.
212-
*/
213-
protected function generateTestEntity() {
214-
$target_entity = EntityTest::create([
215-
'name' => $this->randomMachineName(),
216-
'langcode' => 'en',
217-
'field_test_entity_reference' => NULL,
218-
]);
219-
$target_entity->getFieldDefinition('created')->setTranslatable(FALSE);
220-
$target_entity->getFieldDefinition('user_id')->setTranslatable(FALSE);
221-
$target_entity->save();
222-
223-
$target_user = User::create([
224-
'name' => $this->randomMachineName(),
225-
'langcode' => 'en',
226-
]);
227-
$target_user->save();
228-
229-
rdf_get_mapping('entity_test', 'entity_test')->setBundleMapping(
230-
[
231-
'types' => [
232-
"schema:ImageObject",
233-
],
234-
])->setFieldMapping('field_test_text', [
235-
'properties' => ['dc:description'],
236-
])->setFieldMapping('user_id', [
237-
'properties' => ['schema:author'],
238-
])->setFieldMapping('modified', [
239-
'properties' => ['schema:dateModified'],
240-
'datatype' => 'xsd:dateTime',
241-
])->save();
242-
243-
$tz = new \DateTimeZone('UTC');
244-
$dt = new \DateTime(NULL, $tz);
245-
$created = $dt->format("U");
246-
$created_iso = $dt->format(\DateTime::W3C);
247-
// Create an entity.
248-
$values = [
249-
'langcode' => 'en',
250-
'name' => $this->randomMachineName(),
251-
'type' => 'entity_test',
252-
'bundle' => 'entity_test',
253-
'user_id' => $target_user->id(),
254-
'created' => [
255-
'value' => $created,
256-
],
257-
'field_test_text' => [
258-
'value' => $this->randomMachineName(),
259-
'format' => 'full_html',
260-
],
261-
'field_test_entity_reference' => [
262-
'target_id' => $target_entity->id(),
263-
],
264-
];
265-
266-
$entity = EntityTest::create($values);
267-
$entity->save();
268-
269-
$id = "http://localhost/entity_test/" . $entity->id() . "?_format=jsonld";
270-
$target_id = "http://localhost/entity_test/" . $target_entity->id() . "?_format=jsonld";
271-
$user_id = "http://localhost/user/" . $target_user->id() . "?_format=jsonld";
272-
273-
$expected = [
274-
"@graph" => [
275-
[
276-
"@id" => $id,
277-
"@type" => [
278-
'http://schema.org/ImageObject',
279-
],
280-
"http://purl.org/dc/terms/references" => [
281-
[
282-
"@id" => $target_id,
283-
],
284-
],
285-
"http://purl.org/dc/terms/description" => [
286-
[
287-
"@value" => $values['field_test_text']['value'],
288-
"@language" => "en",
289-
],
290-
],
291-
"http://purl.org/dc/terms/title" => [
292-
[
293-
"@language" => "en",
294-
"@value" => $values['name'],
295-
],
296-
],
297-
"http://schema.org/author" => [
298-
[
299-
"@id" => $user_id,
300-
],
301-
],
302-
"http://schema.org/dateCreated" => [
303-
[
304-
"@type" => "http://www.w3.org/2001/XMLSchema#dateTime",
305-
"@value" => $created_iso,
306-
],
307-
],
308-
],
309-
[
310-
"@id" => $user_id,
311-
"@type" => "http://localhost/rest/type/user/user",
312-
],
313-
[
314-
"@id" => $target_id,
315-
"@type" => [
316-
"http://schema.org/ImageObject",
317-
],
318-
],
319-
],
320-
];
321-
322-
return [$entity, $expected];
323-
}
324-
325220
}

0 commit comments

Comments
 (0)