Issue #3057545 by acbramley, hchonov, bbrala, bradjones1, larowlan, yogeshmpawar, Leon Kessler, gease, joachim, gabesullice, kfritsche, jibran, Wim Leers, Berdir, smustgrave, alexpott, catch: ResourceTypeRepository wrongly assumes that all entity reference fields have the setting "target_type"

merge-requests/3241/head
Lee Rowlands 2023-07-03 16:40:34 +10:00
parent 0733716c75
commit da5ea0aeda
No known key found for this signature in database
GPG Key ID: 2B829A3DF9204DC4
10 changed files with 252 additions and 29 deletions

View File

@ -13,7 +13,6 @@ use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
use Drupal\Core\Form\FormStateInterface;
@ -42,7 +41,7 @@ use Drupal\Core\Validation\Plugin\Validation\Constraint\AllowedValuesConstraint;
* list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList",
* )
*/
class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface {
class EntityReferenceItem extends EntityReferenceItemBase implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface {
/**
* {@inheritdoc}
@ -778,4 +777,19 @@ class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterf
return $options;
}
/**
* {@inheritdoc}
*/
public static function getReferenceableBundles(FieldDefinitionInterface $field_definition): array {
$settings = $field_definition->getSettings();
$target_type_id = $settings['target_type'];
$handler_settings = $settings['handler_settings'];
$has_target_bundles = isset($handler_settings['target_bundles']) && !empty($handler_settings['target_bundles']);
$target_bundles = $has_target_bundles
? $handler_settings['target_bundles']
: array_keys(\Drupal::service('entity_type.bundle.info')->getBundleInfo($target_type_id));
return [$target_type_id => $target_bundles];
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Drupal\Core\Field\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
/**
* Base class for field items referencing other entities.
*
* Any field type that is an entity reference should extend from this class in
* order to remain backwards compatible with any changes added in the future
* to EntityReferenceItemInterface.
*/
abstract class EntityReferenceItemBase extends FieldItemBase implements EntityReferenceItemInterface {
}

View File

@ -0,0 +1,29 @@
<?php
namespace Drupal\Core\Field\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Interface definition for field items referencing other entities.
*
* Field items should extend \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemBase.
*/
interface EntityReferenceItemInterface {
/**
* Returns the referenceable entity types and bundles.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition for which to retrieve the referenceable entity
* types and bundles.
*
* @return array
* An array of referenceable bundles where the array is keyed by the entity
* type ID, with values an array of bundle names. (It is a single-value
* array with the entity type ID if the entity type does not implement
* bundles.)
*/
public static function getReferenceableBundles(FieldDefinitionInterface $field_definition): array;
}

View File

@ -6,7 +6,8 @@ use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface;
use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
use Drupal\jsonapi\Access\EntityAccessChecker;
use Drupal\jsonapi\Context\FieldResolver;
use Drupal\jsonapi\Exception\EntityAccessDeniedHttpException;
@ -137,11 +138,32 @@ class IncludeResolver {
$includes = IncludedData::merge($includes, new IncludedData([$exception]));
continue;
}
$target_type = $field_list->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type');
assert(!empty($target_type));
foreach ($field_list as $field_item) {
assert($field_item instanceof EntityReferenceItem);
$references[$target_type][] = $field_item->get($field_item::mainPropertyName())->getValue();
if (is_subclass_of($field_list->getItemDefinition()->getClass(), EntityReferenceItemInterface::class)) {
foreach ($field_list as $field_item) {
if (!($field_item->getDataDefinition()->getPropertyDefinition('entity') instanceof DataReferenceDefinitionInterface)) {
continue;
}
if (!($field_item->entity instanceof EntityInterface)) {
continue;
}
// Support entity reference fields that don't have the referenced
// target type stored in settings.
$references[$field_item->entity->getEntityTypeId()][] = $field_item->get($field_item::mainPropertyName())->getValue();
}
}
else {
@trigger_error(
sprintf('Entity reference field items not implementing %s is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3279140', EntityReferenceItemInterface::class),
E_USER_DEPRECATED
);
$target_type = $field_list->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type');
if (!empty($target_type)) {
foreach ($field_list as $field_item) {
$references[$target_type][] = $field_item->get($field_item::mainPropertyName())->getValue();
}
}
}
}
foreach ($references as $target_type => $ids) {

View File

@ -15,6 +15,7 @@ use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Installer\InstallerKernel;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface;
use Drupal\Core\TypedData\DataReferenceTargetDefinition;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
@ -439,29 +440,45 @@ class ResourceTypeRepository implements ResourceTypeRepositoryInterface {
protected function getRelatableResourceTypesFromFieldDefinition(FieldDefinitionInterface $field_definition, array $resource_types) {
$item_definition = $field_definition->getItemDefinition();
$entity_type_id = $item_definition->getSetting('target_type');
$handler_settings = $item_definition->getSetting('handler_settings');
$target_bundles = empty($handler_settings['target_bundles']) ? $this->getAllBundlesForEntityType($entity_type_id) : $handler_settings['target_bundles'];
$relatable_resource_types = [];
$item_class = $item_definition->getClass();
if (is_subclass_of($item_class, EntityReferenceItemInterface::class)) {
$target_type_bundles = $item_class::getReferenceableBundles($field_definition);
}
else {
@trigger_error(
sprintf('Entity reference field items not implementing %s is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3279140', EntityReferenceItemInterface::class),
E_USER_DEPRECATED
);
$handler_settings = $item_definition->getSetting('handler_settings');
foreach ($target_bundles as $target_bundle) {
if ($resource_type = static::lookupResourceType($resource_types, $entity_type_id, $target_bundle)) {
$relatable_resource_types[] = $resource_type;
}
// Do not warn during the site installation since system integrity
// is not guaranteed in this period and the warnings may pop up falsy,
// adding confusion to the process.
elseif (!InstallerKernel::installationAttempted()) {
trigger_error(
sprintf(
'The "%s" at "%s:%s" references the "%s:%s" entity type that does not exist. Please take action.',
$field_definition->getName(),
$field_definition->getTargetEntityTypeId(),
$field_definition->getTargetBundle(),
$entity_type_id,
$target_bundle
),
E_USER_WARNING
);
$has_target_bundles = isset($handler_settings['target_bundles']) && !empty($handler_settings['target_bundles']);
$target_bundles = $has_target_bundles ? $handler_settings['target_bundles'] : $this->getAllBundlesForEntityType($entity_type_id);
$target_type_bundles = [$entity_type_id => $target_bundles];
}
foreach ($target_type_bundles as $entity_type_id => $target_bundles) {
foreach ($target_bundles as $target_bundle) {
if ($resource_type = static::lookupResourceType($resource_types, $entity_type_id, $target_bundle)) {
$relatable_resource_types[] = $resource_type;
continue;
}
// Do not warn during site installation since system integrity
// is not guaranteed during this period and may cause confusing and
// unnecessary warnings.
if (!InstallerKernel::installationAttempted()) {
trigger_error(
sprintf(
'The "%s" at "%s:%s" references the "%s:%s" entity type that does not exist. Please take action.',
$field_definition->getName(),
$field_definition->getTargetEntityTypeId(),
$field_definition->getTargetBundle(),
$entity_type_id,
$target_bundle
),
E_USER_WARNING
);
}
}
}

View File

@ -0,0 +1,3 @@
name: 'JSON API test deprecated reference field types'
type: module
package: Testing

View File

@ -0,0 +1,24 @@
<?php
/**
* @file
* Contains hook implementations for the jsonapi_test_reference_types module.
*/
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Implements hook_entity_base_field_info().
*/
function jsonapi_test_reference_types_entity_base_field_info(EntityTypeInterface $entity_type) {
// Add a field of the deprecated reference type to nodes.
if ($entity_type->id() === 'node') {
$fields = [];
$fields['deprecated_reference'] = BaseFieldDefinition::create('jsonapi_test_deprecated_reference')
->setLabel(t('Reference'))
->setDescription(t('Deprecated reference field.'));
return $fields;
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace Drupal\jsonapi_test_reference_types\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataReferenceTargetDefinition;
/**
* Entity reference field type which doesn't implement the standard interface.
*
* This is to test the handling of deprecated fields which do not implement
* \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface.
*
* @see https://www.drupal.org/node/3279140
* @see \Drupal\Tests\jsonapi\Kernel\ResourceType\RelatedResourceTypesTest::testGetRelatableResourceTypesFromFieldDefinitionEntityReferenceFieldDeprecated()
*
* @todo Remove this in Drupal 11 https://www.drupal.org/project/drupal/issues/3353314.
*
* @FieldType(
* id = "jsonapi_test_deprecated_reference",
* )
*/
class DeprecatedReferenceItem extends FieldItemBase {
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['target_id'] = DataReferenceTargetDefinition::create('integer')
->setLabel(new TranslatableMarkup('Target ID'))
->setSetting('unsigned', TRUE);
return $properties;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$schema = [
'columns' => [
'target_id' => [
'description' => 'The ID of the target entity.',
'type' => 'int',
'unsigned' => TRUE,
],
],
'indexes' => [
'target_id' => ['target_id'],
],
];
return $schema;
}
/**
* {@inheritdoc}
*/
public static function mainPropertyName() {
return 'target_id';
}
}

View File

@ -526,4 +526,26 @@ class NodeTest extends ResourceTestBase {
$this->assertContains('user.node_grants:view', explode(' ', $response->getHeader('X-Drupal-Cache-Contexts')[0]));
}
/**
* Tests deprecated entity reference items.
*
* @group legacy
*/
public function testDeprecatedEntityReferenceFieldItem(): void {
\Drupal::service('module_installer')->install(['jsonapi_test_reference_types']);
$this->setUpAuthorization('GET');
// @todo Remove line below in favor of commented line in https://www.drupal.org/project/drupal/issues/2878463.
$url = Url::fromRoute(sprintf('jsonapi.%s.individual', static::$resourceTypeName), ['entity' => $this->entity->uuid()]);
// $url = $this->entity->toUrl('jsonapi');
$query = ['include' => 'deprecated_reference'];
$url->setOption('query', $query);
$request_options = [];
$request_options[RequestOptions::HEADERS]['Accept'] = 'application/vnd.api+json';
$request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions());
$this->expectDeprecation('Entity reference field items not implementing Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3279140');
$this->request('GET', $url, $request_options);
}
}

View File

@ -211,4 +211,15 @@ class RelatedResourceTypesTest extends JsonapiKernelTestBase {
}
}
/**
* Test the deprecation error on entity reference fields.
*
* @group legacy
*/
public function testGetRelatableResourceTypesFromFieldDefinitionEntityReferenceFieldDeprecated(): void {
\Drupal::service('module_installer')->install(['jsonapi_test_reference_types']);
$this->expectDeprecation('Entity reference field items not implementing Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItemInterface is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3279140');
$this->resourceTypeRepository->all();
}
}