Skip to content
Snippets Groups Projects
Commit 488eb986 authored by Björn Brala's avatar Björn Brala
Browse files

Issue #3218643 by bbrala, e0ipso: ResourceBuildEvents should be used for field aliasing

parent ba657d7d
No related branches found
No related tags found
1 merge request!2Issue #3218643: ResourceBuildEvents should be used for field aliasing
......@@ -57,3 +57,10 @@ services:
- '@jsonapi.resource_type.repository'
- '@session'
- '@request_stack'
jsonapi_extras.jsonapi_build_subscriber:
class: Drupal\jsonapi_extras\EventSubscriber\JsonApiBuildSubscriber
arguments:
- '@jsonapi_extras.resource_type.repository'
tags:
- { name: event_subscriber }
......@@ -58,7 +58,7 @@ class EntityResource extends JsonApiEntityResourse {
foreach ($default_filter_input as $key => $value) {
if (substr($key, 0, 6) === 'filter') {
$key = str_replace('filter:', '', $key);
// TODO: Replace this with use of the NestedArray utility.
// @todo Replace this with use of the NestedArray utility.
$this->setFilterValue($default_filter, $key, $value);
}
}
......@@ -66,7 +66,7 @@ class EntityResource extends JsonApiEntityResourse {
foreach ($default_sorting_input as $key => $value) {
if (substr($key, 0, 4) === 'sort') {
$key = str_replace('sort:', '', $key);
// TODO: Replace this with use of the NestedArray utility.
// @todo Replace this with use of the NestedArray utility.
$this->setFilterValue($default_sorting, $key, $value);
}
}
......
......@@ -241,7 +241,7 @@ class JsonApiDefaultsFunctionalTest extends JsonApiExtrasFunctionalTestBase {
'sort:title#path' => 'title',
'sort:title#direction' => 'DESC',
],
// TODO: Change this to 'tags.vid'.
// @todo Change this to 'tags.vid'.
'default_include' => ['tags'],
],
],
......
<?php
namespace Drupal\jsonapi_extras\EventSubscriber;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent;
use Drupal\jsonapi\ResourceType\ResourceTypeBuildEvents;
use Drupal\jsonapi_extras\Entity\JsonapiResourceConfig;
use Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository;
use Drupal\jsonapi_extras\ResourceType\NullJsonapiResourceConfig;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* JSON API build subscriber that applies all changes from extra's to the API.
*/
class JsonApiBuildSubscriber implements EventSubscriberInterface {
/**
* @var \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository
* The extra's resource repository
*/
private $repository;
/**
* JsonApiBuildSubscriber constructor.
*
* @param \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository $repository
* Repository from jsonapi_extras is needed to apply configuration.
*/
public function __construct(ConfigurableResourceTypeRepository $repository) {
$this->repository = $repository;
}
/**
* What events to subscribe to.
*/
public static function getSubscribedEvents() {
$events[ResourceTypeBuildEvents::BUILD][] = ['applyResourceConfig'];
return $events;
}
/**
* Apply resource config through the event.
*
* @param \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent $event
* The build event used to change the resources and fields.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function applyResourceConfig(ResourceTypeBuildEvent $event) {
$resource_config = $this->getResourceConfig($event->getResourceTypeName());
if ($resource_config instanceof NullJsonapiResourceConfig) {
return;
}
if ($resource_config->get('disabled')) {
$event->disableResourceType();
}
$this->overrideFields($resource_config, $event);
}
/**
* Get a single resource configuration entity by its ID.
*
* @param string $resource_config_id
* The configuration entity ID.
*
* @return \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig
* The configuration entity for the resource type.
*/
protected function getResourceConfig($resource_config_id) {
$null_resource = new NullJsonapiResourceConfig(
['id' => $resource_config_id],
'jsonapi_resource_config'
);
try {
$resource_configs = $this->repository->getResourceConfigs();
return $resource_configs[$resource_config_id] ?? $null_resource;
}
catch (PluginException $e) {
return $null_resource;
}
}
/**
* Gets the fields for the given field names and entity type + bundle.
*
* @param \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig $resource_config
* The associated resource config.
* @param \Drupal\jsonapi\ResourceType\ResourceTypeBuildEvent $event
* The associated resource config.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function overrideFields(JsonapiResourceConfig $resource_config, ResourceTypeBuildEvent $event) {
// Use the base class to fetch the non-configurable field mappings.
$mappings = $resource_config->getFieldMapping();
// Ignore all the fields that don't have aliases.
$mappings = array_filter($mappings, function ($field_info) {
return $field_info !== TRUE;
});
$fields = $event->getFields();
foreach ($mappings as $internal_name => $mapping) {
if (!isset($fields[$internal_name])) {
continue;
}
if (is_string($mapping)) {
$event->setPublicFieldName($fields[$internal_name], $mapping);
}
if ($mapping === FALSE) {
$event->disableField($fields[$internal_name]);
}
}
}
}
......@@ -63,7 +63,7 @@ class ResourceIdentifierNormalizer extends JsonApiNormalizerDecoratorBase {
// Apply any enhancements necessary.
$context['field_resource_identifier'] = $field;
$transformed = $enhancer->undoTransform($normalized_output->getNormalization(), new Context($context));
// @TODO: Enhancers should utilize CacheableNormalization to infer additional cacheability from the enhancer.
// @todo Enhancers should utilize CacheableNormalization to infer additional cacheability from the enhancer.
return new CacheableNormalization(
$normalized_output,
array_intersect_key($transformed, array_flip(['id', 'type', 'meta']))
......
......@@ -43,7 +43,7 @@ abstract class ResourceFieldEnhancerBase extends PluginBase implements ResourceF
* {@inheritdoc}
*/
public function calculateDependencies() {
// TODO: This should have a dependency on the resource_config entity.
// @todo This should have a dependency on the resource_config entity.
return [];
}
......@@ -82,7 +82,7 @@ abstract class ResourceFieldEnhancerBase extends PluginBase implements ResourceF
* {@inheritdoc}
*/
public function getInputValidator() {
// @TODO: Implement a getInputJsonSchema method.
// @todo Implement a getInputJsonSchema method.
return new AcceptValidator();
}
......
......@@ -7,7 +7,6 @@ use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\jsonapi\ResourceType\ResourceTypeRepository;
use Drupal\jsonapi_extras\Entity\JsonapiResourceConfig;
use Drupal\jsonapi_extras\Plugin\ResourceFieldEnhancerManager;
use Drupal\Core\Config\ConfigFactoryInterface;
......@@ -132,32 +131,32 @@ class ConfigurableResourceTypeRepository extends ResourceTypeRepository {
* 2. Field mapping not based on logic, but on configuration.
*/
protected function createResourceType(EntityTypeInterface $entity_type, $bundle) {
$resource_type = parent::createResourceType($entity_type, $bundle);
$configurable_resource_type = new ConfigurableResourceType(
$resource_type->getEntityTypeId(),
$resource_type->getBundle(),
$resource_type->getDeserializationTargetClass(),
$resource_type->isInternal(),
$resource_type->isLocatable(),
$resource_type->isMutable(),
$resource_type->isVersionable(),
$resource_type->getFields()
);
$resource_config_id = static::buildResourceConfigId(
$entity_type->id(),
$bundle
);
$resource_config = $this->getResourceConfig($resource_config_id);
// Create subclassed ResourceType object with the same parameters as the
// parent implementation.
$resource_type = new ConfigurableResourceType(
$entity_type->id(),
$bundle,
$entity_type->getClass(),
$entity_type->isInternal() || (bool) $resource_config->get('disabled'),
static::isLocatableResourceType($entity_type, $bundle),
TRUE,
static::isVersionableResourceType($entity_type),
$this->overrideFields($resource_config)
);
// Inject additional services through setters. By using setter injection
// rather that constructor injection, we prevent most future BC breaks.
$resource_type->setJsonapiResourceConfig($resource_config);
$resource_type->setEnhancerManager($this->enhancerManager);
$resource_type->setConfigFactory($this->configFactory);
$configurable_resource_type->setJsonapiResourceConfig($resource_config);
$configurable_resource_type->setEnhancerManager($this->enhancerManager);
$configurable_resource_type->setConfigFactory($this->configFactory);
return $resource_type;
return $configurable_resource_type;
}
/**
......@@ -238,54 +237,12 @@ class ConfigurableResourceTypeRepository extends ResourceTypeRepository {
$this->resourceConfigs = [];
}
/**
* Gets the fields for the given field names and entity type + bundle.
*
* @param \Drupal\jsonapi_extras\Entity\JsonapiResourceConfig $resource_config
* The associated resource config.
*
* @return \Drupal\jsonapi\ResourceType\ResourceTypeField[]
* An array of JSON:API resource type fields keyed by internal field names.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function overrideFields(JsonapiResourceConfig $resource_config) {
// This is not ideal, but we cannot load the resource type to get the entity
// type object. That is because this is used during the creation of the
// ResourceType.
list($entity_type_id, $bundle) = explode('--', $resource_config->getOriginalId());
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
$field_names = $this->getAllFieldNames($entity_type, $bundle);
// Use the base class to fetch the non-configurable field mappings.
$mappings = $resource_config->getFieldMapping();
// Ignore all the fields that don't have aliases.
$mappings = array_filter($mappings, function ($field_info) {
return $field_info !== TRUE;
});
// Make sure to respect the overrides coming from JSON:API if there is no
// input in JSON:API Extras.
$fields = $this->getFields($field_names, $entity_type, $bundle);
foreach ($mappings as $internal_name => $mapping) {
if (!isset($fields[$internal_name])) {
continue;
}
if (is_string($mapping)) {
$fields[$internal_name] = $fields[$internal_name]->withPublicName($mapping);
}
if ($mapping === FALSE) {
$fields[$internal_name] = $fields[$internal_name]->disabled();
}
}
return $fields;
}
/**
* {@inheritdoc}
*/
public function getByTypeName($type_name) {
$resource_types = $this->all();
if (isset($resource_types[$type_name])) {
return $resource_types[$type_name];
}
......
......@@ -3,6 +3,7 @@
namespace Drupal\Tests\jsonapi_extras\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
......@@ -29,6 +30,7 @@ class JsonApiExtrasFunctionalTest extends JsonApiFunctionalTestBase {
public static $modules = [
'jsonapi_extras',
'basic_auth',
'jsonapi_test_resource_type_building',
];
/**
......@@ -41,6 +43,7 @@ class JsonApiExtrasFunctionalTest extends JsonApiFunctionalTestBase {
}
parent::setUp();
// Add vocabs field to the tags.
$this->createEntityReferenceField(
'taxonomy_term',
......@@ -138,6 +141,9 @@ class JsonApiExtrasFunctionalTest extends JsonApiFunctionalTestBase {
return parent::drupalGet($path, $options, $headers);
}
/**
*
*/
public function testOverwriteFieldWithOtherField() {
$this->createDefaultContent(1, 1, FALSE, TRUE, static::IS_NOT_MULTILINGUAL);
......@@ -166,23 +172,50 @@ class JsonApiExtrasFunctionalTest extends JsonApiFunctionalTestBase {
$stringResponse = $this->drupalGet('/api/articles', ['query' => ['sort' => 'field_text_moved.value']]);
$output = Json::decode($stringResponse);
// Check if order changed as expected
// Check if order changed as expected.
$this->assertEquals('a', $output['data'][0]['attributes']['field_text_moved']['value']);
$this->assertEquals('b', $output['data'][1]['attributes']['field_text_moved']['value']);
$stringResponse = $this->drupalGet('/api/articles', ['query' => ['sort' => '-field_text_moved.value']]);
$output = Json::decode($stringResponse);
// Check if order changed as expected
// Check if order changed as expected.
$this->assertEquals('b', $output['data'][0]['attributes']['field_text_moved']['value']);
$this->assertEquals('a', $output['data'][1]['attributes']['field_text_moved']['value']);
}
/**
* Tests that resource type fields can be aliased per resource type.
*
* @see Core jsonapi module how the state code below works:
* Drupal\jsonapi_test_resource_type_building\EventSubscriber\ResourceTypeBuildEventSubscriber
*
* @todo Create a test similar to this
*/
public function testResourceTypeFieldAliasing() {
/** @var \Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository $resourceTypeRepository */
$resourceTypeRepository = $this->container->get('jsonapi.resource_type.repository');
$nodeArticleType = $resourceTypeRepository->getByTypeName('node--article');
$this->assertSame('owner', $nodeArticleType->getPublicName('uid'));
$resource_type_field_aliases = [
'node--article' => [
'uid' => 'author',
],
];
\Drupal::state()->set('jsonapi_test_resource_type_builder.resource_type_field_aliases', $resource_type_field_aliases);
Cache::invalidateTags(['jsonapi_resource_types']);
$this->assertSame('author', $resourceTypeRepository->getByTypeName('node--article')->getPublicName('uid'));
}
/**
* Test the GET method.
*/
public function testRead() {
$num_articles = 61;
$this->createDefaultContent($num_articles, 5, TRUE, TRUE, static::IS_NOT_MULTILINGUAL);
// Make the link for node/3 to point to an entity.
......@@ -381,7 +414,6 @@ class JsonApiExtrasFunctionalTest extends JsonApiFunctionalTestBase {
$this->assertCount(3, $created_response['data']);
}
/**
* Creates the JSON:API Resource Config entities to override the resources.
*/
......@@ -620,7 +652,6 @@ class JsonApiExtrasFunctionalTest extends JsonApiFunctionalTestBase {
],
])->save();
}
/**
......
......@@ -53,17 +53,17 @@ class EntityToJsonApiTest extends JsonapiKernelTestBase {
];
/**
* @var NodeType
* @var \Drupal\node\Entity\NodeType
*/
private $nodeType;
/**
* @var Vocabulary
* @var \Drupal\taxonomy\Entity\Vocabulary
*/
private $vocabulary;
/**
* @var Node
* @var \Drupal\node\Entity\Node
*/
private $node;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment