Skip to content

Commit

Permalink
Merge and deduplicate entity references so we don't get invalid Json-…
Browse files Browse the repository at this point in the history
…ld (#60)

* Fix @ids being an array, and duplicate @types
* Deduplicate entity references
  • Loading branch information
whikloj authored Apr 20, 2022
1 parent 52a939c commit 630e109
Show file tree
Hide file tree
Showing 9 changed files with 399 additions and 132 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build-2.x.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: ["7.3", "7.4"]
drupal-version: ["8.9.11", "9.1.5"]
php-versions: ["7.4", "8.0", "8.1"]
drupal-version: ["9.3.x", "9.4.x-dev"]

services:
mysql:
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
"role": "Maintainer"
}
],
"require" : {
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "^8",
"squizlabs/php_codesniffer": "^3",
Expand Down
10 changes: 9 additions & 1 deletion src/Normalizer/ContentEntityNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Drupal\jsonld\Normalizer;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\hal\LinkManager\LinkManagerInterface;
Expand Down Expand Up @@ -163,7 +164,14 @@ public function normalize($entity, $format = NULL, array $context = []) {
// but the interface (typehint) does not.
// We could check if serializer implements normalizer interface
// to avoid any possible errors in case someone swaps serializer.
$normalized = array_merge_recursive($normalized, $normalized_property);
$normalized = NestedArray::mergeDeepArray(
[
$normalized,
$normalized_property,
]
);
// Deduplicate the @type elements and arrays of entity references.
$normalized = self::deduplicateTypesAndReferences($normalized);
}
}
// Clean up @graph if this is the top-level entity
Expand Down
50 changes: 50 additions & 0 deletions src/Normalizer/NormalizerBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,54 @@ public static function escapePrefix($predicate, array $namespaces) {
return $namespaces[$exploded[0]] . $exploded[1];
}

/**
* Deduplicate lists of @types and predicate to entity references.
*
* @param array $array
* The array to deduplicate.
*
* @return array
* The deduplicated array.
*/
protected static function deduplicateTypesAndReferences(array $array): array {
if (isset($array['@graph'])) {
// Should only be run on a top level Jsonld array.
foreach ($array['@graph'] as $object_key => $object_value) {
foreach ($object_value as $key => $values) {
if ($key == '@type' && is_array($values)) {
$array['@graph'][$object_key]['@type'] = array_unique($values);
}
elseif ($key != '@id' && is_array($array['@graph'][$object_key][$key])
&& count($array['@graph'][$object_key][$key]) > 1) {
$array['@graph'][$object_key][$key] = self::deduplicateArrayOfIds($array['@graph'][$object_key][$key]);
}
}
}
}
return $array;
}

/**
* Deduplicate multi-dimensional array based on the `@id` value.
*
* @param array $array
* The multi-dimensional array.
*
* @return array
* The deduplicated multi-dimensional array.
*/
private static function deduplicateArrayOfIds(array $array): array {
$temp_array = [];
if (!isset($array[0]['@id'])) {
// No @id key, so just return the original array.
return $array;
}
foreach ($array as $val) {
if (array_search($val['@id'], array_column($temp_array, '@id')) === FALSE) {
$temp_array[] = $val;
}
}
return $temp_array;
}

}
2 changes: 1 addition & 1 deletion tests/src/Kernel/JsonldContextGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function setUp() :void {
];

// Save bundle mapping config.
$rdfMapping = rdf_get_mapping('entity_test', 'rdf_source')
rdf_get_mapping('entity_test', 'rdf_source')
->setBundleMapping(['types' => $types])
->setFieldMapping('created', $mapping)
->save();
Expand Down
2 changes: 1 addition & 1 deletion tests/src/Kernel/JsonldHookTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function setUp() : void {
*/
public function testAlterNormalizedJsonld() {

list($entity, $expected) = $this->generateTestEntity();
list($entity, $expected) = JsonldTestEntityGenerator::create()->generateNewEntity();
$expected['@graph'][] = [
"@id" => "json_alter_normalize_hooks",
"http://purl.org/dc/elements/1.1/title" => "The hook is tested.",
Expand Down
145 changes: 20 additions & 125 deletions tests/src/Kernel/JsonldKernelTestBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Drupal\Tests\jsonld\Kernel;

use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\jsonld\Encoder\JsonldEncoder;
Expand All @@ -14,7 +13,6 @@
use Drupal\KernelTests\KernelTestBase;
use Drupal\serialization\EntityResolver\ChainEntityResolver;
use Drupal\serialization\EntityResolver\TargetIdResolver;
use Drupal\user\Entity\User;
use Symfony\Component\Serializer\Serializer;
use Drupal\language\Entity\ConfigurableLanguage;

Expand Down Expand Up @@ -141,6 +139,9 @@ protected function setUp() : void {
])->setFieldMapping('field_test_entity_reference', [
'properties' => ['dc:references'],
'datatype' => 'xsd:nonNegativeInteger',
])->setFieldMapping('field_test_entity_reference2', [
'properties' => ['dc:publisher'],
'datatype' => 'xsd:nonNegativeInteger',
])
->save();

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

// Create the a second test entity reference field.
FieldStorageConfig::create([
'field_name' => 'field_test_entity_reference2',
'entity_type' => 'entity_test',
'type' => 'entity_reference',
'translatable' => FALSE,
'settings' => [
'target_type' => 'entity_test',
],
])->save();
FieldConfig::create([
'entity_type' => 'entity_test',
'field_name' => 'field_test_entity_reference2',
'bundle' => 'entity_test',
'translatable' => FALSE,
])->save();

$entity_manager = \Drupal::service('entity_type.manager');
$link_manager = \Drupal::service('hal.link_manager');
$uuid_resolver = \Drupal::service('serializer.entity_resolver.uuid');
Expand All @@ -199,127 +217,4 @@ protected function setUp() : void {
$this->serializer = new Serializer($normalizers, $encoders);
}

/**
* Generate a test entity and the expected normalized array.
*
* @return array
* with [ the entity, the normalized array ].
*
* @throws \Drupal\Core\Entity\EntityStorageException
* Problem saving the entity.
* @throws \Exception
* Problem creating a DateTime.
*/
protected function generateTestEntity() {
$target_entity = EntityTest::create([
'name' => $this->randomMachineName(),
'langcode' => 'en',
'field_test_entity_reference' => NULL,
]);
$target_entity->getFieldDefinition('created')->setTranslatable(FALSE);
$target_entity->getFieldDefinition('user_id')->setTranslatable(FALSE);
$target_entity->save();

$target_user = User::create([
'name' => $this->randomMachineName(),
'langcode' => 'en',
]);
$target_user->save();

rdf_get_mapping('entity_test', 'entity_test')->setBundleMapping(
[
'types' => [
"schema:ImageObject",
],
])->setFieldMapping('field_test_text', [
'properties' => ['dc:description'],
])->setFieldMapping('user_id', [
'properties' => ['schema:author'],
])->setFieldMapping('modified', [
'properties' => ['schema:dateModified'],
'datatype' => 'xsd:dateTime',
])->save();

$tz = new \DateTimeZone('UTC');
$dt = new \DateTime(NULL, $tz);
$created = $dt->format("U");
$created_iso = $dt->format(\DateTime::W3C);
// Create an entity.
$values = [
'langcode' => 'en',
'name' => $this->randomMachineName(),
'type' => 'entity_test',
'bundle' => 'entity_test',
'user_id' => $target_user->id(),
'created' => [
'value' => $created,
],
'field_test_text' => [
'value' => $this->randomMachineName(),
'format' => 'full_html',
],
'field_test_entity_reference' => [
'target_id' => $target_entity->id(),
],
];

$entity = EntityTest::create($values);
$entity->save();

$id = "http://localhost/entity_test/" . $entity->id() . "?_format=jsonld";
$target_id = "http://localhost/entity_test/" . $target_entity->id() . "?_format=jsonld";
$user_id = "http://localhost/user/" . $target_user->id() . "?_format=jsonld";

$expected = [
"@graph" => [
[
"@id" => $id,
"@type" => [
'http://schema.org/ImageObject',
],
"http://purl.org/dc/terms/references" => [
[
"@id" => $target_id,
],
],
"http://purl.org/dc/terms/description" => [
[
"@value" => $values['field_test_text']['value'],
"@language" => "en",
],
],
"http://purl.org/dc/terms/title" => [
[
"@language" => "en",
"@value" => $values['name'],
],
],
"http://schema.org/author" => [
[
"@id" => $user_id,
],
],
"http://schema.org/dateCreated" => [
[
"@type" => "http://www.w3.org/2001/XMLSchema#dateTime",
"@value" => $created_iso,
],
],
],
[
"@id" => $user_id,
"@type" => "http://localhost/rest/type/user/user",
],
[
"@id" => $target_id,
"@type" => [
"http://schema.org/ImageObject",
],
],
],
];

return [$entity, $expected];
}

}
Loading

0 comments on commit 630e109

Please sign in to comment.