Skip to content

Commit 2bb6f95

Browse files
authored
Merge pull request #2492 from alanpoulain/graphql-custom-types
[GraphQL] Custom types
2 parents e1f92d0 + 7331f27 commit 2bb6f95

File tree

15 files changed

+317
-5
lines changed

15 files changed

+317
-5
lines changed

src/Bridge/Symfony/Bundle/ApiPlatformBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass;
1818
use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass;
1919
use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\FilterPass;
20+
use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\GraphQlTypePass;
2021
use Symfony\Component\DependencyInjection\ContainerBuilder;
2122
use Symfony\Component\HttpKernel\Bundle\Bundle;
2223

@@ -38,5 +39,6 @@ public function build(ContainerBuilder $container)
3839
$container->addCompilerPass(new AnnotationFilterPass());
3940
$container->addCompilerPass(new FilterPass());
4041
$container->addCompilerPass(new ElasticsearchClientPass());
42+
$container->addCompilerPass(new GraphQlTypePass());
4143
}
4244
}

src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
2727
use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface;
2828
use ApiPlatform\Core\Exception\RuntimeException;
29+
use ApiPlatform\Core\GraphQl\Type\Definition\TypeInterface as GraphQlTypeInterface;
2930
use Doctrine\Common\Annotations\Annotation;
3031
use Doctrine\ORM\Version;
3132
use Elasticsearch\Client;
@@ -112,6 +113,8 @@ public function load(array $configs, ContainerBuilder $container)
112113
->addTag('api_platform.subresource_data_provider');
113114
$container->registerForAutoconfiguration(FilterInterface::class)
114115
->addTag('api_platform.filter');
116+
$container->registerForAutoconfiguration(GraphQlTypeInterface::class)
117+
->addTag('api_platform.graphql.type');
115118

116119
if (interface_exists(ValidatorInterface::class)) {
117120
$loader->load('validator.xml');
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler;
15+
16+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Reference;
19+
20+
/**
21+
* Injects GraphQL types.
22+
*
23+
* @internal
24+
*
25+
* @author Alan Poulain <[email protected]>
26+
*/
27+
final class GraphQlTypePass implements CompilerPassInterface
28+
{
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
public function process(ContainerBuilder $container)
33+
{
34+
$types = [];
35+
foreach ($container->findTaggedServiceIds('api_platform.graphql.type', true) as $serviceId => $tags) {
36+
foreach ($tags as $tag) {
37+
$types[$tag['id'] ?? $serviceId] = new Reference($serviceId);
38+
}
39+
}
40+
41+
$container->getDefinition('api_platform.graphql.type_locator')->addArgument($types);
42+
$container->getDefinition('api_platform.graphql.types_factory')->addArgument(array_keys($types));
43+
}
44+
}

src/Bridge/Symfony/Bundle/Resources/config/graphql.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@
4040
<argument type="service" id="api_platform.iri_converter" />
4141
</service>
4242

43+
<!-- Type -->
44+
45+
<service id="api_platform.graphql.iterable_type" class="ApiPlatform\Core\GraphQl\Type\Definition\IterableType">
46+
<tag name="api_platform.graphql.type" />
47+
</service>
48+
49+
<service id="api_platform.graphql.type_locator" class="Symfony\Component\DependencyInjection\ServiceLocator">
50+
<tag name="container.service_locator" />
51+
</service>
52+
53+
<service id="api_platform.graphql.types_factory" class="ApiPlatform\Core\GraphQl\Type\TypesFactory">
54+
<argument type="service" id="api_platform.graphql.type_locator" />
55+
</service>
56+
4357
<service id="api_platform.graphql.schema_builder" class="ApiPlatform\Core\GraphQl\Type\SchemaBuilder" public="false">
4458
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
4559
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
@@ -51,6 +65,7 @@
5165
<argument type="service" id="api_platform.graphql.resolver.resource_field" />
5266
<argument type="service" id="api_platform.filter_locator" />
5367
<argument>%api_platform.collection.pagination.enabled%</argument>
68+
<argument type="service" id="api_platform.graphql.types_factory" />
5469
</service>
5570

5671
<!-- Action -->

src/GraphQl/Type/Definition/IterableType.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
*
3232
* @author Alan Poulain <[email protected]>
3333
*/
34-
final class IterableType extends ScalarType
34+
final class IterableType extends ScalarType implements TypeInterface
3535
{
3636
public function __construct()
3737
{
@@ -41,6 +41,11 @@ public function __construct()
4141
parent::__construct();
4242
}
4343

44+
public function getName(): string
45+
{
46+
return $this->name;
47+
}
48+
4449
/**
4550
* {@inheritdoc}
4651
*/
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\GraphQl\Type\Definition;
15+
16+
use GraphQL\Type\Definition\LeafType;
17+
18+
/**
19+
* @experimental
20+
*
21+
* @author Alan Poulain <[email protected]>
22+
*/
23+
interface TypeInterface extends LeafType
24+
{
25+
public function getName(): string;
26+
}

src/GraphQl/Type/SchemaBuilder.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
1717
use ApiPlatform\Core\GraphQl\Resolver\Factory\ResolverFactoryInterface;
1818
use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer;
19-
use ApiPlatform\Core\GraphQl\Type\Definition\IterableType;
2019
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2120
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2221
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
@@ -56,10 +55,11 @@ final class SchemaBuilder implements SchemaBuilderInterface
5655
private $itemMutationResolverFactory;
5756
private $defaultFieldResolver;
5857
private $filterLocator;
58+
private $typesFactory;
5959
private $paginationEnabled;
6060
private $graphqlTypes = [];
6161

62-
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, ResolverFactoryInterface $collectionResolverFactory, ResolverFactoryInterface $itemMutationResolverFactory, callable $itemResolver, callable $defaultFieldResolver, ContainerInterface $filterLocator = null, bool $paginationEnabled = true)
62+
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, ResolverFactoryInterface $collectionResolverFactory, ResolverFactoryInterface $itemMutationResolverFactory, callable $itemResolver, callable $defaultFieldResolver, ContainerInterface $filterLocator = null, bool $paginationEnabled = true, TypesFactoryInterface $typesFactory = null)
6363
{
6464
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
6565
$this->propertyMetadataFactory = $propertyMetadataFactory;
@@ -70,12 +70,14 @@ public function __construct(PropertyNameCollectionFactoryInterface $propertyName
7070
$this->itemMutationResolverFactory = $itemMutationResolverFactory;
7171
$this->defaultFieldResolver = $defaultFieldResolver;
7272
$this->filterLocator = $filterLocator;
73+
$this->typesFactory = $typesFactory;
7374
$this->paginationEnabled = $paginationEnabled;
7475
}
7576

7677
public function getSchema(): Schema
7778
{
78-
$this->graphqlTypes['Iterable'] = new IterableType();
79+
$this->graphqlTypes += $this->typesFactory->getTypes();
80+
7981
$queryFields = ['node' => $this->getNodeQueryField()];
8082
$mutationFields = [];
8183

src/GraphQl/Type/TypesFactory.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\GraphQl\Type;
15+
16+
use ApiPlatform\Core\GraphQl\Type\Definition\TypeInterface;
17+
use Psr\Container\ContainerInterface;
18+
19+
/**
20+
* Get the registered services corresponding to GraphQL types.
21+
*
22+
* @experimental
23+
*
24+
* @author Alan Poulain <[email protected]>
25+
*/
26+
final class TypesFactory implements TypesFactoryInterface
27+
{
28+
private $typeLocator;
29+
private $typeIds;
30+
31+
/**
32+
* @param string[] $typeIds
33+
*/
34+
public function __construct(ContainerInterface $typeLocator, array $typeIds)
35+
{
36+
$this->typeLocator = $typeLocator;
37+
$this->typeIds = $typeIds;
38+
}
39+
40+
public function getTypes(): array
41+
{
42+
$types = [];
43+
44+
foreach ($this->typeIds as $typeId) {
45+
/** @var TypeInterface $type */
46+
$type = $this->typeLocator->get($typeId);
47+
$types[$type->getName()] = $type;
48+
}
49+
50+
return $types;
51+
}
52+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\GraphQl\Type;
15+
16+
/**
17+
* Get the GraphQL types.
18+
*
19+
* @experimental
20+
*
21+
* @author Alan Poulain <[email protected]>
22+
*/
23+
interface TypesFactoryInterface
24+
{
25+
public function getTypes(): array;
26+
}

tests/Bridge/Symfony/Bundle/ApiPlatformBundleTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass;
1919
use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass;
2020
use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\FilterPass;
21+
use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Compiler\GraphQlTypePass;
2122
use PHPUnit\Framework\TestCase;
2223
use Prophecy\Argument;
2324
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -34,6 +35,7 @@ public function testBuild()
3435
$containerProphecy->addCompilerPass(Argument::type(AnnotationFilterPass::class))->shouldBeCalled();
3536
$containerProphecy->addCompilerPass(Argument::type(FilterPass::class))->shouldBeCalled();
3637
$containerProphecy->addCompilerPass(Argument::type(ElasticsearchClientPass::class))->shouldBeCalled();
38+
$containerProphecy->addCompilerPass(Argument::type(GraphQlTypePass::class))->shouldBeCalled();
3739

3840
$bundle = new ApiPlatformBundle();
3941
$bundle->build($containerProphecy->reveal());

0 commit comments

Comments
 (0)