Skip to content

Commit

Permalink
feat(laravel): enable graphQl support
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Aug 28, 2024
1 parent 03357fb commit 925408a
Show file tree
Hide file tree
Showing 17 changed files with 584 additions and 87 deletions.
20 changes: 7 additions & 13 deletions src/GraphQl/Tests/Type/FieldsBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ class FieldsBuilderTest extends TestCase
private ObjectProphecy $typeBuilderProphecy;
private ObjectProphecy $typeConverterProphecy;
private ObjectProphecy $itemResolverFactoryProphecy;
private ObjectProphecy $collectionResolverFactoryProphecy;
private ObjectProphecy $itemMutationResolverFactoryProphecy;
private ObjectProphecy $itemSubscriptionResolverFactoryProphecy;
private ObjectProphecy $filterLocatorProphecy;
private ObjectProphecy $resourceClassResolverProphecy;
private FieldsBuilder $fieldsBuilder;
Expand All @@ -82,17 +79,14 @@ protected function setUp(): void
$this->typeBuilderProphecy = $this->prophesize(ContextAwareTypeBuilderInterface::class);
$this->typeConverterProphecy = $this->prophesize(TypeConverterInterface::class);
$this->itemResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class);
$this->collectionResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class);
$this->itemMutationResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class);
$this->itemSubscriptionResolverFactoryProphecy = $this->prophesize(ResolverFactoryInterface::class);
$this->filterLocatorProphecy = $this->prophesize(ContainerInterface::class);
$this->resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$this->fieldsBuilder = $this->buildFieldsBuilder();
}

private function buildFieldsBuilder(?AdvancedNameConverterInterface $advancedNameConverter = null): FieldsBuilder
{
return new FieldsBuilder($this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->resourceClassResolverProphecy->reveal(), $this->typesContainerProphecy->reveal(), $this->typeBuilderProphecy->reveal(), $this->typeConverterProphecy->reveal(), $this->itemResolverFactoryProphecy->reveal(), $this->collectionResolverFactoryProphecy->reveal(), $this->itemMutationResolverFactoryProphecy->reveal(), $this->itemSubscriptionResolverFactoryProphecy->reveal(), $this->filterLocatorProphecy->reveal(), new Pagination(), $advancedNameConverter ?? new CustomConverter(), '__');
return new FieldsBuilder($this->propertyNameCollectionFactoryProphecy->reveal(), $this->propertyMetadataFactoryProphecy->reveal(), $this->resourceMetadataCollectionFactoryProphecy->reveal(), $this->resourceClassResolverProphecy->reveal(), $this->typesContainerProphecy->reveal(), $this->typeBuilderProphecy->reveal(), $this->typeConverterProphecy->reveal(), $this->itemResolverFactoryProphecy->reveal(), $this->filterLocatorProphecy->reveal(), new Pagination(), $advancedNameConverter ?? new CustomConverter(), '__');
}

public function testGetNodeQueryFields(): void
Expand Down Expand Up @@ -126,7 +120,7 @@ public function testGetItemQueryFields(string $resourceClass, Operation $operati
$this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType);
$this->typeConverterProphecy->resolveType(Argument::type('string'))->willReturn(GraphQLType::string());
$this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
$this->itemResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($resolver);
$this->itemResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation, Argument::any())->willReturn($resolver);

$queryFields = $this->fieldsBuilder->getItemQueryFields($resourceClass, $operation, $configuration);

Expand Down Expand Up @@ -206,7 +200,7 @@ public function testGetCollectionQueryFields(string $resourceClass, Operation $o
$this->typeConverterProphecy->resolveType(Argument::type('string'))->willReturn(GraphQLType::string());
$this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(true);
$this->typeBuilderProphecy->getPaginatedCollectionType($graphqlType, $operation)->willReturn($graphqlType);
$this->collectionResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($resolver);
$this->itemResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation, Argument::any())->willReturn($resolver);
$this->filterLocatorProphecy->has('my_filter')->willReturn(true);
$filterProphecy = $this->prophesize(FilterInterface::class);
$filterProphecy->getDescription($resourceClass)->willReturn([
Expand Down Expand Up @@ -356,7 +350,7 @@ public function testGetMutationFields(string $resourceClass, Operation $operatio
$this->typeConverterProphecy->convertType(Argument::type(Type::class), false, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($graphqlType);
$this->typeConverterProphecy->convertType(Argument::type(Type::class), true, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($inputGraphqlType);
$this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
$this->itemMutationResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($mutationResolver);
$this->itemResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation, Argument::any())->willReturn($mutationResolver);

$mutationFields = $this->fieldsBuilder->getMutationFields($resourceClass, $operation);

Expand Down Expand Up @@ -417,7 +411,7 @@ public function testGetSubscriptionFields(string $resourceClass, Operation $oper
$this->typeConverterProphecy->convertType(Argument::type(Type::class), true, Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), $resourceClass, $resourceClass, null, 0)->willReturn($inputGraphqlType);
$this->typeBuilderProphecy->isCollection(Argument::type(Type::class))->willReturn(false);
$this->resourceMetadataCollectionFactoryProphecy->create($resourceClass)->willReturn(new ResourceMetadataCollection($resourceClass, [(new ApiResource())->withGraphQlOperations([$operation->getName() => $operation])]));
$this->itemSubscriptionResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation)->willReturn($subscriptionResolver);
$this->itemResolverFactoryProphecy->__invoke($resourceClass, $resourceClass, $operation, Argument::any())->willReturn($subscriptionResolver);

$subscriptionFields = $this->fieldsBuilder->getSubscriptionFields($resourceClass, $operation);

Expand Down Expand Up @@ -489,14 +483,14 @@ public function testGetResourceObjectTypeFields(string $resourceClass, Operation

if ('propertyObject' === $propertyName) {
$this->typeConverterProphecy->convertType(Argument::type(Type::class), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), 'objectClass', $resourceClass, $propertyName, $depth + 1)->willReturn(new ObjectType(['name' => 'objectType', 'fields' => []]));
$this->itemResolverFactoryProphecy->__invoke('objectClass', $resourceClass, $operation)->willReturn(static function (): void {
$this->itemResolverFactoryProphecy->__invoke('objectClass', $resourceClass, $operation, Argument::any())->willReturn(static function (): void {
});
}
if ('propertyNestedResource' === $propertyName) {
$nestedResourceQueryOperation = new Query();
$this->resourceMetadataCollectionFactoryProphecy->create('nestedResourceClass')->willReturn(new ResourceMetadataCollection('nestedResourceClass', [(new ApiResource())->withGraphQlOperations(['item_query' => $nestedResourceQueryOperation])]));
$this->typeConverterProphecy->convertType(Argument::type(Type::class), Argument::type('bool'), Argument::that(static fn (Operation $arg): bool => $arg->getName() === $operation->getName()), 'nestedResourceClass', $resourceClass, $propertyName, $depth + 1)->willReturn(new ObjectType(['name' => 'objectType', 'fields' => []]));
$this->itemResolverFactoryProphecy->__invoke('nestedResourceClass', $resourceClass, $nestedResourceQueryOperation)->willReturn(static function (): void {
$this->itemResolverFactoryProphecy->__invoke('nestedResourceClass', $resourceClass, $nestedResourceQueryOperation, Argument::any())->willReturn(static function (): void {
});
}
}
Expand Down
23 changes: 5 additions & 18 deletions src/GraphQl/Type/FieldsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

use ApiPlatform\Doctrine\Odm\State\Options as ODMOptions;
use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactory;
use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface;
use ApiPlatform\GraphQl\Type\Definition\TypeInterface;
use ApiPlatform\Metadata\GraphQl\Mutation;
Expand Down Expand Up @@ -51,7 +50,7 @@ final class FieldsBuilder implements FieldsBuilderEnumInterface
{
private readonly ContextAwareTypeBuilderInterface $typeBuilder;

public function __construct(private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly TypesContainerInterface $typesContainer, ContextAwareTypeBuilderInterface $typeBuilder, private readonly TypeConverterInterface $typeConverter, private readonly ResolverFactoryInterface $itemResolverFactory, private readonly ?ResolverFactoryInterface $collectionResolverFactory, private readonly ?ResolverFactoryInterface $itemMutationResolverFactory, private readonly ?ResolverFactoryInterface $itemSubscriptionResolverFactory, private readonly ContainerInterface $filterLocator, private readonly Pagination $pagination, private readonly ?NameConverterInterface $nameConverter, private readonly string $nestingSeparator, private readonly ?InflectorInterface $inflector = new Inflector())
public function __construct(private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly TypesContainerInterface $typesContainer, ContextAwareTypeBuilderInterface $typeBuilder, private readonly TypeConverterInterface $typeConverter, private readonly ResolverFactoryInterface $resolverFactory, private readonly ContainerInterface $filterLocator, private readonly Pagination $pagination, private readonly ?NameConverterInterface $nameConverter, private readonly string $nestingSeparator, private readonly ?InflectorInterface $inflector = new Inflector())
{
$this->typeBuilder = $typeBuilder;
}
Expand All @@ -66,7 +65,7 @@ public function getNodeQueryFields(): array
'args' => [
'id' => ['type' => GraphQLType::nonNull(GraphQLType::id())],
],
'resolve' => ($this->itemResolverFactory)(),
'resolve' => ($this->resolverFactory)(),
];
}

Expand Down Expand Up @@ -450,22 +449,10 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
$args = $this->getFilterArgs($args, $resourceClass, $rootResource, $resourceOperation, $rootOperation, $property, $depth);
}

if ($this->itemResolverFactory instanceof ResolverFactory) {
if ($isStandardGraphqlType || $input) {
$resolve = null;
} else {
$resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $resourceOperation, $this->propertyMetadataFactory);
}
if ($isStandardGraphqlType || $input) {
$resolve = null;
} else {
if ($isStandardGraphqlType || $input) {
$resolve = null;
} elseif (($rootOperation instanceof Mutation || $rootOperation instanceof Subscription) && $depth <= 0) {
$resolve = $rootOperation instanceof Mutation ? ($this->itemMutationResolverFactory)($resourceClass, $rootResource, $resourceOperation) : ($this->itemSubscriptionResolverFactory)($resourceClass, $rootResource, $resourceOperation);
} elseif ($this->typeBuilder->isCollection($type)) {
$resolve = ($this->collectionResolverFactory)($resourceClass, $rootResource, $resourceOperation);
} else {
$resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $resourceOperation);
}
$resolve = ($this->resolverFactory)($resourceClass, $rootResource, $resourceOperation, $this->propertyMetadataFactory);
}

return [
Expand Down
8 changes: 7 additions & 1 deletion src/GraphQl/Type/TypeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,17 @@ final class TypeBuilder implements ContextAwareTypeBuilderInterface
{
private $defaultFieldResolver;

public function __construct(private readonly TypesContainerInterface $typesContainer, callable $defaultFieldResolver, private readonly ContainerInterface $fieldsBuilderLocator, private readonly Pagination $pagination)
public function __construct(private readonly TypesContainerInterface $typesContainer, callable $defaultFieldResolver, private ?ContainerInterface $fieldsBuilderLocator, private readonly Pagination $pagination)
{
$this->fieldsBuilderLocator = $fieldsBuilderLocator;
$this->defaultFieldResolver = $defaultFieldResolver;
}

public function setFieldsBuilderLocator(ContainerInterface $fieldsBuilderLocator): void
{
$this->fieldsBuilderLocator = $fieldsBuilderLocator;
}

/**
* {@inheritdoc}
*/
Expand Down
4 changes: 3 additions & 1 deletion src/GraphQl/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"api-platform/metadata": "^3.2 || ^4.0",
"api-platform/serializer": "^3.2 || ^4.0",
"api-platform/state": "^3.2 || ^4.0",
"api-platform/validator": "^3.2 || ^4.0",
"symfony/property-info": "^6.4 || ^7.1",
"symfony/serializer": "^6.4 || ^7.1",
"webonyx/graphql-php": "^14.0 || ^15.0",
Expand All @@ -42,6 +41,9 @@
"api-platform/doctrine-odm": "^3.2 || ^4.0",
"api-platform/doctrine-orm": "^3.2 || ^4.0"
},
"suggest": {
"api-platform/validator": "To support validation."
},
"autoload": {
"psr-4": {
"ApiPlatform\\GraphQl\\": ""
Expand Down
Loading

0 comments on commit 925408a

Please sign in to comment.