From e8167e25eebaab17f68d5c3d4b5c7b0d766bf3f5 Mon Sep 17 00:00:00 2001 From: Al Munnings <17699-almunnings@users.noreply.drupalcode.org> Date: Thu, 4 Jan 2024 06:18:13 +0000 Subject: [PATCH] Issue #3410650 by almunnings: Support dynamic entity reference module --- .../SchemaType/RouteEntityUnion.php | 12 +- .../Plugin/GraphQLCompose/SchemaType/View.php | 18 +-- phpstan-baseline.neon | 4 + .../DataProducer/EntityUnpublishedFilter.php | 12 +- .../DataProducer/FieldEntityReference.php | 148 ++++++++++++++++++ .../FieldType/DynamicEntityReferenceItem.php | 44 ++++++ .../FieldType/EntityReferenceItem.php | 70 +++------ .../EntityReferenceRevisionsItem.php | 9 -- .../GraphQLCompose/FieldUnionInterface.php | 10 +- src/Plugin/GraphQLCompose/FieldUnionTrait.php | 84 +++++++--- .../GraphQLComposeEntityTypeBase.php | 56 ++++--- .../GraphQLCompose/SchemaType/ImageType.php | 8 +- tests/src/Functional/EntityUnionTest.php | 4 +- tests/src/Functional/UnsupportedTest.php | 4 +- 14 files changed, 349 insertions(+), 134 deletions(-) create mode 100644 src/Plugin/GraphQL/DataProducer/FieldEntityReference.php create mode 100644 src/Plugin/GraphQLCompose/FieldType/DynamicEntityReferenceItem.php diff --git a/modules/graphql_compose_routes/src/Plugin/GraphQLCompose/SchemaType/RouteEntityUnion.php b/modules/graphql_compose_routes/src/Plugin/GraphQLCompose/SchemaType/RouteEntityUnion.php index 8e6f490d..b3f12610 100644 --- a/modules/graphql_compose_routes/src/Plugin/GraphQLCompose/SchemaType/RouteEntityUnion.php +++ b/modules/graphql_compose_routes/src/Plugin/GraphQLCompose/SchemaType/RouteEntityUnion.php @@ -6,7 +6,6 @@ namespace Drupal\graphql_compose_routes\Plugin\GraphQLCompose\SchemaType; use Drupal\graphql_compose\Plugin\GraphQLCompose\GraphQLComposeSchemaTypeBase; use Drupal\graphql_compose\Wrapper\EntityTypeWrapper; -use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; /** @@ -24,13 +23,18 @@ class RouteEntityUnion extends GraphQLComposeSchemaTypeBase { public function getTypes(): array { $types = []; + $union_types = array_map( + fn(EntityTypeWrapper $bundle): string => $bundle->getTypeSdl(), + $this->getUnionBundles() + ); + $types[] = new UnionType([ 'name' => $this->getPluginId(), 'description' => (string) $this->t('A list of possible entities that can be returned by URL.'), 'types' => fn() => array_map( - fn(EntityTypeWrapper $bundle): Type => static::type($bundle->getTypeSdl()), - $this->getUnionBundles() - ) ?: [static::type('UnsupportedType')], + static::type(...), + $union_types ?: ['UnsupportedType'] + ), ]); return $types; diff --git a/modules/graphql_compose_views/src/Plugin/GraphQLCompose/SchemaType/View.php b/modules/graphql_compose_views/src/Plugin/GraphQLCompose/SchemaType/View.php index 730e09a2..096e8102 100644 --- a/modules/graphql_compose_views/src/Plugin/GraphQLCompose/SchemaType/View.php +++ b/modules/graphql_compose_views/src/Plugin/GraphQLCompose/SchemaType/View.php @@ -170,9 +170,9 @@ class View extends GraphQLComposeSchemaTypeBase { 'name' => 'ViewResultUnion', 'description' => (string) $this->t('All available view result types.'), 'types' => fn() => array_map( - fn(string $result_name): Type => static::type($result_name), - $union_types - ) ?: [static::type('UnsupportedType')], + static::type(...), + $union_types ?: ['UnsupportedType'] + ), ]); return $types; @@ -537,15 +537,13 @@ class View extends GraphQLComposeSchemaTypeBase { // Create exposed input for contextual filters. $contextual_filters = $display->getOption('arguments') ?: []; - $contextual_fields = array_map( - fn () => Type::string(), - $contextual_filters - ); - - if (!empty($contextual_fields)) { + if ($contextual_filters) { $types[] = new InputObjectType([ 'name' => $display->getGraphQlContextualFilterInputName(), - 'fields' => fn() => $contextual_fields, + 'fields' => fn() => array_map( + fn () => Type::string(), + $contextual_filters, + ), ]); } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ac9262aa..bb1cda78 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -19,3 +19,7 @@ parameters: message: "#^Property Drupal\\\\graphql_compose\\\\Plugin\\\\GraphQLCompose\\\\FieldType\\\\ImageItem\\:\\:\\$sanitizer has unknown class enshrined\\\\svgSanitize\\\\Sanitizer as its type\\.$#" count: 1 path: src/Plugin/GraphQLCompose/FieldType/ImageItem.php + - + message: "#^Call to static method getTargetTypes\\(\\) on an unknown class Drupal\\\\dynamic_entity_reference\\\\Plugin\\\\Field\\\\FieldType\\\\DynamicEntityReferenceItem\\.$#" + count: 1 + path: src/Plugin/GraphQLCompose/FieldType/DynamicEntityReferenceItem.php diff --git a/src/Plugin/GraphQL/DataProducer/EntityUnpublishedFilter.php b/src/Plugin/GraphQL/DataProducer/EntityUnpublishedFilter.php index ce09147d..904270e3 100644 --- a/src/Plugin/GraphQL/DataProducer/EntityUnpublishedFilter.php +++ b/src/Plugin/GraphQL/DataProducer/EntityUnpublishedFilter.php @@ -12,7 +12,7 @@ use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Remove unpublished entities from a results array. + * Remove unpublished entities (ignoring permissions) from a results array. * * @DataProducer( * id = "entity_unpublished_filter", @@ -53,7 +53,7 @@ class EntityUnpublishedFilter extends DataProducerPluginBase implements Containe } /** - * Remove unpublished entities from a results array. + * Remove unpublished entities (ignoring permissions) from a results array. * * @param array $results * The results form a field plugin type to process. @@ -61,12 +61,16 @@ class EntityUnpublishedFilter extends DataProducerPluginBase implements Containe * The cache context. * * @return mixed - * Results from resolution. Array for multiple. + * The filtered results. */ public function resolve(array $results, FieldContext $context) { $settings = $this->configFactory->get('graphql_compose.settings'); + $exclude = $settings->get('settings.exclude_unpublished') ?: FALSE; - if ($settings->get('settings.exclude_unpublished')) { + // Don't exclude unpublished in preview mode. + $preview = $context->getContextValue('preview'); + + if ($exclude && !$preview) { $results = array_filter($results, function ($result) { return ($result instanceof EntityPublishedInterface) ? $result->isPublished() diff --git a/src/Plugin/GraphQL/DataProducer/FieldEntityReference.php b/src/Plugin/GraphQL/DataProducer/FieldEntityReference.php new file mode 100644 index 00000000..09f7b4a2 --- /dev/null +++ b/src/Plugin/GraphQL/DataProducer/FieldEntityReference.php @@ -0,0 +1,148 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\graphql_compose\Plugin\GraphQL\DataProducer; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Field\EntityReferenceFieldItemListInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\graphql\GraphQL\Execution\FieldContext; +use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase; +use Drupal\graphql\Plugin\GraphQL\DataProducer\Field\EntityReferenceTrait; +use GraphQL\Deferred; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Produce the referenced entities from a field. + * + * Note: If you find a nice way to put this into a buffer, that can be used + * to resolve multiple entity types and revisions at once, please contrib! + * + * Deferred with referencedEntities covers a lot of use cases. + * + * @DataProducer( + * id = "field_entity_reference", + * name = @Translation("Field Entity Reference"), + * description = @Translation("Return entity references from a field."), + * produces = @ContextDefinition("mixed", + * label = @Translation("Referenced entities"), + * ), + * consumes = { + * "entity" = @ContextDefinition("entity", + * label = @Translation("Entity instance"), + * ), + * "field" = @ContextDefinition("string", + * label = @Translation("Field name"), + * ), + * "types" = @ContextDefinition("any", + * label = @Translation("Entity types allowed to load"), + * multiple = TRUE, + * ), + * "language" = @ContextDefinition("string", + * label = @Translation("Language to use"), + * required = FALSE, + * ), + * }, + * ) + */ +class FieldEntityReference extends DataProducerPluginBase implements ContainerFactoryPluginInterface { + + use EntityReferenceTrait; + + /** + * Constructs a new EntityLoadByUuidOrId instance. + * + * @param array $configuration + * The plugin configuration. + * @param string $plugin_id + * The plugin ID. + * @param mixed $plugin_definition + * The plugin definition. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity type manager. + */ + public function __construct( + array $configuration, + string $plugin_id, + $plugin_definition, + protected EntityTypeManagerInterface $entityTypeManager + ) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity_type.manager'), + ); + } + + /** + * Finds the requested field on the entity. + * + * @param \Drupal\Core\Entity\EntityInterface|null $entity + * The entity to resolve a field fields off. + * @param string $field + * The field to resolve entities off. + * @param array $types + * The union entity types allowed to load. + * @param string|null $language + * The language to use. + * @param \Drupal\graphql\GraphQL\Execution\FieldContext $context + * The field context. + * + * @return \GraphQL\Deferred|array + * The resolves entities from the field. + */ + public function resolve(?EntityInterface $entity, string $field, array $types, ?string $language, FieldContext $context): Deferred|array { + + if (!$entity instanceof FieldableEntityInterface || !$entity->hasField($field)) { + return []; + } + + $field = $entity->get($field); + if (!$field instanceof EntityReferenceFieldItemListInterface || !$field->access('view')) { + return []; + } + + // Resolve the entities. + return new Deferred(function () use ($field, $types, $language, $context) { + $entities = $field->referencedEntities(); + + if ($language) { + $entities = $this->getTranslated($entities, $language); + } + + foreach ($entities as $entity) { + $context->addCacheableDependency($entity); + } + + $entities = $this->filterAccessible($entities, NULL, 'view', $context); + + // Add a list cache tags for each empty type. + foreach ($types as $type) { + $has_entity = array_filter( + $entities, + fn ($entity) => $entity->getEntityTypeId() === $type + ); + + if (!$has_entity) { + $type = $this->entityTypeManager->getDefinition($type); + $tags = $type->getListCacheTags(); + $context->addCacheTags($tags); + } + } + + return $entities; + }); + } + +} diff --git a/src/Plugin/GraphQLCompose/FieldType/DynamicEntityReferenceItem.php b/src/Plugin/GraphQLCompose/FieldType/DynamicEntityReferenceItem.php new file mode 100644 index 00000000..6978aa6b --- /dev/null +++ b/src/Plugin/GraphQLCompose/FieldType/DynamicEntityReferenceItem.php @@ -0,0 +1,44 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\graphql_compose\Plugin\GraphQLCompose\FieldType; + +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceItem as DynamicEntityReferenceItemBase; + +/** + * {@inheritdoc} + * + * @GraphQLComposeFieldType( + * id = "dynamic_entity_reference" + * ) + */ +class DynamicEntityReferenceItem extends EntityReferenceItem { + + /** + * {@inheritdoc} + * + * Force to be non-generic to get a unique union type. + */ + public function isGenericUnion(): bool { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getTypeSdl(): string { + return $this->getUnionTypeSdl(); + } + + /** + * {@inheritdoc} + */ + protected function getUnionTargetTypes(FieldDefinitionInterface $field_definition): array { + return DynamicEntityReferenceItemBase::getTargetTypes( + $field_definition->getSettings() + ); + } + +} diff --git a/src/Plugin/GraphQLCompose/FieldType/EntityReferenceItem.php b/src/Plugin/GraphQLCompose/FieldType/EntityReferenceItem.php index c68fc246..379afa27 100644 --- a/src/Plugin/GraphQLCompose/FieldType/EntityReferenceItem.php +++ b/src/Plugin/GraphQLCompose/FieldType/EntityReferenceItem.php @@ -8,7 +8,6 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\TypedData\TranslatableInterface; use Drupal\graphql\GraphQL\Resolver\Composite; use Drupal\graphql\GraphQL\ResolverBuilder; -use Drupal\graphql_compose\Plugin\GraphQL\DataProducer\FieldProducerTrait; use Drupal\graphql_compose\Plugin\GraphQLCompose\FieldUnionInterface; use Drupal\graphql_compose\Plugin\GraphQLCompose\FieldUnionTrait; use Drupal\graphql_compose\Plugin\GraphQLCompose\GraphQLComposeFieldTypeBase; @@ -23,61 +22,32 @@ use Drupal\graphql_compose\Plugin\GraphQLCompose\GraphQLComposeFieldTypeBase; class EntityReferenceItem extends GraphQLComposeFieldTypeBase implements FieldUnionInterface { use FieldUnionTrait; - use FieldProducerTrait { - getProducers as getProducersTrait; - } - - /** - * Producer buffer. - * - * Producer to use when not in a preview context. - * - * @var string - */ - protected string $producerBuffer = 'entity_reference'; - - /** - * Producer property. - * - * Producer to use when in a preview context. - * Override the producer trait value property. - * Resolve direct from the entity field value. - * - * @var string - * - * @see \Drupal\graphql_compose\Plugin\GraphQL\DataProducer\FieldProducerPlugin::resolve() - */ - public string $producerProperty = 'entity'; /** * {@inheritdoc} */ public function getProducers(ResolverBuilder $builder): Composite { - return $builder->compose( - $builder->cond([ - [ - // If in preview, resolve direct from the provided entity. - $builder->fromContext('preview'), - $this->getProducersTrait($builder), - ], [ - // Use GraphQL module producers with buffers. - $builder->fromValue(TRUE), - $builder->compose( - $builder->produce($this->producerBuffer) - ->map('field', $builder->fromValue($this->getFieldName())) - ->map('entity', $builder->fromParent()) - ->map('language', $builder->callback( - fn (EntityInterface $entity) => ($entity instanceof TranslatableInterface) - ? $entity->language()->getId() - : NULL - )), - // Remove unpublished entities (optional). - $builder->produce('entity_unpublished_filter') - ->map('value', $builder->fromParent()), - ), - ], - ]), + $field_name = $this->getFieldName(); + + $target_types = $this->getUnionTargetTypes( + $this->getFieldDefinition() + ); + + return $builder->compose( + $builder->produce('field_entity_reference') + ->map('entity', $builder->fromParent()) + ->map('field', $builder->fromValue($field_name)) + ->map('types', $builder->fromValue($target_types)) + ->map('language', $builder->callback( + fn (EntityInterface $entity) => ($entity instanceof TranslatableInterface) + ? $entity->language()->getId() + : NULL + )), + + // Optionally remove any unpublished references. + $builder->produce('entity_unpublished_filter') + ->map('value', $builder->fromParent()), ); } diff --git a/src/Plugin/GraphQLCompose/FieldType/EntityReferenceRevisionsItem.php b/src/Plugin/GraphQLCompose/FieldType/EntityReferenceRevisionsItem.php index a914141e..4a051f93 100644 --- a/src/Plugin/GraphQLCompose/FieldType/EntityReferenceRevisionsItem.php +++ b/src/Plugin/GraphQLCompose/FieldType/EntityReferenceRevisionsItem.php @@ -13,13 +13,4 @@ namespace Drupal\graphql_compose\Plugin\GraphQLCompose\FieldType; */ class EntityReferenceRevisionsItem extends EntityReferenceItem { - /** - * Producer buffer. - * - * Producer to use when not in preview. - * - * @var string - */ - protected string $producerBuffer = 'entity_reference_revisions'; - } diff --git a/src/Plugin/GraphQLCompose/FieldUnionInterface.php b/src/Plugin/GraphQLCompose/FieldUnionInterface.php index dec60750..db60af1a 100644 --- a/src/Plugin/GraphQLCompose/FieldUnionInterface.php +++ b/src/Plugin/GraphQLCompose/FieldUnionInterface.php @@ -34,10 +34,14 @@ interface FieldUnionInterface { public function getUnionTypeSdl(): string; /** - * Get the target types for target type unions. + * Get the target schema types keyed by entity type and bundle. * - * @return array - * Schema types available to union. + * The result is an array of types in the format: TYPE_ID:BUNDLE_ID => SDL + * EG: node:page => NodePage + * user:user => User. + * + * @return string[] + * Schema types available to union on the field. */ public function getUnionTypeMapping(): array; diff --git a/src/Plugin/GraphQLCompose/FieldUnionTrait.php b/src/Plugin/GraphQLCompose/FieldUnionTrait.php index b1d5b201..770d935e 100644 --- a/src/Plugin/GraphQLCompose/FieldUnionTrait.php +++ b/src/Plugin/GraphQLCompose/FieldUnionTrait.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Drupal\graphql_compose\Plugin\GraphQLCompose; +use Drupal\Core\Field\FieldDefinitionInterface; + use function Symfony\Component\String\u; /** @@ -109,13 +111,13 @@ trait FieldUnionTrait { } // Ensure we have something to map. - if (!$mapping = $this->getUnionTypeMapping()) { + if (!$union_mapping = $this->getUnionTypeMapping()) { return 'UnsupportedType'; } // If single type, return first type configured. if ($this->isSingleUnion()) { - return reset($mapping); + return reset($union_mapping); } // Generate a new type for the field. @@ -138,15 +140,9 @@ trait FieldUnionTrait { * Schema types available to union. */ public function getUnionTypeMapping(): array { - /** @var \Drupal\Core\Field\FieldDefinition $field_definition */ - $field_definition = $this->getFieldDefinition(); - - if (!$field_definition) { - return []; - } - - // Reduce lookups. $mapping = &drupal_static('graphql_compose_union_type_mapping', []); + + $field_definition = $this->getFieldDefinition(); $field_id = $field_definition->getUniqueIdentifier(); if (isset($mapping[$field_id])) { @@ -155,39 +151,79 @@ trait FieldUnionTrait { $mapping[$field_id] = []; - // Unknown target type. - if (!$target_type_id = $field_definition->getSetting('target_type')) { - return []; + $target_types = $this->getUnionTargetTypes($field_definition); + foreach ($target_types as $entity_type_id) { + $mapping[$field_id] += $this->getUnionTargetBundles($field_definition, $entity_type_id); } - // Entity type plugin not defined. - if (!$plugin_instance = $this->gqlEntityTypeManager->getPluginInstance($target_type_id)) { + return $mapping[$field_id]; + } + + /** + * Get the target types for target type unions. + * + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition. + * + * @return string[] + * The target entity types. + */ + protected function getUnionTargetTypes(FieldDefinitionInterface $field_definition): array { + $target_types = $field_definition->getSetting('target_type'); + return is_array($target_types) ? $target_types : [$target_types]; + } + + /** + * Get the target bundles for entity type. + * + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition. + * @param string $entity_type_id + * The entity type id. + * + * @return string[] + * The target entity types available to map. + */ + protected function getUnionTargetBundles(FieldDefinitionInterface $field_definition, string $entity_type_id) { + $result = []; + + // This entity type is not supported by graphql compose. + $plugin_instance = $this->gqlEntityTypeManager->getPluginInstance($entity_type_id); + if (!$plugin_instance) { return []; } // Get the target configuration from the field. $handler_settings = $field_definition->getSetting('handler_settings'); + if (!$handler_settings) { + // Look for type specific config (eg dynamic entity reference) + $type_settings = $field_definition->getSetting($entity_type_id); + $handler_settings = $type_settings['handler_settings'] ?? []; + } + + $target_bundles = array_keys($handler_settings['target_bundles'] ?? [] ?: []); + $all_bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id)); - $all_bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($target_type_id)); - $target_bundles = array_keys($handler_settings['target_bundles'] ?? []); + // Some plugins allow you to negate your selection. + $negate = (bool) ($handler_settings['negate'] ?? FALSE); - // Paragraphs allows you to negate. - if (!empty($handler_settings['negate'])) { + if ($negate) { + // Get the opposite of the selected bundles. $target_bundles = array_diff($all_bundles, $target_bundles); } else { - // Assumption none is all. + // Use "all" if nothing selected. $target_bundles = $target_bundles ?: $all_bundles; } // Limit mapping to enabled bundles within the entity type plugin. - foreach ($target_bundles as $target_bundle_id) { - if ($target_bundle = $plugin_instance->getBundle($target_bundle_id)) { - $mapping[$field_id][$target_bundle_id] = $target_bundle->getTypeSdl(); + foreach ($target_bundles as $bundle_id) { + if ($target_bundle = $plugin_instance->getBundle($bundle_id)) { + $result[$entity_type_id . ':' . $bundle_id] = $target_bundle->getTypeSdl(); } } - return $mapping[$field_id]; + return $result; } } diff --git a/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php b/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php index b7e47e58..e2b69b18 100644 --- a/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php +++ b/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Drupal\graphql_compose\Plugin\GraphQLCompose; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; @@ -265,16 +266,21 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ } // Create generic entity wide union. - $union = new UnionType([ + $union_types = array_map( + fn(EntityTypeWrapper $bundle): string => $bundle->getTypeSdl(), + $bundles + ); + + $entity_union = new UnionType([ 'name' => $this->getUnionTypeSdl(), 'description' => $this->getDescription(), 'types' => fn() => array_map( - fn($bundle): Type => $this->gqlSchemaTypeManager->get($bundle->getTypeSdl()), - $bundles - ) ?: [$this->gqlSchemaTypeManager->get('UnsupportedType')], + $this->gqlSchemaTypeManager->get(...), + $union_types ?: ['UnsupportedType'] + ), ]); - $this->gqlSchemaTypeManager->add($union); + $this->gqlSchemaTypeManager->add($entity_union); // Create generic entity wide query. $enabled_query_bundles = array_filter( @@ -284,7 +290,9 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ if ($this->isQueryLoadSimple() && $enabled_query_bundles) { // Entities without bundles shouldn't return a union. - $query_type = $entity_type_has_bundles ? $this->getUnionTypeSdl() : $this->getTypeSdl(); + $query_type = $entity_type_has_bundles + ? $this->getUnionTypeSdl() + : $this->getTypeSdl(); $entityQuery = new ObjectType([ 'name' => 'Query', @@ -331,7 +339,7 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ 'name' => $bundle->getTypeSdl(), 'description' => $bundle->getDescription() ?: $this->getDescription(), 'interfaces' => fn() => array_map( - fn($interface): Type => $this->gqlSchemaTypeManager->get($interface), + $this->gqlSchemaTypeManager->get(...), $this->getInterfaces() ), 'fields' => function () use ($fields) { @@ -382,9 +390,8 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ $this->gqlSchemaTypeManager->extend($entityQuery); } - // Add union types for non-simple unions. + // Add per-field union types. foreach ($fields as $field_plugin) { - // Check it uses the union trait. if (!$field_plugin instanceof FieldUnionInterface) { continue; } @@ -404,14 +411,13 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ continue; } - // Create the new union type. $union = new UnionType([ 'name' => $field_plugin->getUnionTypeSdl(), 'description' => $field_plugin->getDescription(), 'types' => fn() => array_map( - fn($type): Type => $this->gqlSchemaTypeManager->get($type), - $field_plugin->getUnionTypeMapping() - ) ?: [$this->gqlSchemaTypeManager->get('UnsupportedType')], + $this->gqlSchemaTypeManager->get(...), + $field_plugin->getUnionTypeMapping() ?: ['UnsupportedType'] + ), ]); $this->gqlSchemaTypeManager->add($union); @@ -473,7 +479,7 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ } } - throw new UserError(sprintf('Could not resolve entity of type %s, is it enabled in the schema?', $entity_class)); + throw new UserError(sprintf('Could not resolve union for %s', $entity_class)); } ); @@ -541,25 +547,25 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ } // Generic unions return a generic entity union. - if ($field_plugin->isGenericUnion()) { - continue; - } - // Single unions just return the type. - if ($field_plugin->isSingleUnion()) { + if ($field_plugin->isGenericUnion() || $field_plugin->isSingleUnion()) { continue; } - $map = $field_plugin->getUnionTypeMapping(); - $registry->addTypeResolver( $field_plugin->getUnionTypeSdl(), - function ($value) use ($entity_class, $map) { - if (array_key_exists($value->bundle(), $map)) { - return $map[$value->bundle()]; + function (?EntityInterface $value) use ($field_plugin) { + $entity_type_id = $value?->getEntityTypeId(); + $entity_bundle_id = $value?->bundle(); + + $union_map = $entity_type_id . ':' . $entity_bundle_id; + $union_mapping = $field_plugin->getUnionTypeMapping(); + + if (array_key_exists($union_map, $union_mapping)) { + return $union_mapping[$union_map]; } - throw new UserError(sprintf('Could not resolve entity of type %s::%s for this field, is it enabled in the field config?', $entity_class, $value->bundle())); + throw new UserError(sprintf('Could not resolve union mapping %s:%s', $entity_type_id, $entity_bundle_id)); } ); } diff --git a/src/Plugin/GraphQLCompose/SchemaType/ImageType.php b/src/Plugin/GraphQLCompose/SchemaType/ImageType.php index ee10d6c6..f2f76f16 100644 --- a/src/Plugin/GraphQLCompose/SchemaType/ImageType.php +++ b/src/Plugin/GraphQLCompose/SchemaType/ImageType.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Drupal\graphql_compose\Plugin\GraphQLCompose\SchemaType; +use Drupal\Core\StringTranslation\ByteSizeMarkup; use Drupal\graphql_compose\Plugin\GraphQLCompose\GraphQLComposeSchemaTypeBase; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; @@ -63,10 +64,15 @@ class ImageType extends GraphQLComposeSchemaTypeBase { if ($config->get('settings.svg_image')) { $svg_max = (int) $config->get('settings.svg_filesize') ?: 100; + // format_size is deprecated in drupal:10.2.0. + $svg_max = (floatval(\Drupal::VERSION) >= 10.2) + ? ByteSizeMarkup::create($svg_max * 1024) + : format_size($svg_max * 1024); /* @phpstan-ignore-line */ + $fields['svg'] = [ 'type' => Type::string(), 'description' => (string) $this->t('Contents of the image, if the mime is `image/svg+xml` and size <= `@size`.', [ - '@size' => format_size($svg_max * 1024), + '@size' => $svg_max, ]), ]; } diff --git a/tests/src/Functional/EntityUnionTest.php b/tests/src/Functional/EntityUnionTest.php index fc4e1fcc..a2fb4b06 100644 --- a/tests/src/Functional/EntityUnionTest.php +++ b/tests/src/Functional/EntityUnionTest.php @@ -7,7 +7,7 @@ namespace Drupal\Tests\graphql_compose\Functional; use Drupal\media\Entity\Media; use Drupal\media\MediaInterface; use Drupal\node\NodeInterface; -use Drupal\Tests\field\Traits\EntityReferenceTestTrait; +use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait; use Drupal\Tests\media\Traits\MediaTypeCreationTrait; /** @@ -18,7 +18,7 @@ use Drupal\Tests\media\Traits\MediaTypeCreationTrait; class EntityUnionTest extends GraphQLComposeBrowserTestBase { use MediaTypeCreationTrait; - use EntityReferenceTestTrait; + use EntityReferenceFieldCreationTrait; /** * The test node. diff --git a/tests/src/Functional/UnsupportedTest.php b/tests/src/Functional/UnsupportedTest.php index 78b7fce7..ef2b6fb0 100644 --- a/tests/src/Functional/UnsupportedTest.php +++ b/tests/src/Functional/UnsupportedTest.php @@ -7,7 +7,7 @@ namespace Drupal\Tests\graphql_compose\Functional; use Drupal\media\Entity\Media; use Drupal\media\MediaInterface; use Drupal\node\NodeInterface; -use Drupal\Tests\field\Traits\EntityReferenceTestTrait; +use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait; use Drupal\Tests\media\Traits\MediaTypeCreationTrait; /** @@ -18,7 +18,7 @@ use Drupal\Tests\media\Traits\MediaTypeCreationTrait; class UnsupportedTest extends GraphQLComposeBrowserTestBase { use MediaTypeCreationTrait; - use EntityReferenceTestTrait; + use EntityReferenceFieldCreationTrait; /** * The test node. -- GitLab