mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 12:56:05 +03:00
Merge branch 'master' into 0.13.x
# Conflicts: # composer.json # src/Server/OperationParams.php # src/Type/Definition/EnumType.php # src/Type/Definition/InputObjectType.php # src/Type/Definition/InterfaceType.php # src/Type/Definition/ListOfType.php # src/Type/Definition/ResolveInfo.php # src/Type/Definition/UnionType.php # src/Validator/Rules/ValuesOfCorrectType.php # tests/Utils/SchemaExtenderTest.php
This commit is contained in:
commit
d0ab4dc8d8
@ -1,7 +1,6 @@
|
|||||||
# Contributing to GraphQL PHP
|
# Contributing to GraphQL PHP
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
If your contribution requires significant or breaking changes, or if you plan to propose a major new feature,
|
If your contribution requires significant or breaking changes, or if you plan to propose a major new feature,
|
||||||
we recommend you to create an issue on the [GitHub](https://github.com/webonyx/graphql-php/issues) with
|
we recommend you to create an issue on the [GitHub](https://github.com/webonyx/graphql-php/issues) with
|
||||||
a brief proposal and discuss it with us first.
|
a brief proposal and discuss it with us first.
|
||||||
@ -14,9 +13,10 @@ For smaller contributions just use this workflow:
|
|||||||
* Check your changes using `composer check-all`
|
* Check your changes using `composer check-all`
|
||||||
* Send a pull request
|
* Send a pull request
|
||||||
|
|
||||||
## Using GraphQL PHP from a Git checkout
|
## Setup the Development Environment
|
||||||
|
First, copy the URL of your fork and `git clone` it to your local machine.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/webonyx/graphql-php.git
|
|
||||||
cd graphql-php
|
cd graphql-php
|
||||||
composer install
|
composer install
|
||||||
```
|
```
|
||||||
@ -29,28 +29,26 @@ composer install
|
|||||||
Some tests have annotation `@see it('<description>')`. It is used for reference to same tests in [graphql-js implementation](https://github.com/graphql/graphql-js) with the same description.
|
Some tests have annotation `@see it('<description>')`. It is used for reference to same tests in [graphql-js implementation](https://github.com/graphql/graphql-js) with the same description.
|
||||||
|
|
||||||
## Coding Standard
|
## Coding Standard
|
||||||
Coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard). To run the inspection:
|
The coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard).
|
||||||
|
|
||||||
|
Run the inspections:
|
||||||
```sh
|
```sh
|
||||||
./vendor/bin/phpcs
|
./vendor/bin/phpcs
|
||||||
```
|
```
|
||||||
|
|
||||||
Auto-fixing:
|
Apply automatic code style fixes:
|
||||||
```sh
|
```sh
|
||||||
./vendor/bin/phpcbf
|
./vendor/bin/phpcbf
|
||||||
```
|
```
|
||||||
|
|
||||||
## Static analysis
|
## Static analysis
|
||||||
Based on [PHPStan](https://github.com/phpstan/phpstan)
|
Based on [PHPStan](https://github.com/phpstan/phpstan).
|
||||||
```sh
|
```sh
|
||||||
./vendor/bin/phpstan analyse --ansi --memory-limit 256M
|
./vendor/bin/phpstan analyse --ansi --memory-limit 256M
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Running benchmarks
|
## Running benchmarks
|
||||||
|
Benchmarks are run via [PHPBench](https://github.com/phpbench/phpbench).
|
||||||
Benchmarks are run via phpbench:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./vendor/bin/phpbench run .
|
./vendor/bin/phpbench run .
|
||||||
```
|
```
|
||||||
|
32
README.md
32
README.md
@ -14,7 +14,7 @@ composer require webonyx/graphql-php
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
Full documentation is available on the [Documentation site](http://webonyx.github.io/graphql-php/) as well
|
Full documentation is available on the [Documentation site](https://webonyx.github.io/graphql-php/) as well
|
||||||
as in the [docs](docs/) folder of the distribution.
|
as in the [docs](docs/) folder of the distribution.
|
||||||
|
|
||||||
If you don't know what GraphQL is, visit this [official website](http://graphql.org)
|
If you don't know what GraphQL is, visit this [official website](http://graphql.org)
|
||||||
@ -24,11 +24,29 @@ by the Facebook engineering team.
|
|||||||
There are several ready examples in the [examples](examples/) folder of the distribution with specific
|
There are several ready examples in the [examples](examples/) folder of the distribution with specific
|
||||||
README file per example.
|
README file per example.
|
||||||
|
|
||||||
## Contribute
|
## Contributors
|
||||||
Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute.
|
|
||||||
|
|
||||||
## Old README.md
|
This project exists thanks to [all the people](https://github.com/webonyx/graphql-php/graphs/contributors) who contribute. [[Contribute](CONTRIBUTING.md)].
|
||||||
Here is a [link to the old README.md](https://github.com/webonyx/graphql-php/blob/v0.9.14/README.md).
|
|
||||||
|
|
||||||
Keep in mind that it relates to the version 0.9.x. It may contain outdated information for
|
## Backers
|
||||||
newer versions (even though we try to preserve backwards compatibility).
|
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php#backers" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/backers.svg?width=890"></a>
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/webonyx-graphql-php#sponsor)]
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/0/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/0/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/1/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/1/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/2/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/2/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/3/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/3/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/4/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/4/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/5/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/5/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/6/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/6/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/7/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/7/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/8/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/8/avatar.svg"></a>
|
||||||
|
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/9/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/9/avatar.svg"></a>
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [LICENCE](LICENSE).
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
"ext-mbstring": "*"
|
"ext-mbstring": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/coding-standard": "^5.0",
|
"doctrine/coding-standard": "^6.0",
|
||||||
"phpbench/phpbench": "^0.14.0",
|
"phpbench/phpbench": "^0.14.0",
|
||||||
"phpstan/phpstan": "0.10.5",
|
"phpstan/phpstan": "^0.11.4",
|
||||||
"phpstan/phpstan-phpunit": "0.10.0",
|
"phpstan/phpstan-phpunit": "^0.11.0",
|
||||||
"phpstan/phpstan-strict-rules": "0.10.1",
|
"phpstan/phpstan-strict-rules": "^0.11.0",
|
||||||
"phpunit/phpcov": "^5.0",
|
"phpunit/phpcov": "^5.0",
|
||||||
"phpunit/phpunit": "^7.2",
|
"phpunit/phpunit": "^7.2",
|
||||||
"psr/http-message": "^1.0",
|
"psr/http-message": "^1.0",
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
# Integrations
|
# Integrations
|
||||||
|
|
||||||
* [Integration with Relay](https://github.com/ivome/graphql-relay-php)
|
* [Standard Server](executing-queries.md/#using-server) – Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)).
|
||||||
* [Integration with Laravel 5](https://github.com/Folkloreatelier/laravel-graphql) + [Relay Helpers for Laravel](https://github.com/nuwave/laravel-graphql-relay) + [Nuwave Lighthouse](https://github.com/nuwave/lighthouse)
|
* [Relay Library for graphql-php](https://github.com/ivome/graphql-relay-php) – Helps construct Relay related schema definitions.
|
||||||
* [Symfony Bundle](https://github.com/overblog/GraphQLBundle) by Overblog
|
* [Lighthouse](https://github.com/nuwave/lighthouse) – Laravel based, uses Schema Definition Language
|
||||||
* Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)) via [Standard Server](executing-queries.md/#using-server)
|
* [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) – Bundle for Symfony
|
||||||
|
* [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress
|
||||||
|
|
||||||
# GraphQL PHP Tools
|
# GraphQL PHP Tools
|
||||||
|
|
||||||
* Define types with Doctrine ORM annotations ([for PHP7.1](https://github.com/Ecodev/graphql-doctrine), for [earlier PHP versions](https://github.com/rahuljayaraman/doctrine-graphql))
|
* [GraphQLite](https://graphqlite.thecodingmachine.io) – Define your complete schema with annotations
|
||||||
* [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
|
* [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) – Define types with Doctrine ORM annotations
|
||||||
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental)
|
* [DataLoaderPHP](https://github.com/overblog/dataloader-php) – as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
|
||||||
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server
|
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) – A PSR-15 middleware to support file uploads in GraphQL.
|
||||||
* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) - Simple library that provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches.
|
* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) – Provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches.
|
||||||
|
* [GraphQL Utils](https://github.com/simPod/GraphQL-Utils) – Objective schema definition builders (no need for arrays anymore) and `DateTime` scalar
|
||||||
|
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server _(experimental)_
|
||||||
|
|
||||||
# General GraphQL Tools
|
# General GraphQL Tools
|
||||||
|
|
||||||
* [GraphiQL](https://github.com/graphql/graphiql) - An in-browser IDE for exploring GraphQL
|
* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) – GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).
|
||||||
|
* [GraphiQL](https://github.com/graphql/graphiql) – An in-browser IDE for exploring GraphQL
|
||||||
* [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
|
* [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
|
||||||
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) -
|
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) –
|
||||||
GraphiQL as Google Chrome extension
|
GraphiQL as Google Chrome extension
|
||||||
* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) - GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).
|
|
||||||
|
@ -136,13 +136,13 @@ So for example following batch will require single DB request (if user field is
|
|||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"query": "{user(id: 1)} { id }"
|
"query": "{user(id: 1) { id }}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"query": "{user(id: 2)} { id }"
|
"query": "{user(id: 2) { id }}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"query": "{user(id: 3)} { id }"
|
"query": "{user(id: 3) { id }}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
@ -114,7 +114,7 @@ Option | Type | Notes
|
|||||||
name | `string` | **Required.** Name of the input field. When not set - inferred from **fields** array key
|
name | `string` | **Required.** Name of the input field. When not set - inferred from **fields** array key
|
||||||
type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**Scalar**, **Enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers)
|
type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**Scalar**, **Enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers)
|
||||||
description | `string` | Plain-text description of this input field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
|
description | `string` | Plain-text description of this input field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
|
||||||
defaultValue | `scalar` | Default value of this input field
|
defaultValue | `scalar` | Default value of this input field. Use the internal value if specifying a default for an **enum** type
|
||||||
|
|
||||||
# Using Input Object Type
|
# Using Input Object Type
|
||||||
In the example above we defined our InputObjectType. Now let's use it in one of field arguments:
|
In the example above we defined our InputObjectType. Now let's use it in one of field arguments:
|
||||||
|
@ -94,7 +94,7 @@ Option | Type | Notes
|
|||||||
name | `string` | **Required.** Name of the argument. When not set - inferred from **args** array key
|
name | `string` | **Required.** Name of the argument. When not set - inferred from **args** array key
|
||||||
type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**scalar**, **enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers)
|
type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**scalar**, **enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers)
|
||||||
description | `string` | Plain-text description of this argument for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
|
description | `string` | Plain-text description of this argument for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
|
||||||
defaultValue | `scalar` | Default value for this argument
|
defaultValue | `scalar` | Default value for this argument. Use the internal value if specifying a default for an **enum** type
|
||||||
|
|
||||||
# Shorthand field definitions
|
# Shorthand field definitions
|
||||||
Fields can be also defined in **shorthand** notation (with only **name** and **type** options):
|
Fields can be also defined in **shorthand** notation (with only **name** and **type** options):
|
||||||
|
@ -81,7 +81,7 @@ $cacheFilename = 'cached_schema.php';
|
|||||||
|
|
||||||
if (!file_exists($cacheFilename)) {
|
if (!file_exists($cacheFilename)) {
|
||||||
$document = Parser::parse(file_get_contents('./schema.graphql'));
|
$document = Parser::parse(file_get_contents('./schema.graphql'));
|
||||||
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true));
|
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true) . ";\n");
|
||||||
} else {
|
} else {
|
||||||
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
|
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ parameters:
|
|||||||
ignoreErrors:
|
ignoreErrors:
|
||||||
- "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~"
|
- "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~"
|
||||||
- "~(Method|Property) .+::.+(\\(\\))? (has parameter \\$\\w+ with no|has no return|has no) typehint specified~"
|
- "~(Method|Property) .+::.+(\\(\\))? (has parameter \\$\\w+ with no|has no return|has no) typehint specified~"
|
||||||
|
- "~Variable property access on .+~"
|
||||||
|
- "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" # TODO get rid of
|
||||||
|
|
||||||
includes:
|
includes:
|
||||||
- vendor/phpstan/phpstan-phpunit/extension.neon
|
- vendor/phpstan/phpstan-phpunit/extension.neon
|
||||||
|
@ -122,13 +122,13 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
|||||||
|
|
||||||
if ($previous instanceof ClientAware) {
|
if ($previous instanceof ClientAware) {
|
||||||
$this->isClientSafe = $previous->isClientSafe();
|
$this->isClientSafe = $previous->isClientSafe();
|
||||||
$this->category = $previous->getCategory() ?: static::CATEGORY_INTERNAL;
|
$this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL;
|
||||||
} elseif ($previous) {
|
} elseif ($previous) {
|
||||||
$this->isClientSafe = false;
|
$this->isClientSafe = false;
|
||||||
$this->category = static::CATEGORY_INTERNAL;
|
$this->category = self::CATEGORY_INTERNAL;
|
||||||
} else {
|
} else {
|
||||||
$this->isClientSafe = true;
|
$this->isClientSafe = true;
|
||||||
$this->category = static::CATEGORY_GRAPHQL;
|
$this->category = self::CATEGORY_GRAPHQL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,11 +148,11 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
|||||||
if ($error instanceof self) {
|
if ($error instanceof self) {
|
||||||
if ($error->path && $error->nodes) {
|
if ($error->path && $error->nodes) {
|
||||||
return $error;
|
return $error;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
$nodes = $nodes ?: $error->nodes;
|
$nodes = $nodes ?: $error->nodes;
|
||||||
$path = $path ?: $error->path;
|
$path = $path ?: $error->path;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$source = $positions = $originalError = null;
|
$source = $positions = $originalError = null;
|
||||||
$extensions = [];
|
$extensions = [];
|
||||||
|
@ -231,7 +231,7 @@ class FormattedError
|
|||||||
*
|
*
|
||||||
* @param mixed[] $formattedError
|
* @param mixed[] $formattedError
|
||||||
* @param Throwable $e
|
* @param Throwable $e
|
||||||
* @param bool $debug
|
* @param bool|int $debug
|
||||||
*
|
*
|
||||||
* @return mixed[]
|
* @return mixed[]
|
||||||
*
|
*
|
||||||
@ -297,7 +297,7 @@ class FormattedError
|
|||||||
* Prepares final error formatter taking in account $debug flags.
|
* Prepares final error formatter taking in account $debug flags.
|
||||||
* If initial formatter is not set, FormattedError::createFromException is used
|
* If initial formatter is not set, FormattedError::createFromException is used
|
||||||
*
|
*
|
||||||
* @param bool $debug
|
* @param bool|int $debug
|
||||||
*
|
*
|
||||||
* @return callable|callable
|
* @return callable|callable
|
||||||
*/
|
*/
|
||||||
|
@ -7,8 +7,6 @@ namespace GraphQL\Error;
|
|||||||
use LogicException;
|
use LogicException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class InvariantVoilation
|
|
||||||
*
|
|
||||||
* Note:
|
* Note:
|
||||||
* This exception should not inherit base Error exception as it is raised when there is an error somewhere in
|
* This exception should not inherit base Error exception as it is raised when there is an error somewhere in
|
||||||
* user-land code
|
* user-land code
|
||||||
|
@ -7,8 +7,6 @@ namespace GraphQL\Error;
|
|||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class UserError
|
|
||||||
*
|
|
||||||
* Error caused by actions of GraphQL clients. Can be safely displayed to a client...
|
* Error caused by actions of GraphQL clients. Can be safely displayed to a client...
|
||||||
*/
|
*/
|
||||||
class UserError extends RuntimeException implements ClientAware
|
class UserError extends RuntimeException implements ClientAware
|
||||||
|
@ -61,8 +61,6 @@ final class Warning
|
|||||||
} elseif ($suppress === false) {
|
} elseif ($suppress === false) {
|
||||||
self::$enableWarnings = self::ALL;
|
self::$enableWarnings = self::ALL;
|
||||||
} else {
|
} else {
|
||||||
$suppress = (int) $suppress;
|
|
||||||
|
|
||||||
self::$enableWarnings &= ~$suppress;
|
self::$enableWarnings &= ~$suppress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,8 +84,6 @@ final class Warning
|
|||||||
} elseif ($enable === false) {
|
} elseif ($enable === false) {
|
||||||
self::$enableWarnings = 0;
|
self::$enableWarnings = 0;
|
||||||
} else {
|
} else {
|
||||||
$enable = (int) $enable;
|
|
||||||
|
|
||||||
self::$enableWarnings |= $enable;
|
self::$enableWarnings |= $enable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ class ExecutionResult implements JsonSerializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($this->extensions)) {
|
if (! empty($this->extensions)) {
|
||||||
$result['extensions'] = (array) $this->extensions;
|
$result['extensions'] = $this->extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
|
@ -13,8 +13,6 @@ use function is_object;
|
|||||||
use function method_exists;
|
use function method_exists;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class SyncPromise
|
|
||||||
*
|
|
||||||
* Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
|
* Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
|
||||||
* (using queue to defer promises execution)
|
* (using queue to defer promises execution)
|
||||||
*/
|
*/
|
||||||
|
@ -160,7 +160,9 @@ class SyncPromiseAdapter implements PromiseAdapter
|
|||||||
|
|
||||||
if ($syncPromise->state === SyncPromise::FULFILLED) {
|
if ($syncPromise->state === SyncPromise::FULFILLED) {
|
||||||
return $syncPromise->result;
|
return $syncPromise->result;
|
||||||
} elseif ($syncPromise->state === SyncPromise::REJECTED) {
|
}
|
||||||
|
|
||||||
|
if ($syncPromise->state === SyncPromise::REJECTED) {
|
||||||
throw $syncPromise->result;
|
throw $syncPromise->result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (! $operation) {
|
if ($operation === null) {
|
||||||
if ($operationName) {
|
if ($operationName) {
|
||||||
$errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName));
|
$errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName));
|
||||||
} else {
|
} else {
|
||||||
@ -165,7 +165,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
$variableValues = null;
|
$variableValues = null;
|
||||||
if ($operation) {
|
if ($operation !== null) {
|
||||||
[$coercionErrors, $coercedVariableValues] = Values::getVariableValues(
|
[$coercionErrors, $coercedVariableValues] = Values::getVariableValues(
|
||||||
$schema,
|
$schema,
|
||||||
$operation->variableDefinitions ?: [],
|
$operation->variableDefinitions ?: [],
|
||||||
@ -182,6 +182,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
}
|
}
|
||||||
Utils::invariant($operation, 'Has operation if no errors.');
|
Utils::invariant($operation, 'Has operation if no errors.');
|
||||||
Utils::invariant($variableValues !== null, 'Has variables if no errors.');
|
Utils::invariant($variableValues !== null, 'Has variables if no errors.');
|
||||||
|
|
||||||
return new ExecutionContext(
|
return new ExecutionContext(
|
||||||
$schema,
|
$schema,
|
||||||
$fragments,
|
$fragments,
|
||||||
@ -206,6 +207,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
// resolved Promise.
|
// resolved Promise.
|
||||||
$data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue);
|
$data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue);
|
||||||
$result = $this->buildResponse($data);
|
$result = $this->buildResponse($data);
|
||||||
|
|
||||||
// Note: we deviate here from the reference implementation a bit by always returning promise
|
// Note: we deviate here from the reference implementation a bit by always returning promise
|
||||||
// But for the "sync" case it is always fulfilled
|
// But for the "sync" case it is always fulfilled
|
||||||
return $this->isPromise($result)
|
return $this->isPromise($result)
|
||||||
@ -228,6 +230,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
if ($data !== null) {
|
if ($data !== null) {
|
||||||
$data = (array) $data;
|
$data = (array) $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ExecutionResult($data, $this->exeContext->errors);
|
return new ExecutionResult($data, $this->exeContext->errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,13 +260,16 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
null,
|
null,
|
||||||
function ($error) {
|
function ($error) {
|
||||||
$this->exeContext->addError($error);
|
$this->exeContext->addError($error);
|
||||||
|
|
||||||
return $this->exeContext->promises->createFulfilled(null);
|
return $this->exeContext->promises->createFulfilled(null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
} catch (Error $error) {
|
} catch (Error $error) {
|
||||||
$this->exeContext->addError($error);
|
$this->exeContext->addError($error);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,6 +292,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
[$operation]
|
[$operation]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $queryType;
|
return $queryType;
|
||||||
case 'mutation':
|
case 'mutation':
|
||||||
$mutationType = $schema->getMutationType();
|
$mutationType = $schema->getMutationType();
|
||||||
@ -295,6 +302,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
[$operation]
|
[$operation]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $mutationType;
|
return $mutationType;
|
||||||
case 'subscription':
|
case 'subscription':
|
||||||
$subscriptionType = $schema->getSubscriptionType();
|
$subscriptionType = $schema->getSubscriptionType();
|
||||||
@ -304,6 +312,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
[$operation]
|
[$operation]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $subscriptionType;
|
return $subscriptionType;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -378,6 +387,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,10 +417,8 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
$node,
|
$node,
|
||||||
$variableValues
|
$variableValues
|
||||||
);
|
);
|
||||||
if (isset($include['if']) && $include['if'] === false) {
|
|
||||||
return false;
|
return ! isset($include['if']) || $include['if'] !== false;
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -445,6 +453,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
if ($conditionalType instanceof AbstractType) {
|
if ($conditionalType instanceof AbstractType) {
|
||||||
return $this->exeContext->schema->isPossibleType($conditionalType, $type);
|
return $this->exeContext->schema->isPossibleType($conditionalType, $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,10 +483,12 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
if ($promise) {
|
if ($promise) {
|
||||||
return $promise->then(static function ($resolvedResult) use ($responseName, $results) {
|
return $promise->then(static function ($resolvedResult) use ($responseName, $results) {
|
||||||
$results[$responseName] = $resolvedResult;
|
$results[$responseName] = $resolvedResult;
|
||||||
|
|
||||||
return $results;
|
return $results;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$results[$responseName] = $result;
|
$results[$responseName] = $result;
|
||||||
|
|
||||||
return $results;
|
return $results;
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
@ -487,6 +498,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
return self::fixResultsIfEmptyArray($resolvedResults);
|
return self::fixResultsIfEmptyArray($resolvedResults);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::fixResultsIfEmptyArray($result);
|
return self::fixResultsIfEmptyArray($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -554,6 +566,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
$path,
|
$path,
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,12 +591,17 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
$typeNameMetaFieldDef = $typeNameMetaFieldDef ?: Introspection::typeNameMetaFieldDef();
|
$typeNameMetaFieldDef = $typeNameMetaFieldDef ?: Introspection::typeNameMetaFieldDef();
|
||||||
if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) {
|
if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) {
|
||||||
return $schemaMetaFieldDef;
|
return $schemaMetaFieldDef;
|
||||||
} elseif ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) {
|
}
|
||||||
|
|
||||||
|
if ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) {
|
||||||
return $typeMetaFieldDef;
|
return $typeMetaFieldDef;
|
||||||
} elseif ($fieldName === $typeNameMetaFieldDef->name) {
|
}
|
||||||
|
|
||||||
|
if ($fieldName === $typeNameMetaFieldDef->name) {
|
||||||
return $typeNameMetaFieldDef;
|
return $typeNameMetaFieldDef;
|
||||||
}
|
}
|
||||||
$tmp = $parentType->getFields();
|
$tmp = $parentType->getFields();
|
||||||
|
|
||||||
return $tmp[$fieldName] ?? null;
|
return $tmp[$fieldName] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,6 +628,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
$fieldNode,
|
$fieldNode,
|
||||||
$this->exeContext->variableValues
|
$this->exeContext->variableValues
|
||||||
);
|
);
|
||||||
|
|
||||||
return $resolveFn($source, $args, $context, $info);
|
return $resolveFn($source, $args, $context, $info);
|
||||||
} catch (Exception $error) {
|
} catch (Exception $error) {
|
||||||
return $error;
|
return $error;
|
||||||
@ -663,15 +682,18 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
null,
|
null,
|
||||||
function ($error) use ($exeContext) {
|
function ($error) use ($exeContext) {
|
||||||
$exeContext->addError($error);
|
$exeContext->addError($error);
|
||||||
|
|
||||||
return $this->exeContext->promises->createFulfilled(null);
|
return $this->exeContext->promises->createFulfilled(null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $completed;
|
return $completed;
|
||||||
} catch (Error $err) {
|
} catch (Error $err) {
|
||||||
// If `completeValueWithLocatedError` returned abruptly (threw an error), log the error
|
// If `completeValueWithLocatedError` returned abruptly (threw an error), log the error
|
||||||
// and return null.
|
// and return null.
|
||||||
$exeContext->addError($err);
|
$exeContext->addError($err);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -716,6 +738,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $completed;
|
return $completed;
|
||||||
} catch (Exception $error) {
|
} catch (Exception $error) {
|
||||||
throw Error::createLocatedError($error, $fieldNodes, $path);
|
throw Error::createLocatedError($error, $fieldNodes, $path);
|
||||||
@ -786,6 +809,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.'
|
'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $completed;
|
return $completed;
|
||||||
}
|
}
|
||||||
// If result is null-like, return null.
|
// If result is null-like, return null.
|
||||||
@ -863,8 +887,10 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
Utils::printSafe($promise)
|
Utils::printSafe($promise)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $promise;
|
return $promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -891,6 +917,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
return $callback($resolved, $value);
|
return $callback($resolved, $value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return $callback($previous, $value);
|
return $callback($previous, $value);
|
||||||
},
|
},
|
||||||
$initialValue
|
$initialValue
|
||||||
@ -928,6 +955,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
}
|
}
|
||||||
$completedItems[] = $completedItem;
|
$completedItems[] = $completedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $containsPromise ? $this->exeContext->promises->all($completedItems) : $completedItems;
|
return $containsPromise ? $this->exeContext->promises->all($completedItems) : $completedItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1001,6 +1029,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->completeObjectValue(
|
return $this->completeObjectValue(
|
||||||
$this->ensureValidRuntimeType(
|
$this->ensureValidRuntimeType(
|
||||||
$runtimeType,
|
$runtimeType,
|
||||||
@ -1076,9 +1105,11 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
return $possibleTypes[$index];
|
return $possibleTypes[$index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1111,6 +1142,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
if (! $isTypeOfResult) {
|
if (! $isTypeOfResult) {
|
||||||
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
|
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->collectAndExecuteSubfields(
|
return $this->collectAndExecuteSubfields(
|
||||||
$returnType,
|
$returnType,
|
||||||
$fieldNodes,
|
$fieldNodes,
|
||||||
@ -1123,6 +1155,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
|
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->collectAndExecuteSubfields(
|
return $this->collectAndExecuteSubfields(
|
||||||
$returnType,
|
$returnType,
|
||||||
$fieldNodes,
|
$fieldNodes,
|
||||||
@ -1164,6 +1197,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
&$result
|
&$result
|
||||||
) {
|
) {
|
||||||
$subFieldNodes = $this->collectSubFields($returnType, $fieldNodes);
|
$subFieldNodes = $this->collectSubFields($returnType, $fieldNodes);
|
||||||
|
|
||||||
return $this->executeFields($returnType, $result, $path, $subFieldNodes);
|
return $this->executeFields($returnType, $result, $path, $subFieldNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1189,6 +1223,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
}
|
}
|
||||||
$this->subFieldCache[$returnType][$fieldNodes] = $subFieldNodes;
|
$this->subFieldCache[$returnType][$fieldNodes] = $subFieldNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->subFieldCache[$returnType][$fieldNodes];
|
return $this->subFieldCache[$returnType][$fieldNodes];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1222,6 +1257,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
if (! $containsPromise) {
|
if (! $containsPromise) {
|
||||||
return self::fixResultsIfEmptyArray($finalResults);
|
return self::fixResultsIfEmptyArray($finalResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, results is a map from field name to the result
|
// Otherwise, results is a map from field name to the result
|
||||||
// of resolving that field, which is possibly a promise. Return
|
// of resolving that field, which is possibly a promise. Return
|
||||||
// a promise that will return this same map, but with any
|
// a promise that will return this same map, but with any
|
||||||
@ -1241,6 +1277,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
if ($results === []) {
|
if ($results === []) {
|
||||||
return new stdClass();
|
return new stdClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1260,11 +1297,13 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
$keys = array_keys($assoc);
|
$keys = array_keys($assoc);
|
||||||
$valuesAndPromises = array_values($assoc);
|
$valuesAndPromises = array_values($assoc);
|
||||||
$promise = $this->exeContext->promises->all($valuesAndPromises);
|
$promise = $this->exeContext->promises->all($valuesAndPromises);
|
||||||
|
|
||||||
return $promise->then(static function ($values) use ($keys) {
|
return $promise->then(static function ($values) use ($keys) {
|
||||||
$resolvedResults = [];
|
$resolvedResults = [];
|
||||||
foreach ($values as $i => $value) {
|
foreach ($values as $i => $value) {
|
||||||
$resolvedResults[$keys[$i]] = $value;
|
$resolvedResults[$keys[$i]] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::fixResultsIfEmptyArray($resolvedResults);
|
return self::fixResultsIfEmptyArray($resolvedResults);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1318,6 +1357,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $runtimeType;
|
return $runtimeType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,11 +86,13 @@ class Collector
|
|||||||
} else {
|
} else {
|
||||||
$this->runtime->addError(new Error('Must provide an operation.'));
|
$this->runtime->addError(new Error('Must provide an operation.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hasMultipleAssumedOperations) {
|
if ($hasMultipleAssumedOperations) {
|
||||||
$this->runtime->addError(new Error('Must provide operation name if query contains multiple operations.'));
|
$this->runtime->addError(new Error('Must provide operation name if query contains multiple operations.'));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +213,9 @@ class Collector
|
|||||||
|
|
||||||
if (isset($this->visitedFragments[$fragmentName])) {
|
if (isset($this->visitedFragments[$fragmentName])) {
|
||||||
continue;
|
continue;
|
||||||
} elseif (! isset($this->fragments[$fragmentName])) {
|
}
|
||||||
|
|
||||||
|
if (! isset($this->fragments[$fragmentName])) {
|
||||||
$this->runtime->addError(new Error(
|
$this->runtime->addError(new Error(
|
||||||
sprintf('Fragment "%s" does not exist.', $fragmentName),
|
sprintf('Fragment "%s" does not exist.', $fragmentName),
|
||||||
$selection
|
$selection
|
||||||
|
@ -147,6 +147,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||||||
if ($emptyObjectAsStdClass && empty($array)) {
|
if ($emptyObjectAsStdClass && empty($array)) {
|
||||||
return new stdClass();
|
return new stdClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +156,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||||||
foreach ($value as $key => $item) {
|
foreach ($value as $key => $item) {
|
||||||
$array[$key] = self::resultToArray($item);
|
$array[$key] = self::resultToArray($item);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,6 +367,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||||||
// short-circuit evaluation for __typename
|
// short-circuit evaluation for __typename
|
||||||
if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
|
if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
|
||||||
$ctx->result->{$ctx->shared->resultName} = $ctx->type->name;
|
$ctx->result->{$ctx->shared->resultName} = $ctx->type->name;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ class Location
|
|||||||
$tmp = new static();
|
$tmp = new static();
|
||||||
$tmp->start = $start;
|
$tmp->start = $start;
|
||||||
$tmp->end = $end;
|
$tmp->end = $end;
|
||||||
|
|
||||||
return $tmp;
|
return $tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +105,7 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
|
|||||||
if ($list instanceof self) {
|
if ($list instanceof self) {
|
||||||
$list = $list->nodes;
|
$list = $list->nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new NodeList(array_merge($this->nodes, $list));
|
return new NodeList(array_merge($this->nodes, $list));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,9 +114,8 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
|
|||||||
*/
|
*/
|
||||||
public function getIterator()
|
public function getIterator()
|
||||||
{
|
{
|
||||||
$count = count($this->nodes);
|
foreach ($this->nodes as $key => $_) {
|
||||||
for ($i = 0; $i < $count; $i++) {
|
yield $this->offsetGet($key);
|
||||||
yield $this->offsetGet($i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,22 @@ use function preg_match;
|
|||||||
*/
|
*/
|
||||||
class Lexer
|
class Lexer
|
||||||
{
|
{
|
||||||
|
private const TOKEN_BANG = 33;
|
||||||
|
private const TOKEN_HASH = 35;
|
||||||
|
private const TOKEN_DOLLAR = 36;
|
||||||
|
private const TOKEN_AMP = 38;
|
||||||
|
private const TOKEN_PAREN_L = 40;
|
||||||
|
private const TOKEN_PAREN_R = 41;
|
||||||
|
private const TOKEN_DOT = 46;
|
||||||
|
private const TOKEN_COLON = 58;
|
||||||
|
private const TOKEN_EQUALS = 61;
|
||||||
|
private const TOKEN_AT = 64;
|
||||||
|
private const TOKEN_BRACKET_L = 91;
|
||||||
|
private const TOKEN_BRACKET_R = 93;
|
||||||
|
private const TOKEN_BRACE_L = 123;
|
||||||
|
private const TOKEN_PIPE = 124;
|
||||||
|
private const TOKEN_BRACE_R = 125;
|
||||||
|
|
||||||
/** @var Source */
|
/** @var Source */
|
||||||
public $source;
|
public $source;
|
||||||
|
|
||||||
@ -93,6 +109,7 @@ class Lexer
|
|||||||
public function advance()
|
public function advance()
|
||||||
{
|
{
|
||||||
$this->lastToken = $this->token;
|
$this->lastToken = $this->token;
|
||||||
|
|
||||||
return $this->token = $this->lookahead();
|
return $this->token = $this->lookahead();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,44 +148,45 @@ class Lexer
|
|||||||
[, $code, $bytes] = $this->readChar(true);
|
[, $code, $bytes] = $this->readChar(true);
|
||||||
|
|
||||||
switch ($code) {
|
switch ($code) {
|
||||||
case 33: // !
|
case self::TOKEN_BANG:
|
||||||
return new Token(Token::BANG, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::BANG, $position, $position + 1, $line, $col, $prev);
|
||||||
case 35: // #
|
case self::TOKEN_HASH: // #
|
||||||
$this->moveStringCursor(-1, -1 * $bytes);
|
$this->moveStringCursor(-1, -1 * $bytes);
|
||||||
|
|
||||||
return $this->readComment($line, $col, $prev);
|
return $this->readComment($line, $col, $prev);
|
||||||
case 36: // $
|
case self::TOKEN_DOLLAR:
|
||||||
return new Token(Token::DOLLAR, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::DOLLAR, $position, $position + 1, $line, $col, $prev);
|
||||||
case 38: // &
|
case self::TOKEN_AMP:
|
||||||
return new Token(Token::AMP, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::AMP, $position, $position + 1, $line, $col, $prev);
|
||||||
case 40: // (
|
case self::TOKEN_PAREN_L:
|
||||||
return new Token(Token::PAREN_L, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::PAREN_L, $position, $position + 1, $line, $col, $prev);
|
||||||
case 41: // )
|
case self::TOKEN_PAREN_R:
|
||||||
return new Token(Token::PAREN_R, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::PAREN_R, $position, $position + 1, $line, $col, $prev);
|
||||||
case 46: // .
|
case self::TOKEN_DOT: // .
|
||||||
[, $charCode1] = $this->readChar(true);
|
[, $charCode1] = $this->readChar(true);
|
||||||
[, $charCode2] = $this->readChar(true);
|
[, $charCode2] = $this->readChar(true);
|
||||||
|
|
||||||
if ($charCode1 === 46 && $charCode2 === 46) {
|
if ($charCode1 === self::TOKEN_DOT && $charCode2 === self::TOKEN_DOT) {
|
||||||
return new Token(Token::SPREAD, $position, $position + 3, $line, $col, $prev);
|
return new Token(Token::SPREAD, $position, $position + 3, $line, $col, $prev);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 58: // :
|
case self::TOKEN_COLON:
|
||||||
return new Token(Token::COLON, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::COLON, $position, $position + 1, $line, $col, $prev);
|
||||||
case 61: // =
|
case self::TOKEN_EQUALS:
|
||||||
return new Token(Token::EQUALS, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::EQUALS, $position, $position + 1, $line, $col, $prev);
|
||||||
case 64: // @
|
case self::TOKEN_AT:
|
||||||
return new Token(Token::AT, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::AT, $position, $position + 1, $line, $col, $prev);
|
||||||
case 91: // [
|
case self::TOKEN_BRACKET_L:
|
||||||
return new Token(Token::BRACKET_L, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::BRACKET_L, $position, $position + 1, $line, $col, $prev);
|
||||||
case 93: // ]
|
case self::TOKEN_BRACKET_R:
|
||||||
return new Token(Token::BRACKET_R, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::BRACKET_R, $position, $position + 1, $line, $col, $prev);
|
||||||
case 123: // {
|
case self::TOKEN_BRACE_L:
|
||||||
return new Token(Token::BRACE_L, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::BRACE_L, $position, $position + 1, $line, $col, $prev);
|
||||||
case 124: // |
|
case self::TOKEN_PIPE:
|
||||||
return new Token(Token::PIPE, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::PIPE, $position, $position + 1, $line, $col, $prev);
|
||||||
case 125: // }
|
case self::TOKEN_BRACE_R:
|
||||||
return new Token(Token::BRACE_R, $position, $position + 1, $line, $col, $prev);
|
return new Token(Token::BRACE_R, $position, $position + 1, $line, $col, $prev);
|
||||||
|
|
||||||
// A-Z
|
// A-Z
|
||||||
case 65:
|
case 65:
|
||||||
case 66:
|
case 66:
|
||||||
@ -227,6 +245,7 @@ class Lexer
|
|||||||
case 122:
|
case 122:
|
||||||
return $this->moveStringCursor(-1, -1 * $bytes)
|
return $this->moveStringCursor(-1, -1 * $bytes)
|
||||||
->readName($line, $col, $prev);
|
->readName($line, $col, $prev);
|
||||||
|
|
||||||
// -
|
// -
|
||||||
case 45:
|
case 45:
|
||||||
// 0-9
|
// 0-9
|
||||||
@ -242,6 +261,7 @@ class Lexer
|
|||||||
case 57:
|
case 57:
|
||||||
return $this->moveStringCursor(-1, -1 * $bytes)
|
return $this->moveStringCursor(-1, -1 * $bytes)
|
||||||
->readNumber($line, $col, $prev);
|
->readNumber($line, $col, $prev);
|
||||||
|
|
||||||
// "
|
// "
|
||||||
case 34:
|
case 34:
|
||||||
[, $nextCode] = $this->readChar();
|
[, $nextCode] = $this->readChar();
|
||||||
@ -564,11 +584,11 @@ class Lexer
|
|||||||
$prev,
|
$prev,
|
||||||
BlockString::value($value)
|
BlockString::value($value)
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
// move cursor back to before the first quote
|
// move cursor back to before the first quote
|
||||||
$this->moveStringCursor(-2, -2);
|
$this->moveStringCursor(-2, -2);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertValidBlockStringCharacterCode($code, $this->position);
|
$this->assertValidBlockStringCharacterCode($code, $this->position);
|
||||||
$this->moveStringCursor(1, $bytes);
|
$this->moveStringCursor(1, $bytes);
|
||||||
|
@ -439,7 +439,6 @@ class Parser
|
|||||||
case 'mutation':
|
case 'mutation':
|
||||||
case 'subscription':
|
case 'subscription':
|
||||||
return $this->parseOperationDefinition();
|
return $this->parseOperationDefinition();
|
||||||
|
|
||||||
case 'fragment':
|
case 'fragment':
|
||||||
return $this->parseFragmentDefinition();
|
return $this->parseFragmentDefinition();
|
||||||
}
|
}
|
||||||
@ -827,7 +826,9 @@ class Parser
|
|||||||
'value' => $token->value === 'true',
|
'value' => $token->value === 'true',
|
||||||
'loc' => $this->loc($token),
|
'loc' => $this->loc($token),
|
||||||
]);
|
]);
|
||||||
} elseif ($token->value === 'null') {
|
}
|
||||||
|
|
||||||
|
if ($token->value === 'null') {
|
||||||
$this->lexer->advance();
|
$this->lexer->advance();
|
||||||
|
|
||||||
return new NullValueNode([
|
return new NullValueNode([
|
||||||
|
@ -115,6 +115,7 @@ class Printer
|
|||||||
$varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
|
$varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
|
||||||
$directives = $this->join($node->directives, ' ');
|
$directives = $this->join($node->directives, ' ');
|
||||||
$selectionSet = $node->selectionSet;
|
$selectionSet = $node->selectionSet;
|
||||||
|
|
||||||
// Anonymous queries with no directives or variable definitions can use
|
// Anonymous queries with no directives or variable definitions can use
|
||||||
// the query short form.
|
// the query short form.
|
||||||
return ! $name && ! $directives && ! $varDefs && $op === 'query'
|
return ! $name && ! $directives && ! $varDefs && $op === 'query'
|
||||||
|
@ -62,13 +62,10 @@ class OperationParams
|
|||||||
* Creates an instance from given array
|
* Creates an instance from given array
|
||||||
*
|
*
|
||||||
* @param mixed[] $params
|
* @param mixed[] $params
|
||||||
* @param bool $readonly
|
|
||||||
*
|
|
||||||
* @return OperationParams
|
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
public static function create(array $params, $readonly = false)
|
public static function create(array $params, bool $readonly = false) : OperationParams
|
||||||
{
|
{
|
||||||
$instance = new static();
|
$instance = new static();
|
||||||
|
|
||||||
@ -108,7 +105,7 @@ class OperationParams
|
|||||||
$instance->operation = $params['operationname'];
|
$instance->operation = $params['operationname'];
|
||||||
$instance->variables = $params['variables'];
|
$instance->variables = $params['variables'];
|
||||||
$instance->extensions = $params['extensions'];
|
$instance->extensions = $params['extensions'];
|
||||||
$instance->readOnly = (bool) $readonly;
|
$instance->readOnly = $readonly;
|
||||||
|
|
||||||
// Apollo server/client compatibility: look for the queryid in extensions
|
// Apollo server/client compatibility: look for the queryid in extensions
|
||||||
if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) {
|
if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) {
|
||||||
|
@ -225,15 +225,11 @@ class ServerConfig
|
|||||||
/**
|
/**
|
||||||
* Allow batching queries (disabled by default)
|
* Allow batching queries (disabled by default)
|
||||||
*
|
*
|
||||||
* @param bool $enableBatching
|
|
||||||
*
|
|
||||||
* @return self
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
public function setQueryBatching($enableBatching)
|
public function setQueryBatching(bool $enableBatching) : self
|
||||||
{
|
{
|
||||||
$this->queryBatching = (bool) $enableBatching;
|
$this->queryBatching = $enableBatching;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,7 @@ class StandardServer
|
|||||||
StreamInterface $writableBodyStream
|
StreamInterface $writableBodyStream
|
||||||
) {
|
) {
|
||||||
$result = $this->executePsrRequest($request);
|
$result = $this->executePsrRequest($request);
|
||||||
|
|
||||||
return $this->helper->toPsrResponse($result, $response, $writableBodyStream);
|
return $this->helper->toPsrResponse($result, $response, $writableBodyStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,6 +166,7 @@ class StandardServer
|
|||||||
public function executePsrRequest(ServerRequestInterface $request)
|
public function executePsrRequest(ServerRequestInterface $request)
|
||||||
{
|
{
|
||||||
$parsedBody = $this->helper->parsePsrRequest($request);
|
$parsedBody = $this->helper->parsePsrRequest($request);
|
||||||
|
|
||||||
return $this->executeRequest($parsedBody);
|
return $this->executeRequest($parsedBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,9 +11,6 @@ use GraphQL\Language\AST\Node;
|
|||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use function is_bool;
|
use function is_bool;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class BooleanType
|
|
||||||
*/
|
|
||||||
class BooleanType extends ScalarType
|
class BooleanType extends ScalarType
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
@ -58,11 +55,11 @@ class BooleanType extends ScalarType
|
|||||||
*/
|
*/
|
||||||
public function parseLiteral($valueNode, ?array $variables = null)
|
public function parseLiteral($valueNode, ?array $variables = null)
|
||||||
{
|
{
|
||||||
if ($valueNode instanceof BooleanValueNode) {
|
if (! $valueNode instanceof BooleanValueNode) {
|
||||||
return (bool) $valueNode->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intentionally without message, as all information already in wrapped Exception
|
// Intentionally without message, as all information already in wrapped Exception
|
||||||
throw new Exception();
|
throw new Exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $valueNode->value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,6 @@ use function call_user_func;
|
|||||||
use function is_callable;
|
use function is_callable;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class CustomScalarType
|
|
||||||
*/
|
|
||||||
class CustomScalarType extends ScalarType
|
class CustomScalarType extends ScalarType
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -12,9 +12,6 @@ use function array_keys;
|
|||||||
use function in_array;
|
use function in_array;
|
||||||
use function is_array;
|
use function is_array;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Directive
|
|
||||||
*/
|
|
||||||
class Directive
|
class Directive
|
||||||
{
|
{
|
||||||
public const DEFAULT_DEPRECATION_REASON = 'No longer supported';
|
public const DEFAULT_DEPRECATION_REASON = 'No longer supported';
|
||||||
@ -40,7 +37,7 @@ class Directive
|
|||||||
public $locations;
|
public $locations;
|
||||||
|
|
||||||
/** @var FieldArgument[] */
|
/** @var FieldArgument[] */
|
||||||
public $args;
|
public $args = [];
|
||||||
|
|
||||||
/** @var DirectiveDefinitionNode|null */
|
/** @var DirectiveDefinitionNode|null */
|
||||||
public $astNode;
|
public $astNode;
|
||||||
@ -80,6 +77,7 @@ class Directive
|
|||||||
public static function includeDirective()
|
public static function includeDirective()
|
||||||
{
|
{
|
||||||
$internal = self::getInternalDirectives();
|
$internal = self::getInternalDirectives();
|
||||||
|
|
||||||
return $internal['include'];
|
return $internal['include'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,24 +138,30 @@ class Directive
|
|||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::$internalDirectives;
|
return self::$internalDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Directive
|
* @return Directive
|
||||||
*/
|
*/
|
||||||
public static function skipDirective()
|
public static function skipDirective()
|
||||||
{
|
{
|
||||||
$internal = self::getInternalDirectives();
|
$internal = self::getInternalDirectives();
|
||||||
|
|
||||||
return $internal['skip'];
|
return $internal['skip'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Directive
|
* @return Directive
|
||||||
*/
|
*/
|
||||||
public static function deprecatedDirective()
|
public static function deprecatedDirective()
|
||||||
{
|
{
|
||||||
$internal = self::getInternalDirectives();
|
$internal = self::getInternalDirectives();
|
||||||
|
|
||||||
return $internal['deprecated'];
|
return $internal['deprecated'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
|
@ -19,9 +19,6 @@ use function is_int;
|
|||||||
use function is_string;
|
use function is_string;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class EnumType
|
|
||||||
*/
|
|
||||||
class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType
|
class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType
|
||||||
{
|
{
|
||||||
/** @var EnumTypeDefinitionNode|null */
|
/** @var EnumTypeDefinitionNode|null */
|
||||||
@ -33,7 +30,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
|
|||||||
/** @var MixedStore<mixed, EnumValueDefinition> */
|
/** @var MixedStore<mixed, EnumValueDefinition> */
|
||||||
private $valueLookup;
|
private $valueLookup;
|
||||||
|
|
||||||
/** @var \ArrayObject<string, EnumValueDefinition> */
|
/** @var ArrayObject<string, EnumValueDefinition> */
|
||||||
private $nameLookup;
|
private $nameLookup;
|
||||||
|
|
||||||
/** @var EnumTypeExtensionNode[] */
|
/** @var EnumTypeExtensionNode[] */
|
||||||
@ -71,7 +68,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \ArrayObject<string, EnumValueDefinition>
|
* @return ArrayObject<string, EnumValueDefinition>
|
||||||
*/
|
*/
|
||||||
private function getNameLookup()
|
private function getNameLookup()
|
||||||
{
|
{
|
||||||
|
@ -6,9 +6,6 @@ namespace GraphQL\Type\Definition;
|
|||||||
|
|
||||||
use GraphQL\Language\AST\EnumValueDefinitionNode;
|
use GraphQL\Language\AST\EnumValueDefinitionNode;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class EnumValueDefinition
|
|
||||||
*/
|
|
||||||
class EnumValueDefinition
|
class EnumValueDefinition
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
|
@ -11,9 +11,6 @@ use function is_array;
|
|||||||
use function is_string;
|
use function is_string;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class FieldArgument
|
|
||||||
*/
|
|
||||||
class FieldArgument
|
class FieldArgument
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
|
@ -81,7 +81,7 @@ class FieldDefinition
|
|||||||
|
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
|
||||||
$this->complexityFn = $config['complexity'] ?? static::DEFAULT_COMPLEXITY_FN;
|
$this->complexityFn = $config['complexity'] ?? self::DEFAULT_COMPLEXITY_FN;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function defineFieldMap(Type $type, $fields)
|
public static function defineFieldMap(Type $type, $fields)
|
||||||
|
@ -12,9 +12,6 @@ use GraphQL\Language\AST\Node;
|
|||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use function is_numeric;
|
use function is_numeric;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class FloatType
|
|
||||||
*/
|
|
||||||
class FloatType extends ScalarType
|
class FloatType extends ScalarType
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
|
@ -15,9 +15,6 @@ use function is_callable;
|
|||||||
use function is_string;
|
use function is_string;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class InputObjectType
|
|
||||||
*/
|
|
||||||
class InputObjectType extends Type implements InputType, NullableType, NamedType
|
class InputObjectType extends Type implements InputType, NullableType, NamedType
|
||||||
{
|
{
|
||||||
/** @var InputObjectTypeDefinitionNode|null */
|
/** @var InputObjectTypeDefinitionNode|null */
|
||||||
|
@ -14,9 +14,6 @@ use function intval;
|
|||||||
use function is_bool;
|
use function is_bool;
|
||||||
use function is_numeric;
|
use function is_numeric;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class IntType
|
|
||||||
*/
|
|
||||||
class IntType extends ScalarType
|
class IntType extends ScalarType
|
||||||
{
|
{
|
||||||
// As per the GraphQL Spec, Integers are only treated as valid when a valid
|
// As per the GraphQL Spec, Integers are only treated as valid when a valid
|
||||||
|
@ -12,9 +12,6 @@ use function is_callable;
|
|||||||
use function is_string;
|
use function is_string;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class InterfaceType
|
|
||||||
*/
|
|
||||||
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
|
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
|
||||||
{
|
{
|
||||||
/** @var InterfaceTypeDefinitionNode|null */
|
/** @var InterfaceTypeDefinitionNode|null */
|
||||||
|
@ -4,9 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace GraphQL\Type\Definition;
|
namespace GraphQL\Type\Definition;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class ListOfType
|
|
||||||
*/
|
|
||||||
class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType
|
class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType
|
||||||
{
|
{
|
||||||
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */
|
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */
|
||||||
@ -20,15 +17,9 @@ class ListOfType extends Type implements WrappingType, OutputType, NullableType,
|
|||||||
$this->ofType = Type::assertType($type);
|
$this->ofType = Type::assertType($type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function toString() : string
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function toString()
|
|
||||||
{
|
{
|
||||||
$type = $this->ofType;
|
return '[' . $this->ofType->toString() . ']';
|
||||||
$str = $type instanceof Type ? $type->toString() : (string) $type;
|
|
||||||
|
|
||||||
return '[' . $str . ']';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6,9 +6,6 @@ namespace GraphQL\Type\Definition;
|
|||||||
|
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class NonNull
|
|
||||||
*/
|
|
||||||
class NonNull extends Type implements WrappingType, OutputType, InputType
|
class NonNull extends Type implements WrappingType, OutputType, InputType
|
||||||
{
|
{
|
||||||
/** @var NullableType */
|
/** @var NullableType */
|
||||||
|
240
src/Type/Definition/QueryPlan.php
Normal file
240
src/Type/Definition/QueryPlan.php
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace GraphQL\Type\Definition;
|
||||||
|
|
||||||
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Executor\Values;
|
||||||
|
use GraphQL\Language\AST\FieldNode;
|
||||||
|
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||||
|
use GraphQL\Language\AST\InlineFragmentNode;
|
||||||
|
use GraphQL\Language\AST\SelectionSetNode;
|
||||||
|
use GraphQL\Type\Schema;
|
||||||
|
use function array_filter;
|
||||||
|
use function array_key_exists;
|
||||||
|
use function array_keys;
|
||||||
|
use function array_merge;
|
||||||
|
use function array_merge_recursive;
|
||||||
|
use function array_unique;
|
||||||
|
use function array_values;
|
||||||
|
use function count;
|
||||||
|
use function in_array;
|
||||||
|
use function is_array;
|
||||||
|
use function is_numeric;
|
||||||
|
|
||||||
|
class QueryPlan
|
||||||
|
{
|
||||||
|
/** @var string[][] */
|
||||||
|
private $types = [];
|
||||||
|
|
||||||
|
/** @var Schema */
|
||||||
|
private $schema;
|
||||||
|
|
||||||
|
/** @var mixed[] */
|
||||||
|
private $queryPlan = [];
|
||||||
|
|
||||||
|
/** @var mixed[] */
|
||||||
|
private $variableValues;
|
||||||
|
|
||||||
|
/** @var FragmentDefinitionNode[] */
|
||||||
|
private $fragments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FieldNode[] $fieldNodes
|
||||||
|
* @param mixed[] $variableValues
|
||||||
|
* @param FragmentDefinitionNode[] $fragments
|
||||||
|
*/
|
||||||
|
public function __construct(ObjectType $parentType, Schema $schema, iterable $fieldNodes, array $variableValues, array $fragments)
|
||||||
|
{
|
||||||
|
$this->schema = $schema;
|
||||||
|
$this->variableValues = $variableValues;
|
||||||
|
$this->fragments = $fragments;
|
||||||
|
$this->analyzeQueryPlan($parentType, $fieldNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[]
|
||||||
|
*/
|
||||||
|
public function queryPlan() : array
|
||||||
|
{
|
||||||
|
return $this->queryPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getReferencedTypes() : array
|
||||||
|
{
|
||||||
|
return array_keys($this->types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasType(string $type) : bool
|
||||||
|
{
|
||||||
|
return count(array_filter($this->getReferencedTypes(), static function (string $referencedType) use ($type) {
|
||||||
|
return $type === $referencedType;
|
||||||
|
})) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getReferencedFields() : array
|
||||||
|
{
|
||||||
|
return array_values(array_unique(array_merge(...array_values($this->types))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasField(string $field) : bool
|
||||||
|
{
|
||||||
|
return count(array_filter($this->getReferencedFields(), static function (string $referencedField) use ($field) {
|
||||||
|
return $field === $referencedField;
|
||||||
|
})) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function subFields(string $typename) : array
|
||||||
|
{
|
||||||
|
if (! array_key_exists($typename, $this->types)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->types[$typename];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FieldNode[] $fieldNodes
|
||||||
|
*/
|
||||||
|
private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) : void
|
||||||
|
{
|
||||||
|
$queryPlan = [];
|
||||||
|
/** @var FieldNode $fieldNode */
|
||||||
|
foreach ($fieldNodes as $fieldNode) {
|
||||||
|
if (! $fieldNode->selectionSet) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = $parentType->getField($fieldNode->name->value)->getType();
|
||||||
|
if ($type instanceof WrappingType) {
|
||||||
|
$type = $type->getWrappedType();
|
||||||
|
}
|
||||||
|
|
||||||
|
$subfields = $this->analyzeSelectionSet($fieldNode->selectionSet, $type);
|
||||||
|
|
||||||
|
$this->types[$type->name] = array_unique(array_merge(
|
||||||
|
array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [],
|
||||||
|
array_keys($subfields)
|
||||||
|
));
|
||||||
|
|
||||||
|
$queryPlan = array_merge_recursive(
|
||||||
|
$queryPlan,
|
||||||
|
$subfields
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->queryPlan = $queryPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[]
|
||||||
|
*
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
private function analyzeSelectionSet(SelectionSetNode $selectionSet, ObjectType $parentType) : array
|
||||||
|
{
|
||||||
|
$fields = [];
|
||||||
|
foreach ($selectionSet->selections as $selectionNode) {
|
||||||
|
if ($selectionNode instanceof FieldNode) {
|
||||||
|
$fieldName = $selectionNode->name->value;
|
||||||
|
$type = $parentType->getField($fieldName);
|
||||||
|
$selectionType = $type->getType();
|
||||||
|
|
||||||
|
$subfields = [];
|
||||||
|
if ($selectionNode->selectionSet) {
|
||||||
|
$subfields = $this->analyzeSubFields($selectionType, $selectionNode->selectionSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields[$fieldName] = [
|
||||||
|
'type' => $selectionType,
|
||||||
|
'fields' => $subfields ?? [],
|
||||||
|
'args' => Values::getArgumentValues($type, $selectionNode, $this->variableValues),
|
||||||
|
];
|
||||||
|
} elseif ($selectionNode instanceof FragmentSpreadNode) {
|
||||||
|
$spreadName = $selectionNode->name->value;
|
||||||
|
if (isset($this->fragments[$spreadName])) {
|
||||||
|
$fragment = $this->fragments[$spreadName];
|
||||||
|
$type = $this->schema->getType($fragment->typeCondition->name->value);
|
||||||
|
$subfields = $this->analyzeSubFields($type, $fragment->selectionSet);
|
||||||
|
|
||||||
|
$fields = $this->arrayMergeDeep(
|
||||||
|
$subfields,
|
||||||
|
$fields
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} elseif ($selectionNode instanceof InlineFragmentNode) {
|
||||||
|
$type = $this->schema->getType($selectionNode->typeCondition->name->value);
|
||||||
|
$subfields = $this->analyzeSubFields($type, $selectionNode->selectionSet);
|
||||||
|
|
||||||
|
$fields = $this->arrayMergeDeep(
|
||||||
|
$subfields,
|
||||||
|
$fields
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed[]
|
||||||
|
*/
|
||||||
|
private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet) : array
|
||||||
|
{
|
||||||
|
if ($type instanceof WrappingType) {
|
||||||
|
$type = $type->getWrappedType();
|
||||||
|
}
|
||||||
|
|
||||||
|
$subfields = [];
|
||||||
|
if ($type instanceof ObjectType) {
|
||||||
|
$subfields = $this->analyzeSelectionSet($selectionSet, $type);
|
||||||
|
$this->types[$type->name] = array_unique(array_merge(
|
||||||
|
array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [],
|
||||||
|
array_keys($subfields)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $subfields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* similar to array_merge_recursive this merges nested arrays, but handles non array values differently
|
||||||
|
* while array_merge_recursive tries to merge non array values, in this implementation they will be overwritten
|
||||||
|
*
|
||||||
|
* @see https://stackoverflow.com/a/25712428
|
||||||
|
*
|
||||||
|
* @param mixed[] $array1
|
||||||
|
* @param mixed[] $array2
|
||||||
|
*
|
||||||
|
* @return mixed[]
|
||||||
|
*/
|
||||||
|
private function arrayMergeDeep(array $array1, array $array2) : array
|
||||||
|
{
|
||||||
|
$merged = $array1;
|
||||||
|
|
||||||
|
foreach ($array2 as $key => & $value) {
|
||||||
|
if (is_numeric($key)) {
|
||||||
|
if (! in_array($value, $merged, true)) {
|
||||||
|
$merged[] = $value;
|
||||||
|
}
|
||||||
|
} elseif (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
|
||||||
|
$merged[$key] = $this->arrayMergeDeep($merged[$key], $value);
|
||||||
|
} else {
|
||||||
|
$merged[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $merged;
|
||||||
|
}
|
||||||
|
}
|
@ -63,7 +63,7 @@ class ResolveInfo
|
|||||||
* Instance of a schema used for execution
|
* Instance of a schema used for execution
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @var Schema|null
|
* @var Schema
|
||||||
*/
|
*/
|
||||||
public $schema;
|
public $schema;
|
||||||
|
|
||||||
@ -99,6 +99,9 @@ class ResolveInfo
|
|||||||
*/
|
*/
|
||||||
public $variableValues = [];
|
public $variableValues = [];
|
||||||
|
|
||||||
|
/** @var QueryPlan */
|
||||||
|
private $queryPlan;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param FieldNode[] $fieldNodes
|
* @param FieldNode[] $fieldNodes
|
||||||
* @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType
|
* @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType
|
||||||
@ -109,7 +112,7 @@ class ResolveInfo
|
|||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $fieldName,
|
string $fieldName,
|
||||||
$fieldNodes,
|
iterable $fieldNodes,
|
||||||
$returnType,
|
$returnType,
|
||||||
ObjectType $parentType,
|
ObjectType $parentType,
|
||||||
array $path,
|
array $path,
|
||||||
@ -179,6 +182,22 @@ class ResolveInfo
|
|||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function lookAhead() : QueryPlan
|
||||||
|
{
|
||||||
|
if ($this->queryPlan === null) {
|
||||||
|
$this->queryPlan = new QueryPlan(
|
||||||
|
$this->parentType,
|
||||||
|
$this->schema,
|
||||||
|
$this->fieldNodes,
|
||||||
|
$this->variableValues,
|
||||||
|
$this->fragments
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->queryPlan;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool[]
|
* @return bool[]
|
||||||
*/
|
*/
|
||||||
@ -207,6 +226,7 @@ class ResolveInfo
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,6 @@ use function is_object;
|
|||||||
use function is_scalar;
|
use function is_scalar;
|
||||||
use function method_exists;
|
use function method_exists;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class StringType
|
|
||||||
*/
|
|
||||||
class StringType extends ScalarType
|
class StringType extends ScalarType
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
|
@ -194,6 +194,7 @@ abstract class Type implements JsonSerializable
|
|||||||
public static function getInternalTypes()
|
public static function getInternalTypes()
|
||||||
{
|
{
|
||||||
trigger_error(__METHOD__ . ' is deprecated. Use Type::getStandardTypes() instead', E_USER_DEPRECATED);
|
trigger_error(__METHOD__ . ' is deprecated. Use Type::getStandardTypes() instead', E_USER_DEPRECATED);
|
||||||
|
|
||||||
return self::getStandardTypes();
|
return self::getStandardTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,9 +14,6 @@ use function is_callable;
|
|||||||
use function is_string;
|
use function is_string;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class UnionType
|
|
||||||
*/
|
|
||||||
class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
|
class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
|
||||||
{
|
{
|
||||||
/** @var UnionTypeDefinitionNode */
|
/** @var UnionTypeDefinitionNode */
|
||||||
|
@ -353,6 +353,7 @@ class AST
|
|||||||
// No valid return value.
|
// No valid return value.
|
||||||
return $undefined;
|
return $undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: we're not doing any checking that this variable is correct. We're
|
// Note: we're not doing any checking that this variable is correct. We're
|
||||||
// assuming that this query has been validated and the variable usage here
|
// assuming that this query has been validated and the variable usage here
|
||||||
// is of the correct type.
|
// is of the correct type.
|
||||||
|
@ -145,6 +145,7 @@ class ASTDefinitionBuilder
|
|||||||
// value, that would throw immediately while type system validation
|
// value, that would throw immediately while type system validation
|
||||||
// with validateSchema() will produce more actionable results.
|
// with validateSchema() will produce more actionable results.
|
||||||
$type = $this->internalBuildWrappedType($value->type);
|
$type = $this->internalBuildWrappedType($value->type);
|
||||||
|
|
||||||
$config = [
|
$config = [
|
||||||
'name' => $value->name->value,
|
'name' => $value->name->value,
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
|
@ -112,27 +112,31 @@ class BreakingChangesFinder
|
|||||||
* @return string[][]
|
* @return string[][]
|
||||||
*/
|
*/
|
||||||
public static function findTypesThatChangedKind(
|
public static function findTypesThatChangedKind(
|
||||||
Schema $oldSchema,
|
Schema $schemaA,
|
||||||
Schema $newSchema
|
Schema $schemaB
|
||||||
) {
|
) : iterable {
|
||||||
$oldTypeMap = $oldSchema->getTypeMap();
|
$schemaATypeMap = $schemaA->getTypeMap();
|
||||||
$newTypeMap = $newSchema->getTypeMap();
|
$schemaBTypeMap = $schemaB->getTypeMap();
|
||||||
|
|
||||||
$breakingChanges = [];
|
$breakingChanges = [];
|
||||||
foreach ($oldTypeMap as $typeName => $oldType) {
|
foreach ($schemaATypeMap as $typeName => $schemaAType) {
|
||||||
if (! isset($newTypeMap[$typeName])) {
|
if (! isset($schemaBTypeMap[$typeName])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$newType = $newTypeMap[$typeName];
|
$schemaBType = $schemaBTypeMap[$typeName];
|
||||||
if ($oldType instanceof $newType) {
|
if ($schemaAType instanceof $schemaBType) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$oldTypeKindName = self::typeKindName($oldType);
|
if ($schemaBType instanceof $schemaAType) {
|
||||||
$newTypeKindName = self::typeKindName($newType);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$schemaATypeKindName = self::typeKindName($schemaAType);
|
||||||
|
$schemaBTypeKindName = self::typeKindName($schemaBType);
|
||||||
$breakingChanges[] = [
|
$breakingChanges[] = [
|
||||||
'type' => self::BREAKING_CHANGE_TYPE_CHANGED_KIND,
|
'type' => self::BREAKING_CHANGE_TYPE_CHANGED_KIND,
|
||||||
'description' => "${typeName} changed from ${oldTypeKindName} to ${newTypeKindName}.",
|
'description' => "${typeName} changed from ${schemaATypeKindName} to ${schemaBTypeKindName}.",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,12 +531,12 @@ class BreakingChangesFinder
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
// Check if a non-null arg was added to the field
|
// Check if a non-null arg was added to the field
|
||||||
foreach ($newTypeFields[$fieldName]->args as $newArgDef) {
|
foreach ($newTypeFields[$fieldName]->args as $newTypeFieldArgDef) {
|
||||||
$oldArgs = $oldTypeFields[$fieldName]->args;
|
$oldArgs = $oldTypeFields[$fieldName]->args;
|
||||||
$oldArgDef = Utils::find(
|
$oldArgDef = Utils::find(
|
||||||
$oldArgs,
|
$oldArgs,
|
||||||
static function ($arg) use ($newArgDef) {
|
static function ($arg) use ($newTypeFieldArgDef) {
|
||||||
return $arg->name === $newArgDef->name;
|
return $arg->name === $newTypeFieldArgDef->name;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -541,8 +545,8 @@ class BreakingChangesFinder
|
|||||||
}
|
}
|
||||||
|
|
||||||
$newTypeName = $newType->name;
|
$newTypeName = $newType->name;
|
||||||
$newArgName = $newArgDef->name;
|
$newArgName = $newTypeFieldArgDef->name;
|
||||||
if ($newArgDef->getType() instanceof NonNull) {
|
if ($newTypeFieldArgDef->getType() instanceof NonNull) {
|
||||||
$breakingChanges[] = [
|
$breakingChanges[] = [
|
||||||
'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED,
|
'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED,
|
||||||
'description' => "A non-null arg ${newArgName} on ${newTypeName}.${fieldName} was added",
|
'description' => "A non-null arg ${newArgName} on ${newTypeName}.${fieldName} was added",
|
||||||
@ -664,7 +668,7 @@ class BreakingChangesFinder
|
|||||||
{
|
{
|
||||||
$removedArgs = [];
|
$removedArgs = [];
|
||||||
$newArgMap = self::getArgumentMapForDirective($newDirective);
|
$newArgMap = self::getArgumentMapForDirective($newDirective);
|
||||||
foreach ((array) $oldDirective->args as $arg) {
|
foreach ($oldDirective->args as $arg) {
|
||||||
if (isset($newArgMap[$arg->name])) {
|
if (isset($newArgMap[$arg->name])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -723,7 +727,7 @@ class BreakingChangesFinder
|
|||||||
{
|
{
|
||||||
$addedArgs = [];
|
$addedArgs = [];
|
||||||
$oldArgMap = self::getArgumentMapForDirective($oldDirective);
|
$oldArgMap = self::getArgumentMapForDirective($oldDirective);
|
||||||
foreach ((array) $newDirective->args as $arg) {
|
foreach ($newDirective->args as $arg) {
|
||||||
if (isset($oldArgMap[$arg->name])) {
|
if (isset($oldArgMap[$arg->name])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,6 @@ use function is_string;
|
|||||||
*
|
*
|
||||||
* Note: unfortunately when storing array as key - access and modification is O(N)
|
* Note: unfortunately when storing array as key - access and modification is O(N)
|
||||||
* (yet this should be really rare case and should be avoided when possible)
|
* (yet this should be really rare case and should be avoided when possible)
|
||||||
*
|
|
||||||
* Class MixedStore
|
|
||||||
*/
|
*/
|
||||||
class MixedStore implements ArrayAccess
|
class MixedStore implements ArrayAccess
|
||||||
{
|
{
|
||||||
|
@ -66,6 +66,7 @@ class SchemaExtender
|
|||||||
|
|
||||||
return $type->extensionASTNodes;
|
return $type->extensionASTNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return static::$typeExtensionsMap[$name] ?? null;
|
return static::$typeExtensionsMap[$name] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,6 +285,7 @@ class SchemaExtender
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $interfaces;
|
return $interfaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,11 +153,8 @@ class SchemaPrinter
|
|||||||
}
|
}
|
||||||
|
|
||||||
$subscriptionType = $schema->getSubscriptionType();
|
$subscriptionType = $schema->getSubscriptionType();
|
||||||
if ($subscriptionType && $subscriptionType->name !== 'Subscription') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return ! $subscriptionType || $subscriptionType->name === 'Subscription';
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function printDirective($directive, $options) : string
|
private static function printDirective($directive, $options) : string
|
||||||
|
@ -86,18 +86,12 @@ class TypeComparators
|
|||||||
|
|
||||||
// If superType type is an abstract type, maybeSubType type may be a currently
|
// If superType type is an abstract type, maybeSubType type may be a currently
|
||||||
// possible object type.
|
// possible object type.
|
||||||
if (Type::isAbstractType($superType) &&
|
return Type::isAbstractType($superType) &&
|
||||||
$maybeSubType instanceof ObjectType &&
|
$maybeSubType instanceof ObjectType &&
|
||||||
$schema->isPossibleType(
|
$schema->isPossibleType(
|
||||||
$superType,
|
$superType,
|
||||||
$maybeSubType
|
$maybeSubType
|
||||||
)
|
);
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, the child type is not a valid subtype of the parent type.
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,13 +125,11 @@ class TypeComparators
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var $typeB ObjectType */
|
|
||||||
// Determine if the latter type is a possible concrete type of the former.
|
// Determine if the latter type is a possible concrete type of the former.
|
||||||
return $schema->isPossibleType($typeA, $typeB);
|
return $schema->isPossibleType($typeA, $typeB);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($typeB instanceof AbstractType) {
|
if ($typeB instanceof AbstractType) {
|
||||||
/** @var $typeA ObjectType */
|
|
||||||
// Determine if the former type is a possible concrete type of the latter.
|
// Determine if the former type is a possible concrete type of the latter.
|
||||||
return $schema->isPossibleType($typeB, $typeA);
|
return $schema->isPossibleType($typeB, $typeA);
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ use GraphQL\Type\Definition\UnionType;
|
|||||||
use GraphQL\Type\Definition\WrappingType;
|
use GraphQL\Type\Definition\WrappingType;
|
||||||
use GraphQL\Type\Introspection;
|
use GraphQL\Type\Introspection;
|
||||||
use GraphQL\Type\Schema;
|
use GraphQL\Type\Schema;
|
||||||
|
use SplStack;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
use function array_merge;
|
use function array_merge;
|
||||||
use function array_pop;
|
use function array_pop;
|
||||||
@ -35,24 +36,21 @@ use function count;
|
|||||||
use function is_array;
|
use function is_array;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
|
||||||
* Class TypeInfo
|
|
||||||
*/
|
|
||||||
class TypeInfo
|
class TypeInfo
|
||||||
{
|
{
|
||||||
/** @var Schema */
|
/** @var Schema */
|
||||||
private $schema;
|
private $schema;
|
||||||
|
|
||||||
/** @var \SplStack<OutputType> */
|
/** @var SplStack<OutputType> */
|
||||||
private $typeStack;
|
private $typeStack;
|
||||||
|
|
||||||
/** @var \SplStack<CompositeType> */
|
/** @var SplStack<CompositeType> */
|
||||||
private $parentTypeStack;
|
private $parentTypeStack;
|
||||||
|
|
||||||
/** @var \SplStack<InputType> */
|
/** @var SplStack<InputType> */
|
||||||
private $inputTypeStack;
|
private $inputTypeStack;
|
||||||
|
|
||||||
/** @var \SplStack<FieldDefinition> */
|
/** @var SplStack<FieldDefinition> */
|
||||||
private $fieldDefStack;
|
private $fieldDefStack;
|
||||||
|
|
||||||
/** @var Directive */
|
/** @var Directive */
|
||||||
@ -155,6 +153,7 @@ class TypeInfo
|
|||||||
if (! $alreadyInMap) {
|
if (! $alreadyInMap) {
|
||||||
$typeMap[$i] = $type;
|
$typeMap[$i] = $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $typeMap;
|
return $typeMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +177,7 @@ class TypeInfo
|
|||||||
$nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
|
$nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
|
||||||
}
|
}
|
||||||
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
|
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
|
||||||
foreach ((array) $type->getFields() as $fieldName => $field) {
|
foreach ($type->getFields() as $fieldName => $field) {
|
||||||
if (! empty($field->args)) {
|
if (! empty($field->args)) {
|
||||||
$fieldArgTypes = array_map(
|
$fieldArgTypes = array_map(
|
||||||
static function (FieldArgument $arg) {
|
static function (FieldArgument $arg) {
|
||||||
@ -193,12 +192,12 @@ class TypeInfo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($type instanceof InputObjectType) {
|
if ($type instanceof InputObjectType) {
|
||||||
foreach ((array) $type->getFields() as $fieldName => $field) {
|
foreach ($type->getFields() as $fieldName => $field) {
|
||||||
$nestedTypes[] = $field->getType();
|
$nestedTypes[] = $field->getType();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach ($nestedTypes as $type) {
|
foreach ($nestedTypes as $nestedType) {
|
||||||
$typeMap = self::extractTypes($type, $typeMap);
|
$typeMap = self::extractTypes($nestedType, $typeMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $typeMap;
|
return $typeMap;
|
||||||
|
@ -264,8 +264,8 @@ class Utils
|
|||||||
$grouped = [];
|
$grouped = [];
|
||||||
foreach ($traversable as $key => $value) {
|
foreach ($traversable as $key => $value) {
|
||||||
$newKeys = (array) $keyFn($value, $key);
|
$newKeys = (array) $keyFn($value, $key);
|
||||||
foreach ($newKeys as $key) {
|
foreach ($newKeys as $newKey) {
|
||||||
$grouped[$key][] = $value;
|
$grouped[$newKey][] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,6 @@ class DisableIntrospection extends QuerySecurityRule
|
|||||||
|
|
||||||
protected function isEnabled()
|
protected function isEnabled()
|
||||||
{
|
{
|
||||||
return $this->isEnabled !== static::DISABLED;
|
return $this->isEnabled !== self::DISABLED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ class LoneSchemaDefinition extends ValidationRule
|
|||||||
NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount) {
|
NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount) {
|
||||||
if ($alreadyDefined !== false) {
|
if ($alreadyDefined !== false) {
|
||||||
$context->reportError(new Error('Cannot define a new schema within a schema extension.', $node));
|
$context->reportError(new Error('Cannot define a new schema within a schema extension.', $node));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,8 +12,6 @@ use GraphQL\Validator\ValidationContext;
|
|||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class NoUndefinedVariables
|
|
||||||
*
|
|
||||||
* A GraphQL operation is only valid if all variables encountered, both directly
|
* A GraphQL operation is only valid if all variables encountered, both directly
|
||||||
* and via fragment spreads, are defined by that operation.
|
* and via fragment spreads, are defined by that operation.
|
||||||
*/
|
*/
|
||||||
|
@ -206,10 +206,12 @@ class QueryComplexity extends QuerySecurityRule
|
|||||||
$directive = Directive::includeDirective();
|
$directive = Directive::includeDirective();
|
||||||
/** @var bool $directiveArgsIf */
|
/** @var bool $directiveArgsIf */
|
||||||
$directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues)['if'];
|
$directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues)['if'];
|
||||||
|
|
||||||
return ! $directiveArgsIf;
|
return ! $directiveArgsIf;
|
||||||
}
|
}
|
||||||
$directive = Directive::skipDirective();
|
$directive = Directive::skipDirective();
|
||||||
$directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues);
|
$directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues);
|
||||||
|
|
||||||
return $directiveArgsIf['if'];
|
return $directiveArgsIf['if'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,6 +284,6 @@ class QueryComplexity extends QuerySecurityRule
|
|||||||
|
|
||||||
protected function isEnabled()
|
protected function isEnabled()
|
||||||
{
|
{
|
||||||
return $this->getMaxQueryComplexity() !== static::DISABLED;
|
return $this->getMaxQueryComplexity() !== self::DISABLED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,14 +98,12 @@ class QueryDepth extends QuerySecurityRule
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
|
* Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
|
||||||
*
|
|
||||||
* @param int $maxQueryDepth
|
|
||||||
*/
|
*/
|
||||||
public function setMaxQueryDepth($maxQueryDepth)
|
public function setMaxQueryDepth(int $maxQueryDepth)
|
||||||
{
|
{
|
||||||
$this->checkIfGreaterOrEqualToZero('maxQueryDepth', $maxQueryDepth);
|
$this->checkIfGreaterOrEqualToZero('maxQueryDepth', $maxQueryDepth);
|
||||||
|
|
||||||
$this->maxQueryDepth = (int) $maxQueryDepth;
|
$this->maxQueryDepth = $maxQueryDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function maxQueryDepthErrorMessage($max, $count)
|
public static function maxQueryDepthErrorMessage($max, $count)
|
||||||
@ -115,6 +113,6 @@ class QueryDepth extends QuerySecurityRule
|
|||||||
|
|
||||||
protected function isEnabled()
|
protected function isEnabled()
|
||||||
{
|
{
|
||||||
return $this->getMaxQueryDepth() !== static::DISABLED;
|
return $this->getMaxQueryDepth() !== self::DISABLED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ class ValuesOfCorrectType extends ValidationRule
|
|||||||
public function getVisitor(ValidationContext $context)
|
public function getVisitor(ValidationContext $context)
|
||||||
{
|
{
|
||||||
$fieldName = '';
|
$fieldName = '';
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NodeKind::FIELD => [
|
NodeKind::FIELD => [
|
||||||
'enter' => static function (FieldNode $node) use (&$fieldName) {
|
'enter' => static function (FieldNode $node) use (&$fieldName) {
|
||||||
@ -281,6 +282,7 @@ class ValuesOfCorrectType extends ValidationRule
|
|||||||
return self::badArgumentValueMessage($typeName, $valueName, $fieldName, $arg->name, $message);
|
return self::badArgumentValueMessage($typeName, $valueName, $fieldName, $arg->name, $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::badValueMessage($typeName, $valueName, $message);
|
return self::badValueMessage($typeName, $valueName, $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ use GraphQL\Executor\Executor;
|
|||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Tests\Executor\TestClasses\NotSpecial;
|
use GraphQL\Tests\Executor\TestClasses\NotSpecial;
|
||||||
use GraphQL\Tests\Executor\TestClasses\Special;
|
use GraphQL\Tests\Executor\TestClasses\Special;
|
||||||
|
use GraphQL\Type\Definition\EnumType;
|
||||||
use GraphQL\Type\Definition\InputObjectType;
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
use GraphQL\Type\Definition\InterfaceType;
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
@ -284,22 +285,6 @@ class ExecutorTest extends TestCase
|
|||||||
|
|
||||||
Executor::execute($schema, $ast, $rootValue, null, ['var' => '123']);
|
Executor::execute($schema, $ast, $rootValue, null, ['var' => '123']);
|
||||||
|
|
||||||
self::assertEquals(
|
|
||||||
[
|
|
||||||
'fieldName',
|
|
||||||
'fieldNodes',
|
|
||||||
'returnType',
|
|
||||||
'parentType',
|
|
||||||
'path',
|
|
||||||
'schema',
|
|
||||||
'fragments',
|
|
||||||
'rootValue',
|
|
||||||
'operation',
|
|
||||||
'variableValues',
|
|
||||||
],
|
|
||||||
array_keys((array) $info)
|
|
||||||
);
|
|
||||||
|
|
||||||
self::assertEquals('test', $info->fieldName);
|
self::assertEquals('test', $info->fieldName);
|
||||||
self::assertEquals(1, count($info->fieldNodes));
|
self::assertEquals(1, count($info->fieldNodes));
|
||||||
self::assertSame($ast->definitions[0]->selectionSet->selections[0], $info->fieldNodes[0]);
|
self::assertSame($ast->definitions[0]->selectionSet->selections[0], $info->fieldNodes[0]);
|
||||||
@ -1122,7 +1107,18 @@ class ExecutorTest extends TestCase
|
|||||||
'a' => ['type' => Type::int()],
|
'a' => ['type' => Type::int()],
|
||||||
'b' => ['type' => Type::string()],
|
'b' => ['type' => Type::string()],
|
||||||
],
|
],
|
||||||
]), 'defaultValue' => ['a' => 1, 'b' => 'test'],
|
]),
|
||||||
|
'defaultValue' => ['a' => 1, 'b' => 'test'],
|
||||||
|
],
|
||||||
|
'i' => [
|
||||||
|
'type' => new EnumType([
|
||||||
|
'name' => 'EnumType',
|
||||||
|
'values' => [
|
||||||
|
'VALUE1' => 1,
|
||||||
|
'VALUE2' => 2,
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
'defaultValue' => 1,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -1133,7 +1129,7 @@ class ExecutorTest extends TestCase
|
|||||||
$query = Parser::parse('{ field }');
|
$query = Parser::parse('{ field }');
|
||||||
$result = Executor::execute($schema, $query);
|
$result = Executor::execute($schema, $query);
|
||||||
$expected = [
|
$expected = [
|
||||||
'data' => ['field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"}}'],
|
'data' => ['field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"},"i":1}'],
|
||||||
];
|
];
|
||||||
|
|
||||||
self::assertEquals($expected, $result->toArray());
|
self::assertEquals($expected, $result->toArray());
|
||||||
|
@ -59,6 +59,7 @@ class MutationsTest extends TestCase
|
|||||||
],
|
],
|
||||||
'name' => 'NumberHolder',
|
'name' => 'NumberHolder',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return new Schema([
|
return new Schema([
|
||||||
'query' => new ObjectType([
|
'query' => new ObjectType([
|
||||||
'fields' => [
|
'fields' => [
|
||||||
|
@ -16,7 +16,6 @@ use function uniqid;
|
|||||||
class ResolveTest extends TestCase
|
class ResolveTest extends TestCase
|
||||||
{
|
{
|
||||||
// Execute: resolve function
|
// Execute: resolve function
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see it('default function accesses properties')
|
* @see it('default function accesses properties')
|
||||||
*/
|
*/
|
||||||
|
@ -355,7 +355,6 @@ class VariablesTest extends TestCase
|
|||||||
self::assertEquals($expected, $result->toArray());
|
self::assertEquals($expected, $result->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Describe: Handles non-nullable scalars
|
// Describe: Handles non-nullable scalars
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +11,6 @@ class StarWarsIntrospectionTest extends TestCase
|
|||||||
{
|
{
|
||||||
// Star Wars Introspection Tests
|
// Star Wars Introspection Tests
|
||||||
// Basic Introspection
|
// Basic Introspection
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see it('Allows querying the schema for types')
|
* @see it('Allows querying the schema for types')
|
||||||
*/
|
*/
|
||||||
|
@ -12,7 +12,6 @@ class StarWarsValidationTest extends TestCase
|
|||||||
{
|
{
|
||||||
// Star Wars Validation Tests
|
// Star Wars Validation Tests
|
||||||
// Basic Queries
|
// Basic Queries
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see it('Validates a complex but valid query')
|
* @see it('Validates a complex but valid query')
|
||||||
*/
|
*/
|
||||||
|
588
tests/Type/QueryPlanTest.php
Normal file
588
tests/Type/QueryPlanTest.php
Normal file
@ -0,0 +1,588 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace GraphQL\Tests\Type;
|
||||||
|
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
use GraphQL\Type\Definition\QueryPlan;
|
||||||
|
use GraphQL\Type\Definition\ResolveInfo;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Schema;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class QueryPlanTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testQueryPlan() : void
|
||||||
|
{
|
||||||
|
$image = new ObjectType([
|
||||||
|
'name' => 'Image',
|
||||||
|
'fields' => [
|
||||||
|
'url' => ['type' => Type::string()],
|
||||||
|
'width' => ['type' => Type::int()],
|
||||||
|
'height' => ['type' => Type::int()],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$article = null;
|
||||||
|
|
||||||
|
$author = new ObjectType([
|
||||||
|
'name' => 'Author',
|
||||||
|
'fields' => static function () use ($image, &$article) {
|
||||||
|
return [
|
||||||
|
'id' => ['type' => Type::string()],
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'pic' => [
|
||||||
|
'type' => $image,
|
||||||
|
'args' => [
|
||||||
|
'width' => ['type' => Type::int()],
|
||||||
|
'height' => ['type' => Type::int()],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'recentArticle' => ['type' => $article],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
$reply = new ObjectType([
|
||||||
|
'name' => 'Reply',
|
||||||
|
'fields' => [
|
||||||
|
'author' => ['type' => $author],
|
||||||
|
'body' => ['type' => Type::string()],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$article = new ObjectType([
|
||||||
|
'name' => 'Article',
|
||||||
|
'fields' => [
|
||||||
|
'id' => ['type' => Type::string()],
|
||||||
|
'isPublished' => ['type' => Type::boolean()],
|
||||||
|
'author' => ['type' => $author],
|
||||||
|
'title' => ['type' => Type::string()],
|
||||||
|
'body' => ['type' => Type::string()],
|
||||||
|
'image' => ['type' => $image],
|
||||||
|
'replies' => ['type' => Type::listOf($reply)],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$doc = '
|
||||||
|
query Test {
|
||||||
|
article {
|
||||||
|
author {
|
||||||
|
name
|
||||||
|
pic(width: 100, height: 200) {
|
||||||
|
url
|
||||||
|
width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image {
|
||||||
|
width
|
||||||
|
height
|
||||||
|
...MyImage
|
||||||
|
}
|
||||||
|
replies {
|
||||||
|
body
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
pic {
|
||||||
|
url
|
||||||
|
width
|
||||||
|
... on Image {
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recentArticle {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment MyImage on Image {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
';
|
||||||
|
$expectedQueryPlan = [
|
||||||
|
'author' => [
|
||||||
|
'type' => $author,
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'name' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'pic' => [
|
||||||
|
'type' => $image,
|
||||||
|
'args' => [
|
||||||
|
'width' => 100,
|
||||||
|
'height' => 200,
|
||||||
|
],
|
||||||
|
'fields' => [
|
||||||
|
'url' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'width' => [
|
||||||
|
'type' => Type::int(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'image' => [
|
||||||
|
'type' => $image,
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'url' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'width' => [
|
||||||
|
'type' => Type::int(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'height' => [
|
||||||
|
'type' => Type::int(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'replies' => [
|
||||||
|
'type' => Type::listOf($reply),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'body' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'author' => [
|
||||||
|
'type' => $author,
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'id' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'name' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'pic' => [
|
||||||
|
'type' => $image,
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'url' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'width' => [
|
||||||
|
'type' => Type::int(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'height' => [
|
||||||
|
'type' => Type::int(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'recentArticle' => [
|
||||||
|
'type' => $article,
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'id' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'title' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'body' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$expectedReferencedTypes = [
|
||||||
|
'Image',
|
||||||
|
'Author',
|
||||||
|
'Article',
|
||||||
|
'Reply',
|
||||||
|
];
|
||||||
|
|
||||||
|
$expectedReferencedFields = [
|
||||||
|
'url',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'name',
|
||||||
|
'pic',
|
||||||
|
'id',
|
||||||
|
'recentArticle',
|
||||||
|
'title',
|
||||||
|
'body',
|
||||||
|
'author',
|
||||||
|
'image',
|
||||||
|
'replies',
|
||||||
|
];
|
||||||
|
|
||||||
|
$hasCalled = false;
|
||||||
|
/** @var QueryPlan $queryPlan */
|
||||||
|
$queryPlan = null;
|
||||||
|
|
||||||
|
$blogQuery = new ObjectType([
|
||||||
|
'name' => 'Query',
|
||||||
|
'fields' => [
|
||||||
|
'article' => [
|
||||||
|
'type' => $article,
|
||||||
|
'resolve' => static function (
|
||||||
|
$value,
|
||||||
|
$args,
|
||||||
|
$context,
|
||||||
|
ResolveInfo $info
|
||||||
|
) use (
|
||||||
|
&$hasCalled,
|
||||||
|
&$queryPlan
|
||||||
|
) {
|
||||||
|
$hasCalled = true;
|
||||||
|
$queryPlan = $info->lookAhead();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema(['query' => $blogQuery]);
|
||||||
|
$result = GraphQL::executeQuery($schema, $doc)->toArray();
|
||||||
|
|
||||||
|
self::assertTrue($hasCalled);
|
||||||
|
self::assertEquals(['data' => ['article' => null]], $result);
|
||||||
|
self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan());
|
||||||
|
self::assertEquals($expectedReferencedTypes, $queryPlan->getReferencedTypes());
|
||||||
|
self::assertEquals($expectedReferencedFields, $queryPlan->getReferencedFields());
|
||||||
|
self::assertEquals(['url', 'width', 'height'], $queryPlan->subFields('Image'));
|
||||||
|
|
||||||
|
self::assertTrue($queryPlan->hasField('url'));
|
||||||
|
self::assertFalse($queryPlan->hasField('test'));
|
||||||
|
|
||||||
|
self::assertTrue($queryPlan->hasType('Image'));
|
||||||
|
self::assertFalse($queryPlan->hasType('Test'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMergedFragmentsQueryPlan() : void
|
||||||
|
{
|
||||||
|
$image = new ObjectType([
|
||||||
|
'name' => 'Image',
|
||||||
|
'fields' => [
|
||||||
|
'url' => ['type' => Type::string()],
|
||||||
|
'width' => ['type' => Type::int()],
|
||||||
|
'height' => ['type' => Type::int()],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$article = null;
|
||||||
|
|
||||||
|
$author = new ObjectType([
|
||||||
|
'name' => 'Author',
|
||||||
|
'fields' => static function () use ($image, &$article) {
|
||||||
|
return [
|
||||||
|
'id' => ['type' => Type::string()],
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'pic' => [
|
||||||
|
'type' => $image,
|
||||||
|
'args' => [
|
||||||
|
'width' => ['type' => Type::int()],
|
||||||
|
'height' => ['type' => Type::int()],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'recentArticle' => ['type' => $article],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
$reply = new ObjectType([
|
||||||
|
'name' => 'Reply',
|
||||||
|
'fields' => [
|
||||||
|
'author' => ['type' => $author],
|
||||||
|
'body' => ['type' => Type::string()],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$article = new ObjectType([
|
||||||
|
'name' => 'Article',
|
||||||
|
'fields' => [
|
||||||
|
'id' => ['type' => Type::string()],
|
||||||
|
'isPublished' => ['type' => Type::boolean()],
|
||||||
|
'author' => ['type' => $author],
|
||||||
|
'title' => ['type' => Type::string()],
|
||||||
|
'body' => ['type' => Type::string()],
|
||||||
|
'image' => ['type' => $image],
|
||||||
|
'replies' => ['type' => Type::listOf($reply)],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$doc = '
|
||||||
|
query Test {
|
||||||
|
article {
|
||||||
|
author {
|
||||||
|
name
|
||||||
|
pic(width: 100, height: 200) {
|
||||||
|
url
|
||||||
|
width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image {
|
||||||
|
width
|
||||||
|
height
|
||||||
|
...MyImage
|
||||||
|
}
|
||||||
|
...Replies01
|
||||||
|
...Replies02
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment MyImage on Image {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment Replies01 on Article {
|
||||||
|
_replies012: replies {
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment Replies02 on Article {
|
||||||
|
_replies012: replies {
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
pic {
|
||||||
|
url
|
||||||
|
width
|
||||||
|
... on Image {
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recentArticle {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$expectedQueryPlan = [
|
||||||
|
'author' => [
|
||||||
|
'type' => $author,
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'name' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'pic' => [
|
||||||
|
'type' => $image,
|
||||||
|
'args' => [
|
||||||
|
'width' => 100,
|
||||||
|
'height' => 200,
|
||||||
|
],
|
||||||
|
'fields' => [
|
||||||
|
'url' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'width' => [
|
||||||
|
'type' => Type::int(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'image' => [
|
||||||
|
'type' => $image,
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'url' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'width' => [
|
||||||
|
'type' => Type::int(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'height' => [
|
||||||
|
'type' => Type::int(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'replies' => [
|
||||||
|
'type' => Type::listOf($reply),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'body' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'author' => [
|
||||||
|
'type' => $author,
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'id' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'name' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'pic' => [
|
||||||
|
'type' => $image,
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'url' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'width' => [
|
||||||
|
'type' => Type::int(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'height' => [
|
||||||
|
'type' => Type::int(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'recentArticle' => [
|
||||||
|
'type' => $article,
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [
|
||||||
|
'id' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'title' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
'body' => [
|
||||||
|
'type' => Type::string(),
|
||||||
|
'args' => [],
|
||||||
|
'fields' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$expectedReferencedTypes = [
|
||||||
|
'Image',
|
||||||
|
'Author',
|
||||||
|
'Reply',
|
||||||
|
'Article',
|
||||||
|
];
|
||||||
|
|
||||||
|
$expectedReferencedFields = [
|
||||||
|
'url',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'name',
|
||||||
|
'pic',
|
||||||
|
'id',
|
||||||
|
'recentArticle',
|
||||||
|
'body',
|
||||||
|
'author',
|
||||||
|
'replies',
|
||||||
|
'title',
|
||||||
|
'image',
|
||||||
|
];
|
||||||
|
|
||||||
|
$hasCalled = false;
|
||||||
|
/** @var QueryPlan $queryPlan */
|
||||||
|
$queryPlan = null;
|
||||||
|
|
||||||
|
$blogQuery = new ObjectType([
|
||||||
|
'name' => 'Query',
|
||||||
|
'fields' => [
|
||||||
|
'article' => [
|
||||||
|
'type' => $article,
|
||||||
|
'resolve' => static function (
|
||||||
|
$value,
|
||||||
|
$args,
|
||||||
|
$context,
|
||||||
|
ResolveInfo $info
|
||||||
|
) use (
|
||||||
|
&$hasCalled,
|
||||||
|
&$queryPlan
|
||||||
|
) {
|
||||||
|
$hasCalled = true;
|
||||||
|
$queryPlan = $info->lookAhead();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema(['query' => $blogQuery]);
|
||||||
|
$result = GraphQL::executeQuery($schema, $doc)->toArray();
|
||||||
|
|
||||||
|
self::assertTrue($hasCalled);
|
||||||
|
self::assertEquals(['data' => ['article' => null]], $result);
|
||||||
|
self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan());
|
||||||
|
self::assertEquals($expectedReferencedTypes, $queryPlan->getReferencedTypes());
|
||||||
|
self::assertEquals($expectedReferencedFields, $queryPlan->getReferencedFields());
|
||||||
|
self::assertEquals(['url', 'width', 'height'], $queryPlan->subFields('Image'));
|
||||||
|
|
||||||
|
self::assertTrue($queryPlan->hasField('url'));
|
||||||
|
self::assertFalse($queryPlan->hasField('test'));
|
||||||
|
|
||||||
|
self::assertTrue($queryPlan->hasType('Image'));
|
||||||
|
self::assertFalse($queryPlan->hasType('Test'));
|
||||||
|
}
|
||||||
|
}
|
@ -125,6 +125,47 @@ class BreakingChangesFinderTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to compare type of class A (old type) and type of class B (new type)
|
||||||
|
* Class B extends A but are evaluated as same types (if all properties match).
|
||||||
|
* The reason is that when constructing schema from remote schema,
|
||||||
|
* we have no certain way to get information about our classes.
|
||||||
|
* Thus object types from remote schema are constructed as Object Type
|
||||||
|
* while their local counterparts are usually a subclass of Object Type.
|
||||||
|
*
|
||||||
|
* @see https://github.com/webonyx/graphql-php/pull/431
|
||||||
|
*/
|
||||||
|
public function testShouldNotMarkTypesWithInheritedClassesAsChanged() : void
|
||||||
|
{
|
||||||
|
$objectTypeConstructedFromRemoteSchema = new ObjectType([
|
||||||
|
'name' => 'ObjectType',
|
||||||
|
'fields' => [
|
||||||
|
'field1' => ['type' => Type::string()],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$localObjectType = new class([
|
||||||
|
'name' => 'ObjectType',
|
||||||
|
'fields' => [
|
||||||
|
'field1' => ['type' => Type::string()],
|
||||||
|
],
|
||||||
|
]) extends ObjectType{
|
||||||
|
};
|
||||||
|
|
||||||
|
$schemaA = new Schema([
|
||||||
|
'query' => $this->queryType,
|
||||||
|
'types' => [$objectTypeConstructedFromRemoteSchema],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schemaB = new Schema([
|
||||||
|
'query' => $this->queryType,
|
||||||
|
'types' => [$localObjectType],
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertEmpty(BreakingChangesFinder::findTypesThatChangedKind($schemaA, $schemaB));
|
||||||
|
self::assertEmpty(BreakingChangesFinder::findTypesThatChangedKind($schemaB, $schemaA));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see it('should detect if a field on a type was deleted or changed type')
|
* @see it('should detect if a field on a type was deleted or changed type')
|
||||||
*/
|
*/
|
||||||
|
@ -24,7 +24,6 @@ use function count;
|
|||||||
class BuildSchemaTest extends TestCase
|
class BuildSchemaTest extends TestCase
|
||||||
{
|
{
|
||||||
// Describe: Schema Builder
|
// Describe: Schema Builder
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see it('can use built schema for limited execution')
|
* @see it('can use built schema for limited execution')
|
||||||
*/
|
*/
|
||||||
|
@ -198,6 +198,7 @@ class SchemaExtenderTest extends TestCase
|
|||||||
|
|
||||||
preg_match('/^[ \t]*/', $trimmedStr, $indentMatch);
|
preg_match('/^[ \t]*/', $trimmedStr, $indentMatch);
|
||||||
$indent = $indentMatch[0];
|
$indent = $indentMatch[0];
|
||||||
|
|
||||||
return preg_replace('/^' . $indent . '/m', '', $trimmedStr);
|
return preg_replace('/^' . $indent . '/m', '', $trimmedStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,6 +211,7 @@ class SchemaExtenderTest extends TestCase
|
|||||||
$ast = Parser::parse($sdl);
|
$ast = Parser::parse($sdl);
|
||||||
$extendedSchema = SchemaExtender::extend($this->testSchema, $ast, $options);
|
$extendedSchema = SchemaExtender::extend($this->testSchema, $ast, $options);
|
||||||
self::assertEquals(SchemaPrinter::doPrint($this->testSchema), $originalPrint);
|
self::assertEquals(SchemaPrinter::doPrint($this->testSchema), $originalPrint);
|
||||||
|
|
||||||
return $extendedSchema;
|
return $extendedSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1250,7 +1252,6 @@ class SchemaExtenderTest extends TestCase
|
|||||||
self::assertEquals('new directive', $newDirective->description);
|
self::assertEquals('new directive', $newDirective->description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see it('may extend directives with new complex directive')
|
* @see it('may extend directives with new complex directive')
|
||||||
*/
|
*/
|
||||||
@ -1735,7 +1736,6 @@ class SchemaExtenderTest extends TestCase
|
|||||||
self::assertEquals($queryType->name, 'Foo');
|
self::assertEquals($queryType->name, 'Foo');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see it('adds new root types via schema extension')
|
* @see it('adds new root types via schema extension')
|
||||||
*/
|
*/
|
||||||
@ -1875,7 +1875,6 @@ class SchemaExtenderTest extends TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see it('does not allow defining a root operation type twice')
|
* @see it('does not allow defining a root operation type twice')
|
||||||
*/
|
*/
|
||||||
@ -1964,7 +1963,6 @@ extend type Query {
|
|||||||
self::assertSame(['data' => ['hello' => 'Hello World!']], $result->toArray());
|
self::assertSame(['data' => ['hello' => 'Hello World!']], $result->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://github.com/webonyx/graphql-php/issues/180
|
* @see https://github.com/webonyx/graphql-php/issues/180
|
||||||
*/
|
*/
|
||||||
|
@ -21,7 +21,6 @@ use PHPUnit\Framework\TestCase;
|
|||||||
class SchemaPrinterTest extends TestCase
|
class SchemaPrinterTest extends TestCase
|
||||||
{
|
{
|
||||||
// Describe: Type System Printer
|
// Describe: Type System Printer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see it('Prints String Field')
|
* @see it('Prints String Field')
|
||||||
*/
|
*/
|
||||||
|
@ -1135,7 +1135,6 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// DESCRIBE: Valid input object value
|
// DESCRIBE: Valid input object value
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user