vendor/overblog/graphql-bundle/src/EventListener/TypeDecoratorListener.php line 41

  1. <?php
  2. declare(strict_types=1);
  3. namespace Overblog\GraphQLBundle\EventListener;
  4. use GraphQL\Type\Definition\EnumType;
  5. use GraphQL\Type\Definition\InterfaceType;
  6. use GraphQL\Type\Definition\ObjectType;
  7. use GraphQL\Type\Definition\Type;
  8. use GraphQL\Type\Definition\UnionType;
  9. use InvalidArgumentException;
  10. use Overblog\GraphQLBundle\Definition\ArgumentFactory;
  11. use Overblog\GraphQLBundle\Definition\Type\CustomScalarType;
  12. use Overblog\GraphQLBundle\Event\TypeLoadedEvent;
  13. use Overblog\GraphQLBundle\Resolver\ResolverMapInterface;
  14. use Overblog\GraphQLBundle\Resolver\ResolverMaps;
  15. use function array_diff;
  16. use function count;
  17. use function current;
  18. use function implode;
  19. use function is_callable;
  20. use function sprintf;
  21. use function substr;
  22. final class TypeDecoratorListener
  23. {
  24.     private ArgumentFactory $argumentFactory;
  25.     private array $schemaResolverMaps = [];
  26.     public function __construct(ArgumentFactory $argumentFactory)
  27.     {
  28.         $this->argumentFactory $argumentFactory;
  29.     }
  30.     public function addSchemaResolverMaps(string $schemaName, array $resolverMaps): void
  31.     {
  32.         $this->schemaResolverMaps[$schemaName] = === count($resolverMaps) ? current($resolverMaps) : new ResolverMaps($resolverMaps);
  33.     }
  34.     public function onTypeLoaded(TypeLoadedEvent $event): void
  35.     {
  36.         if (!empty($this->schemaResolverMaps[$event->getSchemaName()])) {
  37.             $this->decorateType($event->getType(), $this->schemaResolverMaps[$event->getSchemaName()]);
  38.         }
  39.     }
  40.     public function decorateType(Type $typeResolverMapInterface $resolverMap): void
  41.     {
  42.         if ($type instanceof ObjectType) {
  43.             $this->decorateObjectType($type$resolverMap);
  44.         } elseif ($type instanceof InterfaceType || $type instanceof UnionType) {
  45.             $this->decorateInterfaceOrUnionType($type$resolverMap);
  46.         } elseif ($type instanceof EnumType) {
  47.             $this->decorateEnumType($type$resolverMap);
  48.         } elseif ($type instanceof CustomScalarType) {
  49.             $this->decorateCustomScalarType($type$resolverMap);
  50.         } else {
  51.             $covered $resolverMap->covered($type->name);
  52.             if (!empty($covered)) {
  53.                 throw new InvalidArgumentException(
  54.                     sprintf(
  55.                         '"%s".{"%s"} defined in resolverMap, but type is not managed by TypeDecorator.',
  56.                         $type->name,
  57.                         implode('", "'$covered)
  58.                     )
  59.                 );
  60.             }
  61.         }
  62.     }
  63.     private function decorateObjectType(ObjectType $typeResolverMapInterface $resolverMap): void
  64.     {
  65.         $fieldsResolved = [];
  66.         foreach ($resolverMap->covered($type->name) as $fieldName) {
  67.             if (ResolverMapInterface::IS_TYPEOF === $fieldName) {
  68.                 $this->configTypeMapping($typeResolverMapInterface::IS_TYPEOF$resolverMap);
  69.             } elseif (ResolverMapInterface::RESOLVE_FIELD === $fieldName) {
  70.                 $resolveFieldFn $this->argumentFactory->wrapResolverArgs($resolverMap->resolve($type->nameResolverMapInterface::RESOLVE_FIELD));
  71.                 $type->config[substr(ResolverMapInterface::RESOLVE_FIELD2)] = $resolveFieldFn;
  72.                 $type->resolveFieldFn $resolveFieldFn;
  73.             } else {
  74.                 $fieldsResolved[] = $fieldName;
  75.             }
  76.         }
  77.         $this->decorateObjectTypeFields($type$fieldsResolved$resolverMap);
  78.     }
  79.     /**
  80.      * @param InterfaceType|UnionType $type
  81.      */
  82.     private function decorateInterfaceOrUnionType($typeResolverMapInterface $resolverMap): void
  83.     {
  84.         $this->configTypeMapping($typeResolverMapInterface::RESOLVE_TYPE$resolverMap);
  85.         $covered $resolverMap->covered($type->name);
  86.         if (!empty($covered)) {
  87.             $unknownFields array_diff($covered, [ResolverMapInterface::RESOLVE_TYPE]);
  88.             if (!empty($unknownFields)) {
  89.                 throw new InvalidArgumentException(
  90.                     sprintf(
  91.                         '"%s".{"%s"} defined in resolverMap, but only "%s::RESOLVE_TYPE" is allowed.',
  92.                         $type->name,
  93.                         implode('", "'$unknownFields),
  94.                         ResolverMapInterface::class
  95.                     )
  96.                 );
  97.             }
  98.         }
  99.     }
  100.     private function decorateCustomScalarType(CustomScalarType $typeResolverMapInterface $resolverMap): void
  101.     {
  102.         static $allowedFields = [
  103.             ResolverMapInterface::SCALAR_TYPE,
  104.             ResolverMapInterface::SERIALIZE,
  105.             ResolverMapInterface::PARSE_VALUE,
  106.             ResolverMapInterface::PARSE_LITERAL,
  107.         ];
  108.         foreach ($allowedFields as $fieldName) {
  109.             $this->configTypeMapping($type$fieldName$resolverMap);
  110.         }
  111.         $unknownFields array_diff($resolverMap->covered($type->name), $allowedFields);
  112.         if (!empty($unknownFields)) {
  113.             throw new InvalidArgumentException(
  114.                 sprintf(
  115.                     '"%s".{"%s"} defined in resolverMap, but only "%s::{%s}" is allowed.',
  116.                     $type->name,
  117.                     implode('", "'$unknownFields),
  118.                     ResolverMapInterface::class,
  119.                     implode(', ', ['SCALAR_TYPE''SERIALIZE''PARSE_VALUE''PARSE_LITERAL'])
  120.                 )
  121.             );
  122.         }
  123.     }
  124.     private function decorateEnumType(EnumType $typeResolverMapInterface $resolverMap): void
  125.     {
  126.         $fieldNames = [];
  127.         foreach ($type->config['values'] as $key => &$value) {
  128.             $fieldName $value['name'] ?? $key;
  129.             if ($resolverMap->isResolvable($type->name$fieldName)) {
  130.                 $value['value'] = $resolverMap->resolve($type->name$fieldName);
  131.             }
  132.             $fieldNames[] = $fieldName;
  133.         }
  134.         $unknownFields array_diff($resolverMap->covered($type->name), $fieldNames);
  135.         if (!empty($unknownFields)) {
  136.             throw new InvalidArgumentException(
  137.                 sprintf(
  138.                     '"%s".{"%s"} defined in resolverMap, was defined in resolvers, but enum is not in schema.',
  139.                     $type->name,
  140.                     implode('", "'$unknownFields)
  141.                 )
  142.             );
  143.         }
  144.     }
  145.     private function decorateObjectTypeFields(ObjectType $type, array $fieldsResolvedResolverMapInterface $resolverMap): void
  146.     {
  147.         $fields $type->config['fields'];
  148.         $decoratedFields = function () use ($fields$type$fieldsResolved$resolverMap) {
  149.             if (is_callable($fields)) {
  150.                 $fields $fields();
  151.             }
  152.             $fieldNames = [];
  153.             foreach ($fields as $key => &$field) {
  154.                 $fieldName $field['name'] ?? $key;
  155.                 if ($resolverMap->isResolvable($type->name$fieldName)) {
  156.                     $field['resolve'] = $this->argumentFactory->wrapResolverArgs($resolverMap->resolve($type->name$fieldName));
  157.                 }
  158.                 $fieldNames[] = $fieldName;
  159.             }
  160.             $unknownFields array_diff($fieldsResolved$fieldNames);
  161.             if (!empty($unknownFields)) {
  162.                 throw new InvalidArgumentException(
  163.                     sprintf('"%s".{"%s"} defined in resolverMap, but not in schema.'$type->nameimplode('", "'$unknownFields))
  164.                 );
  165.             }
  166.             return $fields;
  167.         };
  168.         $type->config['fields'] = is_callable($fields) ? $decoratedFields $decoratedFields();
  169.     }
  170.     private function configTypeMapping(Type $typestring $fieldNameResolverMapInterface $resolverMap): void
  171.     {
  172.         if ($resolverMap->isResolvable($type->name$fieldName)) {
  173.             $type->config[substr($fieldName2)] = $resolverMap->resolve($type->name$fieldName);
  174.         }
  175.     }
  176. }