graphql_compose-1.0.0-beta20/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php

src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php
<?php

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\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\graphql\GraphQL\ResolverBuilder;
use Drupal\graphql\GraphQL\ResolverRegistryInterface;
use Drupal\graphql_compose\Utility\ComposeConfig;
use Drupal\graphql_compose\Utility\ComposeProviders;
use Drupal\graphql_compose\Plugin\GraphQLComposeFieldTypeManager;
use Drupal\graphql_compose\Plugin\GraphQLComposeSchemaTypeManager;
use Drupal\graphql_compose\Wrapper\EntityTypeWrapper;
use GraphQL\Error\UserError;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use Symfony\Component\DependencyInjection\ContainerInterface;
use function Symfony\Component\String\u;

/**
 * Base class that can be used for schema extension plugins.
 */
abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQLComposeEntityTypeInterface, ContainerFactoryPluginInterface {

  /**
   * Static storage of bundles for plugin.
   *
   * @var \Drupal\graphql_compose\Wrapper\EntityTypeWrapper[]
   */
  private array $bundles;

  /**
   * Constructs a GraphQLComposeEntityTypeBase object.
   *
   * @param array $configuration
   *   The plugin configuration array.
   * @param string $plugin_id
   *   The plugin id.
   * @param array $plugin_definition
   *   The plugin definition array.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Drupal config factory service.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entityTypeBundleInfo
   *   Drupal entity type bundle service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   Drupal entity type manager service.
   * @param \Drupal\graphql_compose\Plugin\GraphQLComposeFieldTypeManager $gqlFieldTypeManager
   *   GraphQL Compose field type plugin manager.
   * @param \Drupal\graphql_compose\Plugin\GraphQLComposeSchemaTypeManager $gqlSchemaTypeManager
   *   GraphQL Compose schema type plugin manager.
   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
   *   Drupal language manager service.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   Drupal module handler service.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    array $plugin_definition,
    protected ConfigFactoryInterface $configFactory,
    protected EntityTypeBundleInfoInterface $entityTypeBundleInfo,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected GraphQLComposeFieldTypeManager $gqlFieldTypeManager,
    protected GraphQLComposeSchemaTypeManager $gqlSchemaTypeManager,
    protected LanguageManagerInterface $languageManager,
    protected ModuleHandlerInterface $moduleHandler,
  ) {
    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('config.factory'),
      $container->get('entity_type.bundle.info'),
      $container->get('entity_type.manager'),
      $container->get('graphql_compose.field_type_manager'),
      $container->get('graphql_compose.schema_type_manager'),
      $container->get('language_manager'),
      $container->get('module_handler'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityTypeId(): string {
    return $this->getDerivativeId() ?: $this->getPluginId();
  }

  /**
   * {@inheritdoc}
   */
  public function getEntityType(): EntityTypeInterface {
    return $this->entityTypeManager->getDefinition($this->getEntityTypeId());
  }

  /**
   * {@inheritdoc}
   */
  public function getDescription(): string {
    return (string) $this->t('Entity type @id.', [
      '@id' => $this->getEntityTypeId(),
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function getInterfaces(?string $bundle_id = NULL): array {
    $interfaces = $this->pluginDefinition['interfaces'] ?? [];

    if ($this->gqlFieldTypeManager->getInterfaceFields($this->getEntityTypeId())) {
      $interfaces[] = $this->getInterfaceTypeSdl();
    }

    ComposeProviders::invoke('graphql_compose_entity_interfaces_alter', [
      &$interfaces,
      $this,
      $bundle_id,
    ]);

    return array_unique($interfaces);
  }

  /**
   * {@inheritdoc}
   */
  public function getPrefix(): string {
    return $this->pluginDefinition['prefix'] ?? '';
  }

  /**
   * {@inheritdoc}
   */
  public function getNameSdl(): string {
    return u($this->getTypeSdl())
      ->camel()
      ->toString();
  }

  /**
   * {@inheritdoc}
   */
  public function getTypeSdl(): string {
    $type = $this->pluginDefinition['type_sdl'] ?? $this->getEntityTypeId();

    return u($type)
      ->camel()
      ->title()
      ->toString();
  }

  /**
   * {@inheritdoc}
   */
  public function getBaseFields(): array {
    $base_fields = $this->pluginDefinition['base_fields'] ?? [];

    ComposeProviders::invoke('graphql_compose_entity_base_fields_alter', [
      &$base_fields,
      $this->getEntityTypeId(),
    ]);

    return $base_fields;
  }

  /**
   * {@inheritdoc}
   */
  public function getUnionTypeSdl(): string {
    return u($this->getTypeSdl())
      ->append('Union')
      ->toString();
  }

  /**
   * {@inheritdoc}
   */
  public function getInterfaceTypeSdl(): string {
    return u($this->getTypeSdl())
      ->append('Interface')
      ->toString();
  }

  /**
   * {@inheritdoc}
   */
  public function getBundle(string $bundle_id): ?EntityTypeWrapper {
    $bundles = $this->getBundles();
    return $bundles[$bundle_id] ?? NULL;
  }

  /**
   * Wrap a bundle into a utility wrapper.
   *
   * @param mixed $bundle
   *   The bundle to wrap.
   *
   * @return \Drupal\graphql_compose\Wrapper\EntityTypeWrapper
   *   The wrapped bundle.
   */
  protected function wrapBundle($bundle): EntityTypeWrapper {
    return \Drupal::service('graphql_compose.entity_type_wrapper')
      ->setEntityTypePlugin($this)
      ->setEntity($bundle);
  }

  /**
   * {@inheritdoc}
   */
  public function getBundles(): array {
    if (isset($this->bundles)) {
      return $this->bundles;
    }

    $this->bundles = [];

    $entity_type = $this->getEntityType();
    $bundle_info = $this->entityTypeBundleInfo->getBundleInfo($this->getEntityTypeId());

    if ($storage_type = $entity_type->getBundleEntityType()) {
      $entity_types = $this->entityTypeManager->getStorage($storage_type)->loadMultiple();
    }

    foreach (array_keys($bundle_info) as $bundle_id) {
      $bundle = $this->wrapBundle($entity_types[$bundle_id] ?? $entity_type);
      if ($bundle->isEnabled()) {
        $this->bundles[$bundle_id] = $bundle;
      }
    }

    return $this->bundles ?: [];
  }

  /**
   * {@inheritdoc}
   *
   * Register unions and interfaces only if there is multiple enabled bundles.
   */
  public function registerTypes(): void {
    $bundles = $this->getBundles();
    if (!$bundles) {
      return;
    }

    $this->registerEntityInterface();
    $this->registerEntityUnion();
    $this->registerEntityQuery();

    foreach ($bundles as $bundle) {
      $this->registerBundleTypes($bundle);
      $this->registerBundleQueries($bundle);
      $this->registerBundleFieldUnions($bundle);
    }
  }

  /**
   * Register a generic entity wide interface.
   */
  protected function registerEntityInterface(): void {
    $interface_fields = $this->gqlFieldTypeManager->getInterfaceFields($this->getEntityTypeId());
    if ($interface_fields) {
      $interface = new InterfaceType([
        'name' => $this->getInterfaceTypeSdl(),
        'description' => $this->getDescription(),
        'fields' => function () use ($interface_fields) {
          $fields = [];
          foreach ($interface_fields as $field) {
            $fields[$field->getNameSdl()] = [
              'type' => $this->gqlSchemaTypeManager->get(
                $field->getTypeSdl(),
                $field->isMultiple(),
                $field->isRequired()
              ),
              'description' => $field->getDescription(),
            ];
          }

          return $fields;
        },
      ]);

      $this->gqlSchemaTypeManager->add($interface);
    }
  }

  /**
   * Register a generic entity wide union.
   */
  protected function registerEntityUnion(): void {
    $union_types = array_map(
      fn(EntityTypeWrapper $bundle): string => $bundle->getTypeSdl(),
      $this->getBundles()
    );

    $entity_union = new UnionType([
      'name' => $this->getUnionTypeSdl(),
      'description' => $this->getDescription(),
      'types' => fn() => array_map(
        $this->gqlSchemaTypeManager->get(...),
        $union_types ?: ['UnsupportedType']
      ),
    ]);

    $this->gqlSchemaTypeManager->add($entity_union);
  }

  /**
   * Register a generic entity wide query.
   */
  protected function registerEntityQuery(): void {
    $enabled_query_bundles = array_filter(
      $this->getBundles(),
      fn(EntityTypeWrapper $bundle) => $bundle->isQueryLoadEnabled()
    );

    if ($this->isQueryLoadSimple() && $enabled_query_bundles) {

      // Entities without bundles shouldn't return a union.
      $query_type = $this->getEntityType()->getBundleEntityType()
        ? $this->getUnionTypeSdl()
        : $this->getTypeSdl();

      $entityQuery = new ObjectType([
        'name' => 'Query',
        'fields' => fn() => [
          $this->getNameSdl() => [
            'type' => $this->gqlSchemaTypeManager->get($query_type),
            'description' => (string) $this->t('Load a @type entity by id.', [
              '@type' => $this->getTypeSdl(),
            ]),
            'args' => array_filter([
              'id' => [
                'type' => Type::nonNull(Type::id()),
                'description' => (string) $this->t('The id of the @type to load.', [
                  '@type' => $this->getTypeSdl(),
                ]),
              ],
              'langcode' => $this->languageManager->isMultilingual() ? [
                'type' => Type::string(),
                'description' => (string) $this->t('Optionally set the response language. Eg en, ja, fr.'),
              ] : [],
              'revision' => $this->getEntityType()->isRevisionable() ? [
                'type' => Type::id(),
                'description' => (string) $this->t('Optionally set the revision of the entity. Eg current, latest, or an ID.'),
              ] : [],
            ]),
          ],
        ],
      ]);

      $this->gqlSchemaTypeManager->extend($entityQuery);
    }
  }

  /**
   * Register a bundle types into the schema.
   *
   * @param \Drupal\graphql_compose\Wrapper\EntityTypeWrapper $bundle
   *   The bundle to register.
   */
  protected function registerBundleTypes(EntityTypeWrapper $bundle): void {
    $fields = $this->gqlFieldTypeManager->getBundleFields(
      $this->getEntityTypeId(),
      $bundle->getEntity()->id()
    );

    // Create bundle type.
    $entityType = new ObjectType([
      'name' => $bundle->getTypeSdl(),
      'description' => $bundle->getDescription() ?: $this->getDescription(),
      'interfaces' => fn() => array_map(
        $this->gqlSchemaTypeManager->get(...),
        $this->getInterfaces($bundle->getEntity()->id())
      ),
      'fields' => function () use ($fields) {
        $result = [];
        foreach ($fields as $field) {
          $result[$field->getNameSdl()] = [
            'description' => $field->getDescription(),
            'type' => $this->gqlSchemaTypeManager->get(
              $field->getTypeSdl(),
              $field->isMultiple(),
              $field->isRequired()
            ),
            'args' => $field->getArgsSdl(),
          ];
        }
        return $result;
      },
    ]);

    $this->gqlSchemaTypeManager->add($entityType);
  }

  /**
   * Register individual bundle queries into the schema.
   *
   * @param \Drupal\graphql_compose\Wrapper\EntityTypeWrapper $bundle
   *   The bundle to register.
   */
  protected function registerBundleQueries(EntityTypeWrapper $bundle): void {
    if (!$this->isQueryLoadSimple() && $bundle->isQueryLoadEnabled()) {
      $entityQuery = new ObjectType([
        'name' => 'Query',
        'fields' => fn() => [
          $bundle->getNameSdl() => [
            'type' => $this->gqlSchemaTypeManager->get($bundle->getTypeSdl()),
            'description' => (string) $this->t('Load a @bundle entity by id', [
              '@bundle' => $bundle->getTypeSdl(),
            ]),
            'args' => array_filter([
              'id' => [
                'type' => Type::nonNull(Type::id()),
                'description' => (string) $this->t('The id of the @bundle to load.', [
                  '@bundle' => $bundle->getTypeSdl(),
                ]),
              ],
              'langcode' => $this->languageManager->isMultilingual() ? [
                'type' => Type::string(),
                'description' => (string) $this->t('Optionally set the response language. Eg en, ja, fr.'),
              ] : [],
              'revision' => $this->getEntityType()->isRevisionable() ? [
                'type' => Type::id(),
                'description' => (string) $this->t('Optionally set the revision of the entity. Eg current, latest, or an ID.'),
              ] : [],
            ]),
          ],
        ],
      ]);

      $this->gqlSchemaTypeManager->extend($entityQuery);
    }
  }

  /**
   * Register a bundle field union types into the schema.
   *
   * @param \Drupal\graphql_compose\Wrapper\EntityTypeWrapper $bundle
   *   The bundle to register.
   */
  protected function registerBundleFieldUnions(EntityTypeWrapper $bundle): void {
    $fields = $this->gqlFieldTypeManager->getBundleFields(
      $this->getEntityTypeId(),
      $bundle->getEntity()->id()
    );

    // Add per-field union types.
    foreach ($fields as $field_plugin) {
      if (!$field_plugin instanceof FieldUnionInterface) {
        continue;
      }

      // The unsupported field points to an unsupported type.
      if ($field_plugin->getUnionTypeSdl() === 'UnsupportedType') {
        continue;
      }

      // Generic unions return a generic entity union.
      if ($field_plugin->isGenericUnion()) {
        continue;
      }

      // Single unions just return the type.
      if ($field_plugin->isSingleUnion()) {
        continue;
      }

      $union = new UnionType([
        'name' => $field_plugin->getUnionTypeSdl(),
        'description' => $field_plugin->getDescription(),
        'types' => fn() => array_map(
          $this->gqlSchemaTypeManager->get(...),
          $field_plugin->getUnionTypeMapping() ?: ['UnsupportedType']
        ),
      ]);

      $this->gqlSchemaTypeManager->add($union);
    }
  }

  /**
   * {@inheritdoc}
   *
   * Resolve unions only if there is multiple enabled bundles.
   */
  public function registerResolvers(ResolverRegistryInterface $registry, ResolverBuilder $builder): void {
    $bundles = $this->getBundles();
    if (!$bundles) {
      return;
    }

    $this->resolveEntityQuery($registry, $builder);
    $this->resolveEntityUnion($registry, $builder);

    foreach ($bundles as $bundle) {
      $this->resolveBundleTypes($registry, $builder, $bundle);
      $this->resolveBundleQueries($registry, $builder, $bundle);
      $this->resolveBundleFieldUnions($registry, $builder, $bundle);
    }
  }

  /**
   * Resolve generic entity query.
   *
   * @param \Drupal\graphql\GraphQL\ResolverRegistryInterface $registry
   *   The resolver registry.
   * @param \Drupal\graphql\GraphQL\ResolverBuilder $builder
   *   The resolver builder.
   */
  protected function resolveEntityQuery(ResolverRegistryInterface $registry, ResolverBuilder $builder): void {
    // Resolve generic load by id query.
    $enabled_query_bundles = array_filter(
      $this->getBundles(),
      fn(EntityTypeWrapper $bundle) => $bundle->isQueryLoadEnabled()
    );

    // Limit allowed bundle types.
    $enabled_query_bundle_ids = array_map(
      fn(EntityTypeWrapper $bundle) => $bundle->getEntity()->id(),
      $enabled_query_bundles
    );

    if ($this->isQueryLoadSimple() && $enabled_query_bundles) {
      $registry->addFieldResolver(
        'Query',
        $this->getNameSdl(),
        $builder->compose(
          $builder->produce('language_context')
            ->map('language', $builder->fromArgument('langcode')),
          $builder->produce('entity_load_by_uuid_or_id')
            ->map('type', $builder->fromValue($this->getEntityTypeId()))
            ->map('bundles', $builder->fromValue($enabled_query_bundle_ids))
            ->map('identifier', $builder->fromArgument('id'))
            ->map('language', $builder->fromArgument('langcode')),
          $builder->produce('entity_load_revision')
            ->map('entity', $builder->fromParent())
            ->map('identifier', $builder->fromArgument('revision'))
            ->map('language', $builder->fromArgument('langcode'))
        )
      );
    }
  }

  /**
   * Resolve generic entity wide union.
   *
   * @param \Drupal\graphql\GraphQL\ResolverRegistryInterface $registry
   *   The resolver registry.
   * @param \Drupal\graphql\GraphQL\ResolverBuilder $builder
   *   The resolver builder.
   */
  protected function resolveEntityUnion(ResolverRegistryInterface $registry, ResolverBuilder $builder): void {
    // The expected class for the entity type.
    $class = $this->entityTypeManager
      ->getDefinition($this->getEntityTypeId())
      ->getClass();

    // Resolve generic entity wide union.
    $registry->addTypeResolver(
      $this->getUnionTypeSdl(),
      function (?EntityInterface $value) use ($class) {
        if (!is_a($value, $class, TRUE)) {
          throw new UserError(sprintf('Could not resolve union entity type %s', $class));
        }

        $bundle = $this->getBundle($value->bundle());
        if (!$bundle) {
          throw new UserError(sprintf('Could not resolve union entity bundle %s::%s, is it enabled?', $class, $value->bundle()));
        }

        return $bundle->getTypeSdl();
      }
    );
  }

  /**
   * Resolve bundle types for the schema.
   *
   * @param \Drupal\graphql\GraphQL\ResolverRegistryInterface $registry
   *   The resolver registry.
   * @param \Drupal\graphql\GraphQL\ResolverBuilder $builder
   *   The resolver builder.
   * @param \Drupal\graphql_compose\Wrapper\EntityTypeWrapper $bundle
   *   The bundle to resolve.
   */
  protected function resolveBundleTypes(ResolverRegistryInterface $registry, ResolverBuilder $builder, EntityTypeWrapper $bundle): void {

    // The expected class for the entity type.
    $class = $this->entityTypeManager
      ->getDefinition($this->getEntityTypeId())
      ->getClass();

    $registry->addTypeResolver(
      $bundle->getTypeSdl(),
      function (?EntityInterface $value) use ($class) {
        if (!is_a($value, $class, TRUE)) {
          throw new UserError(sprintf('Could not resolve entity type %s', $class));
        }
        return $this->getBundle($value->bundle())->getTypeSdl();
      }
    );

    // Add fields to bundle type.
    $fields = $this->gqlFieldTypeManager->getBundleFields(
      $this->getEntityTypeId(),
      $bundle->getEntity()->id()
    );

    foreach ($fields as $field_plugin) {
      $registry->addFieldResolver(
        $bundle->getTypeSdl(),
        $field_plugin->getNameSdl(),
        $builder->produce('field_results')
          ->map('entity', $builder->fromParent())
          ->map('plugin', $builder->fromValue($field_plugin))
          ->map('value', $field_plugin->getProducers($builder)),
      );
    }
  }

  /**
   * Resolve bundle queries for the schema.
   *
   * @param \Drupal\graphql\GraphQL\ResolverRegistryInterface $registry
   *   The resolver registry.
   * @param \Drupal\graphql\GraphQL\ResolverBuilder $builder
   *   The resolver builder.
   * @param \Drupal\graphql_compose\Wrapper\EntityTypeWrapper $bundle
   *   The bundle to resolve.
   */
  protected function resolveBundleQueries(ResolverRegistryInterface $registry, ResolverBuilder $builder, EntityTypeWrapper $bundle): void {
    if (!$this->isQueryLoadSimple() && $bundle->isQueryLoadEnabled()) {
      $registry->addFieldResolver(
        'Query',
        $bundle->getNameSdl(),
        $builder->compose(
          $builder->produce('language_context')
            ->map('language', $builder->fromArgument('langcode')),
          $builder->produce('entity_load_by_uuid_or_id')
            ->map('type', $builder->fromValue($this->getEntityTypeId()))
            ->map('bundles', $builder->fromValue([$bundle->getEntity()->id()]))
            ->map('identifier', $builder->fromArgument('id'))
            ->map('language', $builder->fromArgument('langcode')),
          $builder->produce('entity_load_revision')
            ->map('entity', $builder->fromParent())
            ->map('identifier', $builder->fromArgument('revision'))
            ->map('language', $builder->fromArgument('langcode'))
        )
      );
    }
  }

  /**
   * Resolve bundle field unions for the schema.
   *
   * @param \Drupal\graphql\GraphQL\ResolverRegistryInterface $registry
   *   The resolver registry.
   * @param \Drupal\graphql\GraphQL\ResolverBuilder $builder
   *   The resolver builder.
   * @param \Drupal\graphql_compose\Wrapper\EntityTypeWrapper $bundle
   *   The bundle to register.
   */
  protected function resolveBundleFieldUnions(ResolverRegistryInterface $registry, ResolverBuilder $builder, EntityTypeWrapper $bundle): void {

    // Get the bundle fields.
    $fields = $this->gqlFieldTypeManager->getBundleFields(
      $this->getEntityTypeId(),
      $bundle->getEntity()->id()
    );

    // Add union field resolution for non-simple unions.
    foreach ($fields as $field_plugin) {
      // Check it uses the union trait.
      if (!$field_plugin instanceof FieldUnionInterface) {
        continue;
      }

      // Generic unions return a generic entity union.
      // Single unions just return the type.
      if ($field_plugin->isGenericUnion() || $field_plugin->isSingleUnion()) {
        continue;
      }

      $registry->addTypeResolver(
        $field_plugin->getUnionTypeSdl(),
        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 union mapping %s:%s', $entity_type_id, $entity_bundle_id));
        }
      );
    }
  }

  /**
   * {@inheritdoc}
   */
  public function isQueryLoadSimple(): bool {
    return ComposeConfig::get('settings.simple_queries', FALSE);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc