Skip to content

Use IRIs as GraphQL's ids and improve perf #1569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Feature: GraphQL query support
Feature: GraphQL collection support

@createSchema
@dropSchema
Expand All @@ -16,7 +16,7 @@ Feature: GraphQL query support
}
}
}
dummyGroup(id: 2) {
dummyGroup(id: "/dummy_groups/2") {
foo
}
}
Expand Down Expand Up @@ -239,7 +239,7 @@ Feature: GraphQL query support
When I send the following GraphQL request:
"""
{
compositePrimitiveItem(name: "Bar", year: 2017) {
compositePrimitiveItem(id: "/composite_primitive_items/name=Bar;year=2017") {
description
}
}
Expand All @@ -256,7 +256,7 @@ Feature: GraphQL query support
When I send the following GraphQL request:
"""
{
compositeRelation(compositeItem: {id: 1}, compositeLabel: {id: 1}) {
compositeRelation(id: "/composite_relations/compositeItem=1;compositeLabel=1") {
value
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Feature: GraphQL query support
Feature: GraphQL introspection support

@createSchema
Scenario: Execute an empty GraphQL query
Expand Down Expand Up @@ -76,7 +76,7 @@ Feature: GraphQL query support
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.type1.description" should be equal to "Dummy Product."
And the JSON node "data.type1.fields[0].type.name" should be equal to "DummyAggregateOfferConnection"
And the JSON node "data.type1.fields[1].type.name" should be equal to "DummyAggregateOfferConnection"
And the JSON node "data.type2.fields[0].name" should be equal to "edges"
And the JSON node "data.type2.fields[0].type.ofType.name" should be equal to "DummyAggregateOfferEdge"
And the JSON node "data.type3.fields[0].name" should be equal to "node"
Expand All @@ -89,9 +89,10 @@ Feature: GraphQL query support
When I send the following GraphQL request:
"""
{
dummyItem: dummy(id: 3) {
dummyItem: dummy(id: "/dummies/3") {
name
relatedDummy {
id
name
__typename
}
Expand Down
62 changes: 23 additions & 39 deletions features/graphql/mutation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -38,37 +38,34 @@ Feature: GraphQL mutation support
When I send the following GraphQL request:
"""
mutation {
createDummy(input: {name: "A new one", alias: "new", description: "brand new!"}) {
createFoo(input: {name: "A new one", bar: "new"}) {
id,
name,
alias,
description
bar
}
}
"""
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.createDummy.id" should be equal to 1
And the JSON node "data.createDummy.name" should be equal to "A new one"
And the JSON node "data.createDummy.alias" should be equal to "new"
And the JSON node "data.createDummy.description" should be equal to "brand new!"
And the JSON node "data.createFoo.id" should be equal to "/foos/1"
And the JSON node "data.createFoo.name" should be equal to "A new one"
And the JSON node "data.createFoo.bar" should be equal to "new"

@dropSchema
Scenario: Delete an item through a mutation
When I send the following GraphQL request:
"""
mutation {
deleteDummy(input: {id: 1}) {
deleteFoo(input: {id: "/foos/1"}) {
id
}
}
"""
Then print last response
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.deleteDummy.id" should be equal to 1
And the JSON node "data.deleteFoo.id" should be equal to "/foos/1"

@createSchema
@dropSchema
Expand All @@ -77,65 +74,52 @@ Feature: GraphQL mutation support
When I send the following GraphQL request:
"""
mutation {
deleteCompositeRelation(input: {compositeItem: {id: 1}, compositeLabel: {id: 1}}) {
compositeItem {
id
},
compositeLabel {
id
}
deleteCompositeRelation(input: {id: "/composite_relations/compositeItem=1;compositeLabel=1"}) {
id
}
}
"""
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.deleteCompositeRelation.compositeItem.id" should be equal to 1
And the JSON node "data.deleteCompositeRelation.compositeLabel.id" should be equal to 1
And the JSON node "data.deleteCompositeRelation.id" should be equal to "/composite_relations/compositeItem=1;compositeLabel=1"

@createSchema
@dropSchema
Scenario: Modify an item through a mutation
Given there is 1 dummy objects
Given there are 1 foo objects with fake names
When I send the following GraphQL request:
"""
mutation {
updateDummy(input: {id: 1, description: "Modified description."}) {
updateFoo(input: {id: "/foos/1", bar: "Modified description."}) {
id,
name,
description
bar
}
}
"""
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.updateDummy.id" should be equal to 1
And the JSON node "data.updateDummy.name" should be equal to "Dummy #1"
And the JSON node "data.updateDummy.description" should be equal to "Modified description."
And the JSON node "data.updateFoo.id" should be equal to "/foos/1"
And the JSON node "data.updateFoo.name" should be equal to "Hawsepipe"
And the JSON node "data.updateFoo.bar" should be equal to "Modified description."

# Composite identifiers are not supported yet
@createSchema
@dropSchema
Scenario: Modify an item with composite identifiers through a mutation
Given there are Composite identifier objects
When I send the following GraphQL request:
"""
mutation {
updateCompositeRelation(input: {compositeItem: {id: 2}, compositeLabel: {id: 8}, value: "Modified value."}) {
compositeItem {
id
},
compositeLabel {
id
},
updateCompositeRelation(input: {id: "/composite_relations/compositeItem=1;compositeLabel=2", value: "Modified value."}) {
id
value
}
}
"""
#Then the response status code should be 200
#And the response should be in JSON
#And the header "Content-Type" should be equal to "application/json"
#And the JSON node "data.putCompositeRelation.compositeItem.id" should be equal to 1
#And the JSON node "data.putCompositeRelation.compositeLabel.id" should be equal to 1
#And the JSON node "data.putCompositeRelation.value" should be equal to "Modified value."
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.updateCompositeRelation.id" should be equal to "/composite_relations/compositeItem=1;compositeLabel=2"
And the JSON node "data.updateCompositeRelation.value" should be equal to "Modified value."
24 changes: 16 additions & 8 deletions features/graphql/query2.feature → features/graphql/query.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ Feature: GraphQL query support
Given there is 2 dummy objects with relatedDummy
When I have the following GraphQL request:
"""
query DummyWithId($itemId: Int = 1) {
query DummyWithId($itemId: ID = "/dummies/1") {
dummyItem: dummy(id: $itemId) {
id
name
relatedDummy {
id
name
}
}
Expand All @@ -18,13 +20,15 @@ Feature: GraphQL query support
When I send the GraphQL request with variables:
"""
{
"itemId": 2
"itemId": "/dummies/2"
}
"""
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.dummyItem.id" should be equal to "/dummies/2"
And the JSON node "data.dummyItem.name" should be equal to "Dummy #2"
And the JSON node "data.dummyItem.relatedDummy.id" should be equal to "/related_dummies/2"
And the JSON node "data.dummyItem.relatedDummy.name" should be equal to "RelatedDummy #2"

@createSchema
Expand All @@ -34,23 +38,25 @@ Feature: GraphQL query support
When I have the following GraphQL request:
"""
query DummyWithId1 {
dummyItem: dummy(id: 1) {
dummyItem: dummy(id: "/dummies/1") {
name
}
}
query DummyWithId2 {
dummyItem: dummy(id: 2) {
dummyItem: dummy(id: "/dummies/2") {
id
name
}
}
"""
When I send the GraphQL request with operation "DummyWithId2"
And I send the GraphQL request with operation "DummyWithId2"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.dummyItem.id" should be equal to "/dummies/2"
And the JSON node "data.dummyItem.name" should be equal to "Dummy #2"
When I send the GraphQL request with operation "DummyWithId1"
Then the JSON node "data.dummyItem.name" should be equal to "Dummy #1"
And I send the GraphQL request with operation "DummyWithId1"
And the JSON node "data.dummyItem.name" should be equal to "Dummy #1"

@createSchema
@dropSchema
Expand All @@ -59,7 +65,7 @@ Feature: GraphQL query support
When I send the following GraphQL request:
"""
{
dummy(id: 2) {
dummy(id: "/dummies/2") {
name
}
}
Expand All @@ -83,10 +89,12 @@ Feature: GraphQL query support
fragment dummyFields on DummyConnection {
edges {
node {
id
name
relatedDummy {
name
thirdLevel {
id
level
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/Api/IriConverterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\Core\Api;

use ApiPlatform\Core\Exception\InvalidArgumentException;
use ApiPlatform\Core\Exception\ItemNotFoundException;
use ApiPlatform\Core\Exception\RuntimeException;

/**
Expand All @@ -30,6 +31,7 @@ interface IriConverterInterface
* @param array $context
*
* @throws InvalidArgumentException
* @throws ItemNotFoundException
*
* @return object
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt

foreach ($classMetadata->associationMappings as $association => $mapping) {
//Don't join if max depth is enabled and the current depth limit is reached
if (isset($context[AbstractObjectNormalizer::ENABLE_MAX_DEPTH]) && 0 === $currentDepth) {
if (0 === $currentDepth && isset($context[AbstractObjectNormalizer::ENABLE_MAX_DEPTH])) {
continue;
}

Expand Down
47 changes: 33 additions & 14 deletions src/Bridge/Symfony/Bundle/Resources/config/graphql.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
<services>
<service id="api_platform.graphql.executor" class="ApiPlatform\Core\Graphql\Executor" public="false" />

<service id="api_platform.graphql.collection_resolver_factory" class="ApiPlatform\Core\Graphql\Resolver\CollectionResolverFactory" public="false">
<!-- Resolvers -->

<service id="api_platform.graphql.resolver.collection_factory" class="ApiPlatform\Core\Graphql\Resolver\CollectionResolverFactory" public="false">
<argument type="service" id="api_platform.collection_data_provider" />
<argument type="service" id="api_platform.subresource_data_provider" />
<argument type="service" id="serializer" />
Expand All @@ -17,44 +19,61 @@
<argument>%api_platform.collection.pagination.enabled%</argument>
</service>

<service id="api_platform.graphql.item_resolver_factory" class="ApiPlatform\Core\Graphql\Resolver\ItemResolverFactory" public="false">
<argument type="service" id="api_platform.item_data_provider" />
<argument type="service" id="api_platform.subresource_data_provider" />
<service id="api_platform.graphql.resolver.item_factory" class="ApiPlatform\Core\Graphql\Resolver\ItemResolverFactory" public="false">
<argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="serializer" />
<argument type="service" id="api_platform.identifiers_extractor.cached" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
</service>

<service id="api_platform.graphql.item_mutation_resolver_factory" class="ApiPlatform\Core\Graphql\Resolver\ItemMutationResolverFactory" public="false">
<argument type="service" id="api_platform.identifiers_extractor.cached" />
<argument type="service" id="api_platform.item_data_provider" />
<service id="api_platform.graphql.resolver.item_mutation_factory" class="ApiPlatform\Core\Graphql\Resolver\ItemMutationResolverFactory" public="false">
<argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="api_platform.data_persister" />
<argument type="service" id="serializer" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.data_persister" />
</service>

<service id="api_platform.graphql.resolver.resource_field" class="ApiPlatform\Core\Graphql\Resolver\ResourceFieldResolver" public="false">
<argument type="service" id="api_platform.iri_converter" />
</service>

<service id="api_platform.graphql.schema_builder" class="ApiPlatform\Core\Graphql\Type\SchemaBuilder" public="false">
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.graphql.collection_resolver_factory" />
<argument type="service" id="api_platform.graphql.item_resolver_factory" />
<argument type="service" id="api_platform.graphql.item_mutation_resolver_factory" />
<argument type="service" id="api_platform.identifiers_extractor.cached" />
<argument type="service" id="api_platform.graphql.resolver.collection_factory" />
<argument type="service" id="api_platform.graphql.resolver.item_factory" />
<argument type="service" id="api_platform.graphql.resolver.item_mutation_factory" />
<argument type="service" id="api_platform.graphql.resolver.resource_field" />
<argument>%api_platform.collection.pagination.enabled%</argument>
</service>

<!-- Action -->

<service id="api_platform.action.graphql_entrypoint" class="ApiPlatform\Core\Graphql\Action\EntrypointAction" public="true">
<service id="api_platform.graphql.action.entrypoint" class="ApiPlatform\Core\Graphql\Action\EntrypointAction" public="true">
<argument type="service" id="api_platform.graphql.schema_builder" />
<argument type="service" id="api_platform.graphql.executor" />
<argument type="service" id="twig" />
<argument>%kernel.debug%</argument>
<argument>%api_platform.graphql.graphiql.enabled%</argument>
<argument>%api_platform.title%</argument>
</service>

<!-- Serializer -->

<service id="api_platform.graphql.normalizer.item" class="ApiPlatform\Core\Graphql\Serializer\ItemNormalizer" public="false">
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
<argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="api_platform.resource_class_resolver" />
<argument type="service" id="api_platform.property_accessor" />
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
<argument type="service" id="serializer.mapping.class_metadata_factory" on-invalid="ignore" />
<argument type="service" id="api_platform.item_data_provider" on-invalid="ignore" />
<argument>%api_platform.allow_plain_identifiers%</argument>

<tag name="serializer.normalizer" priority="8" />
</service>
</services>

</container>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
http://symfony.com/schema/routing/routing-1.0.xsd">

<route id="api_graphql_entrypoint" path="/graphql">
<default key="_controller">api_platform.action.graphql_entrypoint</default>
<default key="_controller">api_platform.graphql.action.entrypoint</default>
</route>

</routes>
Loading