diff --git a/src/ResourceType/ResourceFieldFactory.php b/src/ResourceType/ResourceFieldFactory.php index eb2ec20..06f83e9 100644 --- a/src/ResourceType/ResourceFieldFactory.php +++ b/src/ResourceType/ResourceFieldFactory.php @@ -26,6 +26,13 @@ class ResourceFieldFactory { */ protected $entityFieldManager; + /** + * An index of maps of public field names to internal field names. + * + * @var array + */ + protected $fieldMappings; + /** * ResourceFieldFactory constructor. * @@ -36,6 +43,29 @@ class ResourceFieldFactory { $this->entityFieldManager = $entity_field_manager; } + /** + * Gets all resource fields for a given entity type and bundle. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type for which to get all field names. + * @param string $bundle + * The bundle for which to get all field names. + * + * @return mixed[] + * An array of resource fields (or FALSE if the field is disabled) keyed by + * internal field name. + */ + public function createResourceFields(EntityTypeInterface $entity_type, $bundle) { + return array_reduce($this->getAllFieldNames($entity_type, $bundle), function ($fields, $internal_field_name) use ($entity_type, $bundle) { + $mapping = $this->getFieldMappingByInternalFieldName($entity_type, $bundle, $internal_field_name); + // If the mapping is FALSE, the field is disabled. + $fields[$internal_field_name] = $mapping !== FALSE + ? $this->createResourceField($entity_type, $bundle, $internal_field_name, is_string($mapping) ? $mapping : NULL) + : FALSE; + return $fields; + }, []); + } + /** * Creates a ResourceFieldInterface object for a given field. * @@ -146,4 +176,124 @@ class ResourceFieldFactory { return $data_definition instanceof DataReferenceTargetDefinition; } + /** + * Get the public field alias for a given internal field. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type to which the field belongs. + * @param $bundle + * The bundle to which the field belongs. + * @param $internal_field_name + * The internal field name. + * + * @return string + */ + protected function getFieldMappingByInternalFieldName(EntityTypeInterface $entity_type, $bundle, $internal_field_name) { + if (!isset($this->fieldMappings[$entity_type->id()][$bundle])) { + $this->fieldMappings[$entity_type->id()][$bundle] = static::getFieldMapping($entity_type, $bundle); + } + return $this->fieldMappings[$entity_type->id()][$bundle][$internal_field_name]; + } + + /** + * Gets all field names for a given entity type and bundle. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type for which to get all field names. + * @param string $bundle + * The bundle for which to get all field names. + * + * @return string[] + * All field names. + */ + public function getAllFieldNames(EntityTypeInterface $entity_type, $bundle) { + if ($entity_type instanceof ContentEntityTypeInterface) { + return array_keys($this->entityFieldManager->getFieldDefinitions($entity_type->id(), $bundle)); + } + elseif ($entity_type instanceof ConfigEntityTypeInterface) { + // @todo Uncomment the first line, remove everything else once https://www.drupal.org/project/drupal/issues/2483407 lands. + // return array_keys($entity_type->getPropertiesToExport()); + $export_properties = $entity_type->getPropertiesToExport(); + if ($export_properties !== NULL) { + return array_keys($export_properties); + } + else { + return ['id', 'type', 'uuid', '_core']; + } + } + else { + throw new \LogicException("Only content and config entity types are supported."); + } + } + + /** + * Gets the field mapping for the given field names and entity type + bundle. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type for which to get the field mapping. + * @param string $bundle + * The bundle to assess. + * + * @return array + * An array with: + * - keys are (real/internal) field names + * - values are either FALSE (indicating the field is not exposed despite + * not being internal), TRUE (indicating the field should be exposed under + * its internal name) or a string (indicating the field should not be + * exposed using its internal name, but the name specified in the string) + */ + protected function getFieldMapping(EntityTypeInterface $entity_type, $bundle) { + $field_names = $this->getAllFieldNames($entity_type, $bundle); + assert($entity_type instanceof ContentEntityTypeInterface || $entity_type instanceof ConfigEntityTypeInterface); + assert(is_string($bundle) && !empty($bundle), 'A bundle ID is required. Bundleless entity types should pass the entity type ID again.'); + + $mapping = []; + + // JSON:API resource identifier objects are sufficient to identify + // entities. By exposing all fields as attributes, we expose unwanted, + // confusing or duplicate information: + // - exposing an entity's ID (which is not a UUID) is bad, but it's + // necessary for certain Drupal-coupled clients, so we alias it. + // - exposing its UUID as an attribute is useless (it's already part of + // the mandatory "id" attribute in JSON:API) + // - exposing its revision ID as an attribute will compete with any profile + // defined meta members used for resource object versioning. + // @see http://jsonapi.org/format/#document-resource-identifier-objects + $mapping[$entity_type->getKey('uuid')] = FALSE; + if ($id_field_name = $entity_type->getKey('id')) { + $mapping[$id_field_name] = "drupal_internal__$id_field_name"; + } + if ($entity_type->isRevisionable() && ($revision_id_field_name = $entity_type->getKey('revision'))) { + $mapping[$revision_id_field_name] = "drupal_internal__$revision_id_field_name"; + } + if ($entity_type instanceof ConfigEntityTypeInterface) { + // The '_core' key is reserved by Drupal core to handle complex edge cases + // correctly. Data in the '_core' key is irrelevant to clients reading + // configuration, and is not allowed to be set by clients writing + // configuration: it is for Drupal core only, and managed by Drupal core. + // @see https://www.drupal.org/node/2653358 + $mapping['_core'] = FALSE; + } + + // For all other fields, use their internal field name also as their public + // field name. Unless they're called "id" or "type": those names are + // reserved by the JSON:API spec. + // @see http://jsonapi.or + foreach (array_diff($field_names, array_keys($mapping)) as $field_name) { + if ($field_name === 'id' || $field_name === 'type') { + $alias = $entity_type->id() . '_' . $field_name; + if (isset($field_names[$alias])) { + throw new \LogicException('The generated alias conflicts with an existing field. Please report this in the JSON:API issue queue!'); + } + $mapping[$field_name] = $alias; + continue; + } + + // The default, which applies to most fields: expose as-is. + $mapping[$field_name] = TRUE; + } + + return $mapping; + } + } diff --git a/src/ResourceType/ResourceType.php b/src/ResourceType/ResourceType.php index 2ad6802..86da864 100644 --- a/src/ResourceType/ResourceType.php +++ b/src/ResourceType/ResourceType.php @@ -2,11 +2,6 @@ namespace Drupal\jsonapi\ResourceType; -use Drupal\Component\Assertion\Inspector; -use Drupal\Core\Config\Entity\ConfigEntityTypeInterface; -use Drupal\Core\Entity\ContentEntityTypeInterface; -use Drupal\Core\Entity\EntityTypeInterface; - /** * Value object containing all metadata for a JSON:API resource type. * @@ -24,11 +19,11 @@ use Drupal\Core\Entity\EntityTypeInterface; class ResourceType { /** - * The entity type. + * The entity type ID. * - * @var \Drupal\Core\Entity\EntityTypeInterface + * @var string */ - protected $entityType; + protected $entityTypeId; /** * The bundle ID. @@ -80,50 +75,19 @@ class ResourceType { protected $isVersionable; /** - * The list of fields on the underlying entity type + bundle. + * The resource fields keyed by internal field name. * - * @var string[] + * @var \Drupal\jsonapi\ResourceType\ResourceFieldInterface[] */ protected $fields; /** - * The list of disabled fields. Disabled by default: uuid, id, type. - * - * @var string[] - * - * @see \Drupal\jsonapi\ResourceType\ResourceTypeRepository::getFieldMapping() - */ - protected $disabledFields; - - /** - * The mapping for field aliases: keys=internal names, values=public names. + * A map of public field names to internal field names. * * @var string[] */ protected $fieldMapping; - /** - * The inverse of $fieldMapping. - * - * @var string[] - */ - protected $invertedFieldMapping; - - - /** - * The field definitions for this resource type. - * - * @var \Drupal\Core\Field\FieldDefinitionInterface[] - */ - protected $fieldDefinitions; - - /** - * The resource fields keyed by public field name. - * - * @var \Drupal\jsonapi\ResourceType\ResourceFieldInterface[] - */ - protected $resourceFields; - /** * Gets the entity type ID. * @@ -133,7 +97,7 @@ class ResourceType { * @see \Drupal\Core\Entity\EntityInterface::getEntityTypeId */ public function getEntityTypeId() { - return $this->entityType->id(); + return $this->entityTypeId; } /** @@ -178,7 +142,7 @@ class ResourceType { * @return \Drupal\jsonapi\ResourceType\ResourceFieldInterface */ public function getFieldByName($field_name) { - return $this->resourceFields[$field_name]; + return $this->fields[$this->getInternalName($field_name)]; } /** @@ -188,7 +152,7 @@ class ResourceType { * The fields. */ public function getResourceFields() { - return $this->resourceFields; + return array_values(array_filter($this->fields)); } /** @@ -230,9 +194,7 @@ class ResourceType { */ public function getPublicName($field_name) { // By default the entity field name is the public field name. - return isset($this->fieldMapping[$field_name]) - ? $this->fieldMapping[$field_name] - : $field_name; + return ($field = $this->getFieldByName($field_name)) ? $field->getPublicFieldName() : $field_name; } /** @@ -246,8 +208,8 @@ class ResourceType { */ public function getInternalName($field_name) { // By default the entity field name is the public field name. - return isset($this->invertedFieldMapping[$field_name]) - ? $this->invertedFieldMapping[$field_name] + return isset($this->fieldMapping[$field_name]) + ? $this->fieldMapping[$field_name] : $field_name; } @@ -269,7 +231,7 @@ class ResourceType { * otherwise. */ public function hasField($field_name) { - return in_array($field_name, $this->fields, TRUE); + return isset($this->fields[$field_name]); } /** @@ -286,7 +248,7 @@ class ResourceType { * of the data model. FALSE otherwise. */ public function isFieldEnabled($field_name) { - return $this->hasField($field_name) && !in_array($field_name, $this->disabledFields, TRUE); + return $this->hasField($field_name) && $this->fields[$field_name] !== FALSE; } /** @@ -363,14 +325,12 @@ class ResourceType { /** * Instantiates a ResourceType object. * - * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * @param string $entity_type_id * An entity type ID. * @param string $bundle * A bundle. * @param string $deserialization_target_class * The deserialization target class. - * @param array $field_definitions - * The field definitions for this resource type. * @param bool $internal * (optional) Whether the resource type should be internal. * @param bool $is_locatable @@ -379,12 +339,15 @@ class ResourceType { * (optional) Whether the resource type is mutable. * @param bool $is_versionable * (optional) Whether the resource type is versionable. + * @param array $fields + * An array of resource fields keyed by internal field name. An array value + * of FALSE means the field is disabled. */ - public function __construct(EntityTypeInterface $entity_type, $bundle, $deserialization_target_class, array $field_definitions, $internal = FALSE, $is_locatable = TRUE, $is_mutable = TRUE, $is_versionable = FALSE) { - $this->entityType = $entity_type; + public function __construct($entity_type_id, $bundle, $deserialization_target_class, $internal = FALSE, $is_locatable = TRUE, $is_mutable = TRUE, $is_versionable = FALSE, array $fields = []) { + $this->entityTypeId = $entity_type_id; $this->bundle = $bundle; + $this->fields = $fields; $this->deserializationTargetClass = $deserialization_target_class; - $this->fieldDefinitions = $field_definitions; $this->internal = $internal; $this->isLocatable = $is_locatable; $this->isMutable = $is_mutable; @@ -392,32 +355,11 @@ class ResourceType { $this->typeName = $this->bundle === '?' ? 'unknown' - : sprintf('%s--%s', $this->entityType->id(), $this->bundle); - - $field_mapping = static::getFieldMapping( - $this->getAllFieldNames(), - $entity_type, - $bundle - ); - $this->fields = array_keys($field_mapping); - $this->disabledFields = array_keys(array_filter($field_mapping, function ($v) { - return $v === FALSE; - })); - $this->fieldMapping = array_filter($field_mapping, 'is_string'); - $this->invertedFieldMapping = array_flip($this->fieldMapping); - $this->resourceFields = []; - foreach ($field_mapping as $field_name => $alias) { - if ($alias === FALSE) { - continue; - } - $resource_field_factory = \Drupal::service('jsonapi.resource_field.factory'); - assert($resource_field_factory instanceof TypedResourceFieldFactory); - $this->resourceFields[$field_name] = $resource_field_factory->createResourceField( - $this->entityType, - $this->bundle, - $field_name, - $alias - ); + : sprintf('%s--%s', $this->entityTypeId, $this->bundle); + + foreach ($this->fields as $internal_field_name => $field) { + $public_field_name = $field instanceof ResourceFieldInterface ? $field->getPublicFieldName() : $internal_field_name; + $this->fieldMapping[$public_field_name] = $internal_field_name; } } @@ -432,9 +374,7 @@ class ResourceType { */ public function setRelatableResourceTypes(array $relatable_resource_types) { $this->relatableResourceTypes = $relatable_resource_types; - $rels = array_filter($this->resourceFields, function (ResourceFieldInterface $field) { - return $field instanceof ResourceRelationship; - }); + $rels = $this->getResourceRelationships(); array_walk($rels, function (ResourceRelationship $rel) use ($relatable_resource_types) { $relatable = empty($relatable_resource_types[$rel->getPublicFieldName()]) ? [] @@ -470,10 +410,9 @@ class ResourceType { * @see self::getRelatableResourceTypes() */ public function getRelatableResourceTypesByField($field_name) { - $relatable_resource_types = $this->getRelatableResourceTypes(); - return isset($relatable_resource_types[$field_name]) ? - $relatable_resource_types[$field_name] : - []; + $field = $this->getFieldByName($field_name); + assert($field instanceof ResourceRelationship); + return $field->getRelatableResourceTypes(); } /** @@ -488,103 +427,4 @@ class ResourceType { return sprintf('/%s/%s', $this->getEntityTypeId(), $this->getBundle()); } - /** - * Gets all field names for a given entity type and bundle. - * - * @return string[] - * All field names. - */ - protected function getAllFieldNames() { - $entity_type = $this->entityType; - if ($entity_type instanceof ContentEntityTypeInterface) { - return array_keys($this->fieldDefinitions); - } - elseif ($entity_type instanceof ConfigEntityTypeInterface) { - // @todo Uncomment the first line, remove everything else once https://www.drupal.org/project/drupal/issues/2483407 lands. - // return array_keys($entity_type->getPropertiesToExport()); - $export_properties = $entity_type->getPropertiesToExport(); - if ($export_properties !== NULL) { - return array_keys($export_properties); - } - else { - return ['id', 'type', 'uuid', '_core']; - } - } - else { - throw new \LogicException("Only content and config entity types are supported."); - } - } - - /** - * Gets the field mapping for the given field names and entity type + bundle. - * - * @param string[] $field_names - * All field names on a bundle of the given entity type. - * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type - * The entity type for which to get the field mapping. - * @param string $bundle - * The bundle to assess. - * - * @return array - * An array with: - * - keys are (real/internal) field names - * - values are either FALSE (indicating the field is not exposed despite - * not being internal), TRUE (indicating the field should be exposed under - * its internal name) or a string (indicating the field should not be - * exposed using its internal name, but the name specified in the string) - */ - protected static function getFieldMapping(array $field_names, EntityTypeInterface $entity_type, $bundle) { - assert(Inspector::assertAllStrings($field_names)); - assert($entity_type instanceof ContentEntityTypeInterface || $entity_type instanceof ConfigEntityTypeInterface); - assert(is_string($bundle) && !empty($bundle), 'A bundle ID is required. Bundleless entity types should pass the entity type ID again.'); - - $mapping = []; - - // JSON:API resource identifier objects are sufficient to identify - // entities. By exposing all fields as attributes, we expose unwanted, - // confusing or duplicate information: - // - exposing an entity's ID (which is not a UUID) is bad, but it's - // necessary for certain Drupal-coupled clients, so we alias it. - // - exposing its UUID as an attribute is useless (it's already part of - // the mandatory "id" attribute in JSON:API) - // - exposing its revision ID as an attribute will compete with any profile - // defined meta members used for resource object versioning. - // @see http://jsonapi.org/format/#document-resource-identifier-objects - $mapping[$entity_type->getKey('uuid')] = FALSE; - if ($id_field_name = $entity_type->getKey('id')) { - $mapping[$id_field_name] = "drupal_internal__$id_field_name"; - } - if ($entity_type->isRevisionable() && ($revision_id_field_name = $entity_type->getKey('revision'))) { - $mapping[$revision_id_field_name] = "drupal_internal__$revision_id_field_name"; - } - if ($entity_type instanceof ConfigEntityTypeInterface) { - // The '_core' key is reserved by Drupal core to handle complex edge cases - // correctly. Data in the '_core' key is irrelevant to clients reading - // configuration, and is not allowed to be set by clients writing - // configuration: it is for Drupal core only, and managed by Drupal core. - // @see https://www.drupal.org/node/2653358 - $mapping['_core'] = FALSE; - } - - // For all other fields, use their internal field name also as their public - // field name. Unless they're called "id" or "type": those names are - // reserved by the JSON:API spec. - // @see http://jsonapi.org/format/#document-resource-object-fields - foreach (array_diff($field_names, array_keys($mapping)) as $field_name) { - if ($field_name === 'id' || $field_name === 'type') { - $alias = $entity_type->id() . '_' . $field_name; - if (isset($field_name[$alias])) { - throw new \LogicException('The generated alias conflicts with an existing field. Please report this in the JSON:API issue queue!'); - } - $mapping[$field_name] = $alias; - continue; - } - - // The default, which applies to most fields: expose as-is. - $mapping[$field_name] = TRUE; - } - - return $mapping; - } - } diff --git a/src/ResourceType/ResourceTypeRepository.php b/src/ResourceType/ResourceTypeRepository.php index 2a3eb9f..a04fc56 100644 --- a/src/ResourceType/ResourceTypeRepository.php +++ b/src/ResourceType/ResourceTypeRepository.php @@ -57,6 +57,13 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface { */ protected $entityFieldManager; + /** + * The JSON:API resource field factory. + * + * @var \Drupal\jsonapi\ResourceType\ResourceFieldFactory + */ + protected $resourceFieldFactory; + /** * The static cache backend. * @@ -87,6 +94,7 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface { $this->entityTypeManager = $entity_type_manager; $this->entityTypeBundleInfo = $entity_bundle_info; $this->entityFieldManager = $entity_field_manager; + $this->resourceFieldFactory = $resource_field_factory; $this->staticCache = $static_cache; } @@ -123,22 +131,15 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface { * A JSON:API resource type. */ protected function createResourceType(EntityTypeInterface $entity_type, $bundle) { - $field_definitions = []; - if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) { - $field_definitions = $this->entityFieldManager->getFieldDefinitions( - $entity_type->id(), - $bundle - ); - } return new ResourceType( - $entity_type, + $entity_type->id(), $bundle, $entity_type->getClass(), - $field_definitions, $entity_type->isInternal(), static::isLocatableResourceType($entity_type, $bundle), static::isMutableResourceType($entity_type, $bundle), - static::isVersionableResourceType($entity_type) + static::isVersionableResourceType($entity_type), + $this->resourceFieldFactory->createResourceFields($entity_type, $bundle) ); } @@ -298,19 +299,6 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface { }, $target_bundles); } - /** - * Gets all bundle IDs for a given entity type. - * - * @param string $entity_type_id - * The entity type for which to get bundles. - * - * @return string[] - * The bundle IDs. - */ - protected function getAllBundlesForEntityType($entity_type_id) { - return array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id)); - } - /** * Determines if a given field definition is a reference field. * @@ -322,7 +310,20 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface { * otherwise. */ protected function isReferenceFieldDefinition(FieldDefinitionInterface $field_definition) { - return ResourceFieldFactory::isReference($field_definition->getItemDefinition()); + return ResourceFieldFactory::isReference($field_definition); + } + + /** + * Gets all bundle IDs for a given entity type. + * + * @param string $entity_type_id + * The entity type for which to get bundles. + * + * @return string[] + * The bundle IDs. + */ + protected function getAllBundlesForEntityType($entity_type_id) { + return array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id)); } } diff --git a/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php b/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php index 33c9c1a..67a3012 100644 --- a/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php +++ b/tests/modules/jsonapi_test_collection_count/src/ResourceType/CountableResourceTypeRepository.php @@ -14,7 +14,6 @@ class CountableResourceTypeRepository extends ResourceTypeRepository { * {@inheritdoc} */ protected function createResourceType(EntityTypeInterface $entity_type, $bundle) { - $raw_fields = $this->getAllFieldNames($entity_type, $bundle); return new CountableResourceType( $entity_type->id(), $bundle, @@ -22,7 +21,8 @@ class CountableResourceTypeRepository extends ResourceTypeRepository { $entity_type->isInternal(), static::isLocatableResourceType($entity_type, $bundle), static::isMutableResourceType($entity_type, $bundle), - static::getFieldMapping($raw_fields, $entity_type, $bundle) + static::isVersionableResourceType($entity_type), + $this->resourceFieldFactory->createResourceFields($entity_type, $bundle) ); } diff --git a/tests/modules/jsonapi_test_field_aliasing/jsonapi_test_field_aliasing.services.yml b/tests/modules/jsonapi_test_field_aliasing/jsonapi_test_field_aliasing.services.yml index 184daa4..c3c4140 100644 --- a/tests/modules/jsonapi_test_field_aliasing/jsonapi_test_field_aliasing.services.yml +++ b/tests/modules/jsonapi_test_field_aliasing/jsonapi_test_field_aliasing.services.yml @@ -1,6 +1,6 @@ services: - jsonapi.resource_type.repository.jsonapi_test_field_aliasing: - class: Drupal\jsonapi_test_field_aliasing\ResourceType\AliasingResourceTypeRepository + jsonapi.resource_field.factory.jsonapi_test_field_aliasing: + class: Drupal\jsonapi_test_field_aliasing\ResourceType\AliasingResourceFieldFactory public: false - decorates: jsonapi.resource_type.repository - parent: jsonapi.resource_type.repository + decorates: jsonapi.resource_field.factory + parent: jsonapi.resource_field.factory diff --git a/tests/modules/jsonapi_test_field_aliasing/src/ResourceType/AliasingResourceTypeRepository.php b/tests/modules/jsonapi_test_field_aliasing/src/ResourceType/AliasingResourceFieldFactory.php similarity index 36% rename from tests/modules/jsonapi_test_field_aliasing/src/ResourceType/AliasingResourceTypeRepository.php rename to tests/modules/jsonapi_test_field_aliasing/src/ResourceType/AliasingResourceFieldFactory.php index 59850da..2765840 100644 --- a/tests/modules/jsonapi_test_field_aliasing/src/ResourceType/AliasingResourceTypeRepository.php +++ b/tests/modules/jsonapi_test_field_aliasing/src/ResourceType/AliasingResourceFieldFactory.php @@ -3,21 +3,21 @@ namespace Drupal\jsonapi_test_field_aliasing\ResourceType; use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\jsonapi\ResourceType\ResourceTypeRepository; +use Drupal\jsonapi\ResourceType\ResourceFieldFactory; /** * Provides a repository of JSON:API resource types with aliasable field names. */ -class AliasingResourceTypeRepository extends ResourceTypeRepository { +class AliasingResourceFieldFactory extends ResourceFieldFactory { /** * {@inheritdoc} */ - protected static function getFieldMapping(array $field_names, EntityTypeInterface $entity_type, $bundle) { - $mapping = parent::getFieldMapping($field_names, $entity_type, $bundle); - foreach ($field_names as $field_name) { - if (strpos($field_name, 'field_test_alias_') === 0) { - $mapping[$field_name] = 'field_test_alias'; + protected function getFieldMapping(EntityTypeInterface $entity_type, $bundle) { + $mapping = parent::getFieldMapping($entity_type, $bundle); + foreach ($mapping as $internal_field_name => $mapped_field_name) { + if (strpos($internal_field_name, 'field_test_alias_') === 0) { + $mapping[$internal_field_name] = 'field_test_alias'; } } return $mapping;