mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-04-03 12:43:31 +03:00
Compare commits
158 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b72ba3c93a | ||
|
1ac5af1d8b | ||
|
a01b089058 | ||
|
4401f4dd18 | ||
|
005b1a38c5 | ||
|
974258b352 | ||
|
a502c33254 | ||
|
22a0da9b98 | ||
|
8c4e7b178d | ||
|
3b33167c87 | ||
|
d037ab7ec3 | ||
|
54064b37b3 | ||
|
f7443b6f0c | ||
|
9704baf422 | ||
|
ed1d835bd5 | ||
|
3b27abafca | ||
|
c069d20ca7 | ||
|
19a37609f4 | ||
|
99453076b5 | ||
|
24f236403a | ||
|
8da3043702 | ||
|
a222cc9137 | ||
|
e704f8cc5c | ||
|
a34bb68d65 | ||
|
218e02a88c | ||
|
9ca7bb6ea1 | ||
|
65e4488ce8 | ||
|
bc66034f40 | ||
|
65a3a8d13e | ||
|
8381f67bd8 | ||
|
91b72f145d | ||
|
6e91e2181c | ||
|
03c33c9dc2 | ||
|
93ccd7351d | ||
|
ed1746e800 | ||
|
84a52c6c76 | ||
|
261f8f5ebd | ||
|
d1d4455eaa | ||
|
d5fbf1b29f | ||
|
c336d01bd2 | ||
|
752010b341 | ||
|
368a9ee2f7 | ||
|
6086792824 | ||
|
d259a303d3 | ||
|
0a71f9fba9 | ||
|
61453a4f0b | ||
|
6a5325a448 | ||
|
e87460880c | ||
|
173a4297d9 | ||
|
6f6a39468c | ||
|
a22a083220 | ||
|
acc0442152 | ||
|
21592f8f28 | ||
|
e17f578842 | ||
|
6d9275e6bc | ||
|
747cb49ae3 | ||
|
cdcf5b4473 | ||
|
692d10c127 | ||
|
6c82b85e79 | ||
|
e5528d14ab | ||
|
c9faa3489b | ||
|
bd02ccd47e | ||
|
08d9493b2c | ||
|
0b4b1485e0 | ||
|
387f416984 | ||
|
cf90a8d338 | ||
|
2fd21dd231 | ||
|
2173bb9696 | ||
|
e55c7d72cb | ||
|
ef9a24b01f | ||
|
40ac5ed269 | ||
|
27539d5af0 | ||
|
58d86abaf7 | ||
|
c803c455b4 | ||
|
9fc4d11425 | ||
|
d0ab4dc8d8 | ||
|
231919fbb2 | ||
|
22e3b0e981 | ||
|
019ed04a51 | ||
|
e5b955ecc8 | ||
|
e94db8a045 | ||
|
49b7aced87 | ||
|
0bd13d1828 | ||
|
179944495e | ||
|
0308cf0c0c | ||
|
11d32d4568 | ||
|
db915d8812 | ||
|
5821caa249 | ||
|
8435c3111e | ||
|
edec095055 | ||
|
06529e1924 | ||
|
ddebd9a414 | ||
|
1acddf4e22 | ||
|
569945cd37 | ||
|
b088720d40 | ||
|
b3bb316224 | ||
|
23ece09407 | ||
|
1864facda8 | ||
|
d15a9405cd | ||
|
d4742a76e5 | ||
|
f107cc2076 | ||
|
29eba82093 | ||
|
e6e9d9ea22 | ||
|
f24e00f4b4 | ||
|
4a39dadd0d | ||
|
616fc10837 | ||
|
2f73bbe96c | ||
|
b667d8b3a3 | ||
|
441d70c7e5 | ||
|
972532cf6c | ||
|
e467f80149 | ||
|
d97fac6ab0 | ||
|
dfefdf24cb | ||
|
20e98aefa4 | ||
|
ff16350aa1 | ||
|
d20a6a9d56 | ||
|
77448ba623 | ||
|
bc637414e5 | ||
|
7d59811c4f | ||
|
31bbc416a5 | ||
|
b1ab1820b6 | ||
|
5b3f44c7a3 | ||
|
1dc291b073 | ||
|
edb5268583 | ||
|
6544197ef8 | ||
|
5ac3eeab18 | ||
|
a80d38747f | ||
|
9c1a89710e | ||
|
fd7465521a | ||
|
828a9fb002 | ||
|
00490d289c | ||
|
2295b96a49 | ||
|
31d89acfae | ||
|
244ec66ecc | ||
|
f96bd2740d | ||
|
ead704022c | ||
|
9609d2ac84 | ||
|
d5fddfd504 | ||
|
62b0036437 | ||
|
9a0dbff26b | ||
|
e22b400373 | ||
|
f644c1a837 | ||
|
c33e41f2bf | ||
|
63b4e3f0a4 | ||
|
039577d9eb | ||
|
6610f4e2da | ||
|
19d9c523c5 | ||
|
99f93939db | ||
|
8338db0480 | ||
|
f95d1e81ea | ||
|
33e3c9c338 | ||
|
376e927505 | ||
|
ead1b864bc | ||
|
b2cea8b538 | ||
|
e344c8a441 | ||
|
90b51e1a5d | ||
|
396d3370dc | ||
|
d591eccd9f |
132 changed files with 2530 additions and 856 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -2,6 +2,7 @@
|
|||
* text eol=lf
|
||||
/benchmarks export-ignore
|
||||
/tests export-ignore
|
||||
/examples export-ignore
|
||||
/tools export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ composer.phar
|
|||
phpcs.xml
|
||||
phpstan.neon
|
||||
vendor/
|
||||
/.idea
|
||||
|
|
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -1,4 +1,21 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
- Add schema validation: Input Objects must not contain non-nullable circular references (#492)
|
||||
|
||||
#### v0.13.5
|
||||
- Fix coroutine executor when using with promise (#486)
|
||||
|
||||
#### v0.13.4
|
||||
- Force int when setting max query depth (#477)
|
||||
|
||||
#### v0.13.3
|
||||
- Reverted minor possible breaking change (#476)
|
||||
|
||||
#### v0.13.2
|
||||
- Added QueryPlan support (#436)
|
||||
- Fixed an issue with NodeList iteration over missing keys (#475)
|
||||
|
||||
#### v0.13.1
|
||||
- Better validation of field/directive arguments
|
||||
- Support for apollo client/server persisted queries
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Contributing to GraphQL PHP
|
||||
|
||||
## Workflow
|
||||
|
||||
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
|
||||
a brief proposal and discuss it with us first.
|
||||
|
@ -11,12 +10,14 @@ For smaller contributions just use this workflow:
|
|||
* Fork the project.
|
||||
* Add your features and or bug fixes.
|
||||
* Add tests. Tests are important for us.
|
||||
* Check your changes using `composer check-all`
|
||||
* Send a pull request
|
||||
* Check your changes using `composer check-all`.
|
||||
* Add an entry to the [Changelog's Unreleases section](CHANGELOG.md#unreleased).
|
||||
* Send a pull request.
|
||||
|
||||
## Setup the Development Environment
|
||||
First, copy the URL of your fork and `git clone` it to your local machine.
|
||||
|
||||
## Using GraphQL PHP from a Git checkout
|
||||
```sh
|
||||
git clone https://github.com/webonyx/graphql-php.git
|
||||
cd graphql-php
|
||||
composer install
|
||||
```
|
||||
|
@ -29,28 +30,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.
|
||||
|
||||
## 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
|
||||
./vendor/bin/phpcs
|
||||
```
|
||||
|
||||
Auto-fixing:
|
||||
Apply automatic code style fixes:
|
||||
```sh
|
||||
./vendor/bin/phpcbf
|
||||
```
|
||||
|
||||
## Static analysis
|
||||
Based on [PHPStan](https://github.com/phpstan/phpstan)
|
||||
Based on [PHPStan](https://github.com/phpstan/phpstan).
|
||||
```sh
|
||||
./vendor/bin/phpstan analyse --ansi --memory-limit 256M
|
||||
```
|
||||
|
||||
|
||||
## Running benchmarks
|
||||
|
||||
Benchmarks are run via phpbench:
|
||||
|
||||
Benchmarks are run via [PHPBench](https://github.com/phpbench/phpbench).
|
||||
```sh
|
||||
./vendor/bin/phpbench run .
|
||||
```
|
||||
|
|
32
README.md
32
README.md
|
@ -14,7 +14,7 @@ composer require webonyx/graphql-php
|
|||
```
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
README file per example.
|
||||
|
||||
## Contribute
|
||||
Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute.
|
||||
## Contributors
|
||||
|
||||
## Old README.md
|
||||
Here is a [link to the old README.md](https://github.com/webonyx/graphql-php/blob/v0.9.14/README.md).
|
||||
This project exists thanks to [all the people](https://github.com/webonyx/graphql-php/graphs/contributors) who contribute. [[Contribute](CONTRIBUTING.md)].
|
||||
|
||||
Keep in mind that it relates to the version 0.9.x. It may contain outdated information for
|
||||
newer versions (even though we try to preserve backwards compatibility).
|
||||
## Backers
|
||||
|
||||
<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 [LICENSE](LICENSE).
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
## Master
|
||||
|
||||
### Breaking (major): dropped deprecations
|
||||
- dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`.
|
||||
|
||||
## Upgrade v0.12.x > v0.13.x
|
||||
|
||||
### Breaking (major): minimum supported version of PHP
|
||||
|
|
|
@ -152,7 +152,7 @@ class SchemaGenerator
|
|||
];
|
||||
}
|
||||
|
||||
public function resolveField($value, $args, $context, $resolveInfo)
|
||||
public function resolveField($objectValue, $args, $context, $resolveInfo)
|
||||
{
|
||||
return $resolveInfo->fieldName . '-value';
|
||||
}
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
"ext-mbstring": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^5.0",
|
||||
"doctrine/coding-standard": "^6.0",
|
||||
"phpbench/phpbench": "^0.14.0",
|
||||
"phpstan/phpstan": "0.10.5",
|
||||
"phpstan/phpstan-phpunit": "0.10.0",
|
||||
"phpstan/phpstan-strict-rules": "0.10.1",
|
||||
"phpstan/phpstan": "^0.11.12",
|
||||
"phpstan/phpstan-phpunit": "^0.11.2",
|
||||
"phpstan/phpstan-strict-rules": "^0.11.1",
|
||||
"phpunit/phpcov": "^5.0",
|
||||
"phpunit/phpunit": "^7.2",
|
||||
"psr/http-message": "^1.0",
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
# Integrations
|
||||
|
||||
* [Integration with Relay](https://github.com/ivome/graphql-relay-php)
|
||||
* [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)
|
||||
* [Symfony Bundle](https://github.com/overblog/GraphQLBundle) by Overblog
|
||||
* 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)
|
||||
* [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/)).
|
||||
* [Relay Library for graphql-php](https://github.com/ivome/graphql-relay-php) – Helps construct Relay related schema definitions.
|
||||
* [Lighthouse](https://github.com/nuwave/lighthouse) – Laravel based, uses Schema Definition Language
|
||||
* [Laravel GraphQL](https://github.com/rebing/graphql-laravel) - Laravel wrapper for Facebook's GraphQL
|
||||
* [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
|
||||
|
||||
* 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))
|
||||
* [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
|
||||
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental)
|
||||
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server
|
||||
* [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.
|
||||
* [GraphQLite](https://graphqlite.thecodingmachine.io) – Define your complete schema with annotations
|
||||
* [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) – Define types with Doctrine ORM annotations
|
||||
* [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) – A PSR-15 middleware to support file uploads in GraphQL.
|
||||
* [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
|
||||
|
||||
* [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)
|
||||
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
|
||||
* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) - GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).
|
||||
|
|
|
@ -103,23 +103,25 @@ for a field you simply override this default resolver.
|
|||
**graphql-php** provides following default field resolver:
|
||||
```php
|
||||
<?php
|
||||
function defaultFieldResolver($source, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
|
||||
{
|
||||
$fieldName = $info->fieldName;
|
||||
$property = null;
|
||||
function defaultFieldResolver($objectValue, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
|
||||
{
|
||||
$fieldName = $info->fieldName;
|
||||
$property = null;
|
||||
|
||||
if (is_array($source) || $source instanceof \ArrayAccess) {
|
||||
if (isset($source[$fieldName])) {
|
||||
$property = $source[$fieldName];
|
||||
}
|
||||
} else if (is_object($source)) {
|
||||
if (isset($source->{$fieldName})) {
|
||||
$property = $source->{$fieldName};
|
||||
if (is_array($objectValue) || $objectValue instanceof \ArrayAccess) {
|
||||
if (isset($objectValue[$fieldName])) {
|
||||
$property = $objectValue[$fieldName];
|
||||
}
|
||||
} elseif (is_object($objectValue)) {
|
||||
if (isset($objectValue->{$fieldName})) {
|
||||
$property = $objectValue->{$fieldName};
|
||||
}
|
||||
}
|
||||
|
||||
return $property instanceof Closure
|
||||
? $property($objectValue, $args, $context, $info)
|
||||
: $property;
|
||||
}
|
||||
|
||||
return $property instanceof Closure ? $property($source, $args, $context, $info) : $property;
|
||||
}
|
||||
```
|
||||
|
||||
As you see it returns value by key (for arrays) or property (for objects).
|
||||
|
@ -161,7 +163,6 @@ $userType = new ObjectType([
|
|||
Keep in mind that **field resolver** has precedence over **default field resolver per type** which in turn
|
||||
has precedence over **default field resolver**.
|
||||
|
||||
|
||||
# Solving N+1 Problem
|
||||
Since: 0.9.0
|
||||
|
||||
|
|
|
@ -136,13 +136,13 @@ So for example following batch will require single DB request (if user field is
|
|||
```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 }}"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
|
@ -54,8 +54,8 @@ $queryType = new ObjectType([
|
|||
'args' => [
|
||||
'message' => Type::nonNull(Type::string()),
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
return $root['prefix'] . $args['message'];
|
||||
'resolve' => function ($rootValue, $args) {
|
||||
return $rootValue['prefix'] . $args['message'];
|
||||
}
|
||||
],
|
||||
],
|
||||
|
|
|
@ -33,7 +33,7 @@ See [related documentation](executing-queries.md).
|
|||
* fieldResolver:
|
||||
* A resolver function to use when one is not provided by the schema.
|
||||
* If not provided, the default field resolver is used (which looks for a
|
||||
* value on the source value with the field's name).
|
||||
* value on the object value with the field's name).
|
||||
* validationRules:
|
||||
* A set of rules for query validation step. Default value is all available rules.
|
||||
* Empty array would allow to skip query validation (may be convenient for persisted
|
||||
|
@ -299,7 +299,7 @@ static function getNullableType($type)
|
|||
```
|
||||
# GraphQL\Type\Definition\ResolveInfo
|
||||
Structure containing information useful for field resolution process.
|
||||
Passed as 3rd argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md).
|
||||
Passed as 4th argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md).
|
||||
|
||||
**Class Props:**
|
||||
```php
|
||||
|
|
|
@ -158,7 +158,7 @@ $heroType = new ObjectType([
|
|||
'args' => [
|
||||
'episode' => Type::nonNull($enumType)
|
||||
],
|
||||
'resolve' => function($_value, $args) {
|
||||
'resolve' => function($hero, $args) {
|
||||
return $args['episode'] === 5 ? true : false;
|
||||
}
|
||||
]
|
||||
|
|
|
@ -114,7 +114,7 @@ Option | Type | Notes
|
|||
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)
|
||||
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
|
||||
In the example above we defined our InputObjectType. Now let's use it in one of field arguments:
|
||||
|
@ -131,7 +131,7 @@ $queryType = new ObjectType([
|
|||
'type' => Type::listOf($storyType),
|
||||
'args' => [
|
||||
'filters' => [
|
||||
'type' => Type::nonNull($filters),
|
||||
'type' => $filters,
|
||||
'defaultValue' => [
|
||||
'popular' => true
|
||||
]
|
||||
|
|
|
@ -80,7 +80,7 @@ Option | Type | Notes
|
|||
name | `string` | **Required.** Name of the field. When not set - inferred from **fields** array key (read about [shorthand field definition](#shorthand-field-definitions) below)
|
||||
type | `Type` | **Required.** An instance of internal or custom type. Note: type must be represented by a single instance within one schema (see also [Type Registry](index.md#type-registry))
|
||||
args | `array` | An array of possible type arguments. Each entry is expected to be an array with keys: **name**, **type**, **description**, **defaultValue**. See [Field Arguments](#field-arguments) section below.
|
||||
resolve | `callable` | **function($value, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$value** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
|
||||
resolve | `callable` | **function($objectValue, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$objectValue** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
|
||||
complexity | `callable` | **function($childrenComplexity, $args)**<br> Used to restrict query complexity. The feature is disabled by default, read about [Security](../security.md#query-complexity-analysis) to use it.
|
||||
description | `string` | Plain-text description of this field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
|
||||
deprecationReason | `string` | Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced)
|
||||
|
@ -94,7 +94,7 @@ Option | Type | Notes
|
|||
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)
|
||||
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
|
||||
Fields can be also defined in **shorthand** notation (with only **name** and **type** options):
|
||||
|
|
|
@ -62,7 +62,7 @@ $mutationType = new ObjectType([
|
|||
'episode' => $episodeEnum,
|
||||
'review' => $reviewInputObject
|
||||
],
|
||||
'resolve' => function($val, $args) {
|
||||
'resolve' => function($rootValue, $args) {
|
||||
// TODOC
|
||||
}
|
||||
]
|
||||
|
|
|
@ -81,7 +81,7 @@ $cacheFilename = 'cached_schema.php';
|
|||
|
||||
if (!file_exists($cacheFilename)) {
|
||||
$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 {
|
||||
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ try {
|
|||
'args' => [
|
||||
'message' => ['type' => Type::string()],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
return $root['prefix'] . $args['message'];
|
||||
'resolve' => function ($rootValue, $args) {
|
||||
return $rootValue['prefix'] . $args['message'];
|
||||
}
|
||||
],
|
||||
],
|
||||
|
@ -35,7 +35,7 @@ try {
|
|||
'x' => ['type' => Type::int()],
|
||||
'y' => ['type' => Type::int()],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
'resolve' => function ($calc, $args) {
|
||||
return $args['x'] + $args['y'];
|
||||
},
|
||||
],
|
||||
|
|
|
@ -35,12 +35,12 @@ class CommentType extends ObjectType
|
|||
Types::htmlField('body')
|
||||
];
|
||||
},
|
||||
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||
'resolveField' => function($comment, $args, $context, ResolveInfo $info) {
|
||||
$method = 'resolve' . ucfirst($info->fieldName);
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}($value, $args, $context, $info);
|
||||
return $this->{$method}($comment, $args, $context, $info);
|
||||
} else {
|
||||
return $value->{$info->fieldName};
|
||||
return $comment->{$info->fieldName};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -57,8 +57,8 @@ class QueryType extends ObjectType
|
|||
],
|
||||
'hello' => Type::string()
|
||||
],
|
||||
'resolveField' => function($val, $args, $context, ResolveInfo $info) {
|
||||
return $this->{$info->fieldName}($val, $args, $context, $info);
|
||||
'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) {
|
||||
return $this->{$info->fieldName}($rootValue, $args, $context, $info);
|
||||
}
|
||||
];
|
||||
parent::__construct($config);
|
||||
|
|
|
@ -75,12 +75,12 @@ class StoryType extends ObjectType
|
|||
'interfaces' => [
|
||||
Types::node()
|
||||
],
|
||||
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||
'resolveField' => function($story, $args, $context, ResolveInfo $info) {
|
||||
$method = 'resolve' . ucfirst($info->fieldName);
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}($value, $args, $context, $info);
|
||||
return $this->{$method}($story, $args, $context, $info);
|
||||
} else {
|
||||
return $value->{$info->fieldName};
|
||||
return $story->{$info->fieldName};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -44,12 +44,12 @@ class UserType extends ObjectType
|
|||
'interfaces' => [
|
||||
Types::node()
|
||||
],
|
||||
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||
'resolveField' => function($user, $args, $context, ResolveInfo $info) {
|
||||
$method = 'resolve' . ucfirst($info->fieldName);
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}($value, $args, $context, $info);
|
||||
return $this->{$method}($user, $args, $context, $info);
|
||||
} else {
|
||||
return $value->{$info->fieldName};
|
||||
return $user->{$info->fieldName};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
|
||||
interface Resolver {
|
||||
public function resolve($root, $args, $context);
|
||||
public function resolve($rootValue, $args, $context);
|
||||
}
|
||||
|
||||
class Addition implements Resolver
|
||||
{
|
||||
public function resolve($root, $args, $context)
|
||||
public function resolve($rootValue, $args, $context)
|
||||
{
|
||||
return $args['x'] + $args['y'];
|
||||
}
|
||||
|
@ -14,22 +14,22 @@ class Addition implements Resolver
|
|||
|
||||
class Echoer implements Resolver
|
||||
{
|
||||
public function resolve($root, $args, $context)
|
||||
public function resolve($rootValue, $args, $context)
|
||||
{
|
||||
return $root['prefix'].$args['message'];
|
||||
return $rootValue['prefix'].$args['message'];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'sum' => function($root, $args, $context) {
|
||||
'sum' => function($rootValue, $args, $context) {
|
||||
$sum = new Addition();
|
||||
|
||||
return $sum->resolve($root, $args, $context);
|
||||
return $sum->resolve($rootValue, $args, $context);
|
||||
},
|
||||
'echo' => function($root, $args, $context) {
|
||||
'echo' => function($rootValue, $args, $context) {
|
||||
$echo = new Echoer();
|
||||
|
||||
return $echo->resolve($root, $args, $context);
|
||||
return $echo->resolve($rootValue, $args, $context);
|
||||
},
|
||||
'prefix' => 'You said: ',
|
||||
];
|
||||
|
|
|
@ -19,8 +19,8 @@ try {
|
|||
'args' => [
|
||||
'message' => ['type' => Type::string()],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
return $root['prefix'] . $args['message'];
|
||||
'resolve' => function ($rootValue, $args) {
|
||||
return $rootValue['prefix'] . $args['message'];
|
||||
}
|
||||
],
|
||||
],
|
||||
|
@ -35,7 +35,7 @@ try {
|
|||
'x' => ['type' => Type::int()],
|
||||
'y' => ['type' => Type::int()],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
'resolve' => function ($calc, $args) {
|
||||
return $args['x'] + $args['y'];
|
||||
},
|
||||
],
|
||||
|
|
|
@ -8,6 +8,8 @@ parameters:
|
|||
ignoreErrors:
|
||||
- "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~"
|
||||
- "~(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:
|
||||
- vendor/phpstan/phpstan-phpunit/extension.neon
|
||||
|
|
|
@ -122,13 +122,13 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
|||
|
||||
if ($previous instanceof ClientAware) {
|
||||
$this->isClientSafe = $previous->isClientSafe();
|
||||
$this->category = $previous->getCategory() ?: static::CATEGORY_INTERNAL;
|
||||
$this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL;
|
||||
} elseif ($previous) {
|
||||
$this->isClientSafe = false;
|
||||
$this->category = static::CATEGORY_INTERNAL;
|
||||
$this->category = self::CATEGORY_INTERNAL;
|
||||
} else {
|
||||
$this->isClientSafe = true;
|
||||
$this->category = static::CATEGORY_GRAPHQL;
|
||||
$this->category = self::CATEGORY_GRAPHQL;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,10 +148,10 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
|||
if ($error instanceof self) {
|
||||
if ($error->path && $error->nodes) {
|
||||
return $error;
|
||||
} else {
|
||||
$nodes = $nodes ?: $error->nodes;
|
||||
$path = $path ?: $error->path;
|
||||
}
|
||||
|
||||
$nodes = $nodes ?: $error->nodes;
|
||||
$path = $path ?: $error->path;
|
||||
}
|
||||
|
||||
$source = $positions = $originalError = null;
|
||||
|
|
|
@ -231,7 +231,7 @@ class FormattedError
|
|||
*
|
||||
* @param mixed[] $formattedError
|
||||
* @param Throwable $e
|
||||
* @param bool $debug
|
||||
* @param bool|int $debug
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
|
@ -297,7 +297,7 @@ class FormattedError
|
|||
* Prepares final error formatter taking in account $debug flags.
|
||||
* If initial formatter is not set, FormattedError::createFromException is used
|
||||
*
|
||||
* @param bool $debug
|
||||
* @param bool|int $debug
|
||||
*
|
||||
* @return callable|callable
|
||||
*/
|
||||
|
|
|
@ -7,8 +7,6 @@ namespace GraphQL\Error;
|
|||
use LogicException;
|
||||
|
||||
/**
|
||||
* Class InvariantVoilation
|
||||
*
|
||||
* Note:
|
||||
* This exception should not inherit base Error exception as it is raised when there is an error somewhere in
|
||||
* user-land code
|
||||
|
|
|
@ -7,8 +7,6 @@ namespace GraphQL\Error;
|
|||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class UserError
|
||||
*
|
||||
* Error caused by actions of GraphQL clients. Can be safely displayed to a client...
|
||||
*/
|
||||
class UserError extends RuntimeException implements ClientAware
|
||||
|
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use GraphQL\Exception\InvalidArgument;
|
||||
use function is_int;
|
||||
use function trigger_error;
|
||||
use const E_USER_WARNING;
|
||||
|
||||
|
@ -15,12 +17,12 @@ use const E_USER_WARNING;
|
|||
*/
|
||||
final class Warning
|
||||
{
|
||||
const WARNING_ASSIGN = 2;
|
||||
const WARNING_CONFIG = 4;
|
||||
const WARNING_FULL_SCHEMA_SCAN = 8;
|
||||
const WARNING_CONFIG_DEPRECATION = 16;
|
||||
const WARNING_NOT_A_TYPE = 32;
|
||||
const ALL = 63;
|
||||
public const WARNING_ASSIGN = 2;
|
||||
public const WARNING_CONFIG = 4;
|
||||
public const WARNING_FULL_SCHEMA_SCAN = 8;
|
||||
public const WARNING_CONFIG_DEPRECATION = 16;
|
||||
public const WARNING_NOT_A_TYPE = 32;
|
||||
public const ALL = 63;
|
||||
|
||||
/** @var int */
|
||||
private static $enableWarnings = self::ALL;
|
||||
|
@ -37,7 +39,7 @@ final class Warning
|
|||
*
|
||||
* @api
|
||||
*/
|
||||
public static function setWarningHandler(?callable $warningHandler = null)
|
||||
public static function setWarningHandler(?callable $warningHandler = null) : void
|
||||
{
|
||||
self::$warningHandler = $warningHandler;
|
||||
}
|
||||
|
@ -54,16 +56,16 @@ final class Warning
|
|||
*
|
||||
* @api
|
||||
*/
|
||||
public static function suppress($suppress = true)
|
||||
public static function suppress($suppress = true) : void
|
||||
{
|
||||
if ($suppress === true) {
|
||||
self::$enableWarnings = 0;
|
||||
} elseif ($suppress === false) {
|
||||
self::$enableWarnings = self::ALL;
|
||||
} else {
|
||||
$suppress = (int) $suppress;
|
||||
|
||||
} elseif (is_int($suppress)) {
|
||||
self::$enableWarnings &= ~$suppress;
|
||||
} else {
|
||||
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,22 +81,22 @@ final class Warning
|
|||
*
|
||||
* @api
|
||||
*/
|
||||
public static function enable($enable = true)
|
||||
public static function enable($enable = true) : void
|
||||
{
|
||||
if ($enable === true) {
|
||||
self::$enableWarnings = self::ALL;
|
||||
} elseif ($enable === false) {
|
||||
self::$enableWarnings = 0;
|
||||
} else {
|
||||
$enable = (int) $enable;
|
||||
|
||||
} elseif (is_int($enable)) {
|
||||
self::$enableWarnings |= $enable;
|
||||
} else {
|
||||
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable);
|
||||
}
|
||||
}
|
||||
|
||||
public static function warnOnce($errorMessage, $warningId, $messageLevel = null)
|
||||
public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
|
||||
{
|
||||
if (self::$warningHandler) {
|
||||
if (self::$warningHandler !== null) {
|
||||
$fn = self::$warningHandler;
|
||||
$fn($errorMessage, $warningId);
|
||||
} elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) {
|
||||
|
@ -103,9 +105,9 @@ final class Warning
|
|||
}
|
||||
}
|
||||
|
||||
public static function warn($errorMessage, $warningId, $messageLevel = null)
|
||||
public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
|
||||
{
|
||||
if (self::$warningHandler) {
|
||||
if (self::$warningHandler !== null) {
|
||||
$fn = self::$warningHandler;
|
||||
$fn($errorMessage, $warningId);
|
||||
} elseif ((self::$enableWarnings & $warningId) > 0) {
|
||||
|
|
20
src/Exception/InvalidArgument.php
Normal file
20
src/Exception/InvalidArgument.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Exception;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use function gettype;
|
||||
use function sprintf;
|
||||
|
||||
final class InvalidArgument extends InvalidArgumentException
|
||||
{
|
||||
/**
|
||||
* @param mixed $argument
|
||||
*/
|
||||
public static function fromExpectedTypeAndArgument(string $expectedType, $argument) : self
|
||||
{
|
||||
return new self(sprintf('Expected type "%s", got "%s"', $expectedType, gettype($argument)));
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ use GraphQL\Type\Schema;
|
|||
* Data that must be available at all points during query execution.
|
||||
*
|
||||
* Namely, schema of the type system that is currently executing,
|
||||
* and the fragments defined in the query document
|
||||
* and the fragments defined in the query document.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
|
@ -45,28 +45,28 @@ class ExecutionContext
|
|||
public $errors;
|
||||
|
||||
/** @var PromiseAdapter */
|
||||
public $promises;
|
||||
public $promiseAdapter;
|
||||
|
||||
public function __construct(
|
||||
$schema,
|
||||
$fragments,
|
||||
$root,
|
||||
$rootValue,
|
||||
$contextValue,
|
||||
$operation,
|
||||
$variables,
|
||||
$variableValues,
|
||||
$errors,
|
||||
$fieldResolver,
|
||||
$promiseAdapter
|
||||
) {
|
||||
$this->schema = $schema;
|
||||
$this->fragments = $fragments;
|
||||
$this->rootValue = $root;
|
||||
$this->rootValue = $rootValue;
|
||||
$this->contextValue = $contextValue;
|
||||
$this->operation = $operation;
|
||||
$this->variableValues = $variables;
|
||||
$this->variableValues = $variableValues;
|
||||
$this->errors = $errors ?: [];
|
||||
$this->fieldResolver = $fieldResolver;
|
||||
$this->promises = $promiseAdapter;
|
||||
$this->promiseAdapter = $promiseAdapter;
|
||||
}
|
||||
|
||||
public function addError(Error $error)
|
||||
|
|
|
@ -154,7 +154,7 @@ class ExecutionResult implements JsonSerializable
|
|||
}
|
||||
|
||||
if (! empty($this->extensions)) {
|
||||
$result['extensions'] = (array) $this->extensions;
|
||||
$result['extensions'] = $this->extensions;
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
|
|
@ -157,31 +157,33 @@ class Executor
|
|||
|
||||
/**
|
||||
* If a resolve function is not given, then a default resolve behavior is used
|
||||
* which takes the property of the source object of the same name as the field
|
||||
* which takes the property of the root value of the same name as the field
|
||||
* and returns it as the result, or if it's a function, returns the result
|
||||
* of calling that function while passing along args and context.
|
||||
*
|
||||
* @param mixed $source
|
||||
* @param mixed $objectValue
|
||||
* @param mixed[] $args
|
||||
* @param mixed|null $context
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public static function defaultFieldResolver($source, $args, $context, ResolveInfo $info)
|
||||
public static function defaultFieldResolver($objectValue, $args, $context, ResolveInfo $info)
|
||||
{
|
||||
$fieldName = $info->fieldName;
|
||||
$property = null;
|
||||
|
||||
if (is_array($source) || $source instanceof ArrayAccess) {
|
||||
if (isset($source[$fieldName])) {
|
||||
$property = $source[$fieldName];
|
||||
if (is_array($objectValue) || $objectValue instanceof ArrayAccess) {
|
||||
if (isset($objectValue[$fieldName])) {
|
||||
$property = $objectValue[$fieldName];
|
||||
}
|
||||
} elseif (is_object($source)) {
|
||||
if (isset($source->{$fieldName})) {
|
||||
$property = $source->{$fieldName};
|
||||
} elseif (is_object($objectValue)) {
|
||||
if (isset($objectValue->{$fieldName})) {
|
||||
$property = $objectValue->{$fieldName};
|
||||
}
|
||||
}
|
||||
|
||||
return $property instanceof Closure ? $property($source, $args, $context, $info) : $property;
|
||||
return $property instanceof Closure
|
||||
? $property($objectValue, $args, $context, $info)
|
||||
: $property;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,6 @@ use function is_object;
|
|||
use function method_exists;
|
||||
|
||||
/**
|
||||
* Class SyncPromise
|
||||
*
|
||||
* Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
|
||||
* (using queue to defer promises execution)
|
||||
*/
|
||||
|
@ -40,16 +38,16 @@ class SyncPromise
|
|||
*/
|
||||
private $waiting = [];
|
||||
|
||||
public static function runQueue()
|
||||
public static function runQueue() : void
|
||||
{
|
||||
$q = self::$queue;
|
||||
while ($q && ! $q->isEmpty()) {
|
||||
while ($q !== null && ! $q->isEmpty()) {
|
||||
$task = $q->dequeue();
|
||||
$task();
|
||||
}
|
||||
}
|
||||
|
||||
public function resolve($value)
|
||||
public function resolve($value) : self
|
||||
{
|
||||
switch ($this->state) {
|
||||
case self::PENDING:
|
||||
|
@ -85,7 +83,7 @@ class SyncPromise
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function reject($reason)
|
||||
public function reject($reason) : self
|
||||
{
|
||||
if (! $reason instanceof Exception && ! $reason instanceof Throwable) {
|
||||
throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
|
||||
|
@ -109,7 +107,7 @@ class SyncPromise
|
|||
return $this;
|
||||
}
|
||||
|
||||
private function enqueueWaitingPromises()
|
||||
private function enqueueWaitingPromises() : void
|
||||
{
|
||||
Utils::invariant(
|
||||
$this->state !== self::PENDING,
|
||||
|
@ -118,7 +116,7 @@ class SyncPromise
|
|||
|
||||
foreach ($this->waiting as $descriptor) {
|
||||
self::getQueue()->enqueue(function () use ($descriptor) {
|
||||
/** @var $promise self */
|
||||
/** @var self $promise */
|
||||
[$promise, $onFulfilled, $onRejected] = $descriptor;
|
||||
|
||||
if ($this->state === self::FULFILLED) {
|
||||
|
@ -147,17 +145,17 @@ class SyncPromise
|
|||
$this->waiting = [];
|
||||
}
|
||||
|
||||
public static function getQueue()
|
||||
public static function getQueue() : SplQueue
|
||||
{
|
||||
return self::$queue ?: self::$queue = new SplQueue();
|
||||
}
|
||||
|
||||
public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
|
||||
{
|
||||
if ($this->state === self::REJECTED && ! $onRejected) {
|
||||
if ($this->state === self::REJECTED && $onRejected === null) {
|
||||
return $this;
|
||||
}
|
||||
if ($this->state === self::FULFILLED && ! $onFulfilled) {
|
||||
if ($this->state === self::FULFILLED && $onFulfilled === null) {
|
||||
return $this;
|
||||
}
|
||||
$tmp = new self();
|
||||
|
|
|
@ -160,7 +160,9 @@ class SyncPromiseAdapter implements PromiseAdapter
|
|||
|
||||
if ($syncPromise->state === SyncPromise::FULFILLED) {
|
||||
return $syncPromise->result;
|
||||
} elseif ($syncPromise->state === SyncPromise::REJECTED) {
|
||||
}
|
||||
|
||||
if ($syncPromise->state === SyncPromise::REJECTED) {
|
||||
throw $syncPromise->result;
|
||||
}
|
||||
|
||||
|
|
|
@ -134,30 +134,30 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
) {
|
||||
$errors = [];
|
||||
$fragments = [];
|
||||
/** @var OperationDefinitionNode $operation */
|
||||
/** @var OperationDefinitionNode|null $operation */
|
||||
$operation = null;
|
||||
$hasMultipleAssumedOperations = false;
|
||||
foreach ($documentNode->definitions as $definition) {
|
||||
switch ($definition->kind) {
|
||||
case NodeKind::OPERATION_DEFINITION:
|
||||
if (! $operationName && $operation) {
|
||||
switch (true) {
|
||||
case $definition instanceof OperationDefinitionNode:
|
||||
if ($operationName === null && $operation !== null) {
|
||||
$hasMultipleAssumedOperations = true;
|
||||
}
|
||||
if (! $operationName ||
|
||||
if ($operationName === null ||
|
||||
(isset($definition->name) && $definition->name->value === $operationName)) {
|
||||
$operation = $definition;
|
||||
}
|
||||
break;
|
||||
case NodeKind::FRAGMENT_DEFINITION:
|
||||
case $definition instanceof FragmentDefinitionNode:
|
||||
$fragments[$definition->name->value] = $definition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $operation) {
|
||||
if ($operationName) {
|
||||
$errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName));
|
||||
} else {
|
||||
if ($operation === null) {
|
||||
if ($operationName === null) {
|
||||
$errors[] = new Error('Must provide an operation.');
|
||||
} else {
|
||||
$errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName));
|
||||
}
|
||||
} elseif ($hasMultipleAssumedOperations) {
|
||||
$errors[] = new Error(
|
||||
|
@ -165,7 +165,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
);
|
||||
}
|
||||
$variableValues = null;
|
||||
if ($operation) {
|
||||
if ($operation !== null) {
|
||||
[$coercionErrors, $coercedVariableValues] = Values::getVariableValues(
|
||||
$schema,
|
||||
$operation->variableDefinitions ?: [],
|
||||
|
@ -182,6 +182,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
}
|
||||
Utils::invariant($operation, 'Has operation if no errors.');
|
||||
Utils::invariant($variableValues !== null, 'Has variables if no errors.');
|
||||
|
||||
return new ExecutionContext(
|
||||
$schema,
|
||||
$fragments,
|
||||
|
@ -198,7 +199,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
public function doExecute() : Promise
|
||||
{
|
||||
// Return a Promise that will eventually resolve to the data described by
|
||||
// The "Response" section of the GraphQL specification.
|
||||
// the "Response" section of the GraphQL specification.
|
||||
//
|
||||
// If errors are encountered while executing a GraphQL field, only that
|
||||
// field and its descendants will be omitted, and sibling fields will still
|
||||
|
@ -206,11 +207,12 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
// resolved Promise.
|
||||
$data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue);
|
||||
$result = $this->buildResponse($data);
|
||||
|
||||
// Note: we deviate here from the reference implementation a bit by always returning promise
|
||||
// But for the "sync" case it is always fulfilled
|
||||
return $this->isPromise($result)
|
||||
? $result
|
||||
: $this->exeContext->promises->createFulfilled($result);
|
||||
: $this->exeContext->promiseAdapter->createFulfilled($result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -228,13 +230,14 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
if ($data !== null) {
|
||||
$data = (array) $data;
|
||||
}
|
||||
|
||||
return new ExecutionResult($data, $this->exeContext->errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the "Evaluating operations" section of the spec.
|
||||
*
|
||||
* @param mixed[] $rootValue
|
||||
* @param mixed $rootValue
|
||||
*
|
||||
* @return Promise|stdClass|mixed[]
|
||||
*/
|
||||
|
@ -249,21 +252,26 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
//
|
||||
// Similar to completeValueCatchingError.
|
||||
try {
|
||||
$result = $operation->operation === 'mutation' ?
|
||||
$this->executeFieldsSerially($type, $rootValue, $path, $fields) :
|
||||
$this->executeFields($type, $rootValue, $path, $fields);
|
||||
$result = $operation->operation === 'mutation'
|
||||
? $this->executeFieldsSerially($type, $rootValue, $path, $fields)
|
||||
: $this->executeFields($type, $rootValue, $path, $fields);
|
||||
if ($this->isPromise($result)) {
|
||||
return $result->then(
|
||||
null,
|
||||
function ($error) {
|
||||
$this->exeContext->addError($error);
|
||||
return $this->exeContext->promises->createFulfilled(null);
|
||||
if ($error instanceof Error) {
|
||||
$this->exeContext->addError($error);
|
||||
|
||||
return $this->exeContext->promiseAdapter->createFulfilled(null);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (Error $error) {
|
||||
$this->exeContext->addError($error);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -280,30 +288,33 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
switch ($operation->operation) {
|
||||
case 'query':
|
||||
$queryType = $schema->getQueryType();
|
||||
if (! $queryType) {
|
||||
if ($queryType === null) {
|
||||
throw new Error(
|
||||
'Schema does not define the required query root type.',
|
||||
[$operation]
|
||||
);
|
||||
}
|
||||
|
||||
return $queryType;
|
||||
case 'mutation':
|
||||
$mutationType = $schema->getMutationType();
|
||||
if (! $mutationType) {
|
||||
if ($mutationType === null) {
|
||||
throw new Error(
|
||||
'Schema is not configured for mutations.',
|
||||
[$operation]
|
||||
);
|
||||
}
|
||||
|
||||
return $mutationType;
|
||||
case 'subscription':
|
||||
$subscriptionType = $schema->getSubscriptionType();
|
||||
if (! $subscriptionType) {
|
||||
if ($subscriptionType === null) {
|
||||
throw new Error(
|
||||
'Schema is not configured for subscriptions.',
|
||||
[$operation]
|
||||
);
|
||||
}
|
||||
|
||||
return $subscriptionType;
|
||||
default:
|
||||
throw new Error(
|
||||
|
@ -334,8 +345,8 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
) {
|
||||
$exeContext = $this->exeContext;
|
||||
foreach ($selectionSet->selections as $selection) {
|
||||
switch ($selection->kind) {
|
||||
case NodeKind::FIELD:
|
||||
switch (true) {
|
||||
case $selection instanceof FieldNode:
|
||||
if (! $this->shouldIncludeNode($selection)) {
|
||||
break;
|
||||
}
|
||||
|
@ -345,7 +356,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
}
|
||||
$fields[$name][] = $selection;
|
||||
break;
|
||||
case NodeKind::INLINE_FRAGMENT:
|
||||
case $selection instanceof InlineFragmentNode:
|
||||
if (! $this->shouldIncludeNode($selection) ||
|
||||
! $this->doesFragmentConditionMatch($selection, $runtimeType)
|
||||
) {
|
||||
|
@ -358,7 +369,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
$visitedFragmentNames
|
||||
);
|
||||
break;
|
||||
case NodeKind::FRAGMENT_SPREAD:
|
||||
case $selection instanceof FragmentSpreadNode:
|
||||
$fragName = $selection->name->value;
|
||||
if (! empty($visitedFragmentNames[$fragName]) || ! $this->shouldIncludeNode($selection)) {
|
||||
break;
|
||||
|
@ -366,7 +377,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
$visitedFragmentNames[$fragName] = true;
|
||||
/** @var FragmentDefinitionNode|null $fragment */
|
||||
$fragment = $exeContext->fragments[$fragName] ?? null;
|
||||
if (! $fragment || ! $this->doesFragmentConditionMatch($fragment, $runtimeType)) {
|
||||
if ($fragment === null || ! $this->doesFragmentConditionMatch($fragment, $runtimeType)) {
|
||||
break;
|
||||
}
|
||||
$this->collectFields(
|
||||
|
@ -378,6 +389,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
@ -386,10 +398,8 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
* directives, where @skip has higher precedence than @include.
|
||||
*
|
||||
* @param FragmentSpreadNode|FieldNode|InlineFragmentNode $node
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldIncludeNode($node)
|
||||
private function shouldIncludeNode($node) : bool
|
||||
{
|
||||
$variableValues = $this->exeContext->variableValues;
|
||||
$skipDirective = Directive::skipDirective();
|
||||
|
@ -407,20 +417,16 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
$node,
|
||||
$variableValues
|
||||
);
|
||||
if (isset($include['if']) && $include['if'] === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
return ! isset($include['if']) || $include['if'] !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the logic to compute the key of a given fields entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getFieldEntryKey(FieldNode $node)
|
||||
private static function getFieldEntryKey(FieldNode $node) : string
|
||||
{
|
||||
return $node->alias ? $node->alias->value : $node->name->value;
|
||||
return $node->alias === null ? $node->name->value : $node->alias->value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -445,6 +451,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
if ($conditionalType instanceof AbstractType) {
|
||||
return $this->exeContext->schema->isPossibleType($conditionalType, $type);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -452,32 +459,34 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
* Implements the "Evaluating selection sets" section of the spec
|
||||
* for "write" mode.
|
||||
*
|
||||
* @param mixed[] $sourceValue
|
||||
* @param mixed $rootValue
|
||||
* @param mixed[] $path
|
||||
* @param ArrayObject $fields
|
||||
*
|
||||
* @return Promise|stdClass|mixed[]
|
||||
*/
|
||||
private function executeFieldsSerially(ObjectType $parentType, $sourceValue, $path, $fields)
|
||||
private function executeFieldsSerially(ObjectType $parentType, $rootValue, $path, $fields)
|
||||
{
|
||||
$result = $this->promiseReduce(
|
||||
array_keys($fields->getArrayCopy()),
|
||||
function ($results, $responseName) use ($path, $parentType, $sourceValue, $fields) {
|
||||
function ($results, $responseName) use ($path, $parentType, $rootValue, $fields) {
|
||||
$fieldNodes = $fields[$responseName];
|
||||
$fieldPath = $path;
|
||||
$fieldPath[] = $responseName;
|
||||
$result = $this->resolveField($parentType, $sourceValue, $fieldNodes, $fieldPath);
|
||||
$result = $this->resolveField($parentType, $rootValue, $fieldNodes, $fieldPath);
|
||||
if ($result === self::$UNDEFINED) {
|
||||
return $results;
|
||||
}
|
||||
$promise = $this->getPromise($result);
|
||||
if ($promise) {
|
||||
if ($promise !== null) {
|
||||
return $promise->then(static function ($resolvedResult) use ($responseName, $results) {
|
||||
$results[$responseName] = $resolvedResult;
|
||||
|
||||
return $results;
|
||||
});
|
||||
}
|
||||
$results[$responseName] = $result;
|
||||
|
||||
return $results;
|
||||
},
|
||||
[]
|
||||
|
@ -487,32 +496,38 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
return self::fixResultsIfEmptyArray($resolvedResults);
|
||||
});
|
||||
}
|
||||
|
||||
return self::fixResultsIfEmptyArray($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the field on the given source object. In particular, this
|
||||
* figures out the value that the field returns by calling its resolve function,
|
||||
* then calls completeValue to complete promises, serialize scalars, or execute
|
||||
* the sub-selection-set for objects.
|
||||
* Resolves the field on the given root value.
|
||||
*
|
||||
* @param object|null $source
|
||||
* In particular, this figures out the value that the field returns
|
||||
* by calling its resolve function, then calls completeValue to complete promises,
|
||||
* serialize scalars, or execute the sub-selection-set for objects.
|
||||
*
|
||||
* @param mixed $rootValue
|
||||
* @param FieldNode[] $fieldNodes
|
||||
* @param mixed[] $path
|
||||
*
|
||||
* @return mixed[]|Exception|mixed|null
|
||||
*/
|
||||
private function resolveField(ObjectType $parentType, $source, $fieldNodes, $path)
|
||||
private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $path)
|
||||
{
|
||||
$exeContext = $this->exeContext;
|
||||
$fieldNode = $fieldNodes[0];
|
||||
$fieldName = $fieldNode->name->value;
|
||||
$fieldDef = $this->getFieldDef($exeContext->schema, $parentType, $fieldName);
|
||||
if (! $fieldDef) {
|
||||
if ($fieldDef === null) {
|
||||
return self::$UNDEFINED;
|
||||
}
|
||||
$returnType = $fieldDef->getType();
|
||||
// The resolve function's optional third argument is a collection of
|
||||
// The resolve function's optional 3rd argument is a context value that
|
||||
// is provided to every resolve function within an execution. It is commonly
|
||||
// used to represent an authenticated user, or request-specific caches.
|
||||
$context = $exeContext->contextValue;
|
||||
// The resolve function's optional 4th argument is a collection of
|
||||
// information about the current execution state.
|
||||
$info = new ResolveInfo(
|
||||
$fieldName,
|
||||
|
@ -533,17 +548,13 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
} else {
|
||||
$resolveFn = $this->exeContext->fieldResolver;
|
||||
}
|
||||
// The resolve function's optional third argument is a context value that
|
||||
// is provided to every resolve function within an execution. It is commonly
|
||||
// used to represent an authenticated user, or request-specific caches.
|
||||
$context = $exeContext->contextValue;
|
||||
// Get the resolve function, regardless of if its result is normal
|
||||
// or abrupt (error).
|
||||
$result = $this->resolveOrError(
|
||||
$fieldDef,
|
||||
$fieldNode,
|
||||
$resolveFn,
|
||||
$source,
|
||||
$rootValue,
|
||||
$context,
|
||||
$info
|
||||
);
|
||||
|
@ -554,23 +565,21 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
$path,
|
||||
$result
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method looks up the field on the given type definition.
|
||||
*
|
||||
* It has special casing for the two introspection fields, __schema
|
||||
* and __typename. __typename is special because it can always be
|
||||
* queried as a field, even in situations where no other fields
|
||||
* are allowed, like on a Union. __schema could get automatically
|
||||
* added to the query type, but that would require mutating type
|
||||
* definitions, which would cause issues.
|
||||
*
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return FieldDefinition
|
||||
*/
|
||||
private function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName)
|
||||
private function getFieldDef(Schema $schema, ObjectType $parentType, string $fieldName) : ?FieldDefinition
|
||||
{
|
||||
static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef;
|
||||
$schemaMetaFieldDef = $schemaMetaFieldDef ?: Introspection::schemaMetaFieldDef();
|
||||
|
@ -578,39 +587,45 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
$typeNameMetaFieldDef = $typeNameMetaFieldDef ?: Introspection::typeNameMetaFieldDef();
|
||||
if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) {
|
||||
return $schemaMetaFieldDef;
|
||||
} elseif ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) {
|
||||
}
|
||||
|
||||
if ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) {
|
||||
return $typeMetaFieldDef;
|
||||
} elseif ($fieldName === $typeNameMetaFieldDef->name) {
|
||||
}
|
||||
|
||||
if ($fieldName === $typeNameMetaFieldDef->name) {
|
||||
return $typeNameMetaFieldDef;
|
||||
}
|
||||
$tmp = $parentType->getFields();
|
||||
|
||||
return $tmp[$fieldName] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField`
|
||||
* function. Returns the result of resolveFn or the abrupt-return Error object.
|
||||
* Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField` function.
|
||||
* Returns the result of resolveFn or the abrupt-return Error object.
|
||||
*
|
||||
* @param FieldDefinition $fieldDef
|
||||
* @param FieldNode $fieldNode
|
||||
* @param callable $resolveFn
|
||||
* @param mixed $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $context
|
||||
* @param ResolveInfo $info
|
||||
*
|
||||
* @return Throwable|Promise|mixed
|
||||
*/
|
||||
private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $source, $context, $info)
|
||||
private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $rootValue, $context, $info)
|
||||
{
|
||||
try {
|
||||
// Build hash of arguments from the field.arguments AST, using the
|
||||
// Build a map of arguments from the field.arguments AST, using the
|
||||
// variables scope to fulfill any variable references.
|
||||
$args = Values::getArgumentValues(
|
||||
$fieldDef,
|
||||
$fieldNode,
|
||||
$this->exeContext->variableValues
|
||||
);
|
||||
return $resolveFn($source, $args, $context, $info);
|
||||
|
||||
return $resolveFn($rootValue, $args, $context, $info);
|
||||
} catch (Exception $error) {
|
||||
return $error;
|
||||
} catch (Throwable $error) {
|
||||
|
@ -658,20 +673,23 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
$result
|
||||
);
|
||||
$promise = $this->getPromise($completed);
|
||||
if ($promise) {
|
||||
if ($promise !== null) {
|
||||
return $promise->then(
|
||||
null,
|
||||
function ($error) use ($exeContext) {
|
||||
$exeContext->addError($error);
|
||||
return $this->exeContext->promises->createFulfilled(null);
|
||||
|
||||
return $this->exeContext->promiseAdapter->createFulfilled(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return $completed;
|
||||
} catch (Error $err) {
|
||||
// If `completeValueWithLocatedError` returned abruptly (threw an error), log the error
|
||||
// and return null.
|
||||
$exeContext->addError($err);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -704,11 +722,11 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
$result
|
||||
);
|
||||
$promise = $this->getPromise($completed);
|
||||
if ($promise) {
|
||||
if ($promise !== null) {
|
||||
return $promise->then(
|
||||
null,
|
||||
function ($error) use ($fieldNodes, $path) {
|
||||
return $this->exeContext->promises->createRejected(Error::createLocatedError(
|
||||
return $this->exeContext->promiseAdapter->createRejected(Error::createLocatedError(
|
||||
$error,
|
||||
$fieldNodes,
|
||||
$path
|
||||
|
@ -716,6 +734,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
return $completed;
|
||||
} catch (Exception $error) {
|
||||
throw Error::createLocatedError($error, $fieldNodes, $path);
|
||||
|
@ -763,7 +782,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
) {
|
||||
$promise = $this->getPromise($result);
|
||||
// If result is a Promise, apply-lift over completeValue.
|
||||
if ($promise) {
|
||||
if ($promise !== null) {
|
||||
return $promise->then(function (&$resolved) use ($returnType, $fieldNodes, $info, $path) {
|
||||
return $this->completeValue($returnType, $fieldNodes, $info, $path, $resolved);
|
||||
});
|
||||
|
@ -786,6 +805,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.'
|
||||
);
|
||||
}
|
||||
|
||||
return $completed;
|
||||
}
|
||||
// If result is null-like, return null.
|
||||
|
@ -800,7 +820,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
// instance than `resolveType` or $field->getType() or $arg->getType()
|
||||
if ($returnType !== $this->exeContext->schema->getType($returnType->name)) {
|
||||
$hint = '';
|
||||
if ($this->exeContext->schema->getConfig()->typeLoader) {
|
||||
if ($this->exeContext->schema->getConfig()->typeLoader !== null) {
|
||||
$hint = sprintf(
|
||||
'Make sure that type loader returns the same instance as defined in %s.%s',
|
||||
$info->parentType,
|
||||
|
@ -838,7 +858,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
*/
|
||||
private function isPromise($value)
|
||||
{
|
||||
return $value instanceof Promise || $this->exeContext->promises->isThenable($value);
|
||||
return $value instanceof Promise || $this->exeContext->promiseAdapter->isThenable($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -854,17 +874,19 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
if ($value === null || $value instanceof Promise) {
|
||||
return $value;
|
||||
}
|
||||
if ($this->exeContext->promises->isThenable($value)) {
|
||||
$promise = $this->exeContext->promises->convertThenable($value);
|
||||
if ($this->exeContext->promiseAdapter->isThenable($value)) {
|
||||
$promise = $this->exeContext->promiseAdapter->convertThenable($value);
|
||||
if (! $promise instanceof Promise) {
|
||||
throw new InvariantViolation(sprintf(
|
||||
'%s::convertThenable is expected to return instance of GraphQL\Executor\Promise\Promise, got: %s',
|
||||
get_class($this->exeContext->promises),
|
||||
get_class($this->exeContext->promiseAdapter),
|
||||
Utils::printSafe($promise)
|
||||
));
|
||||
}
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -878,7 +900,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
* @param mixed[] $values
|
||||
* @param Promise|mixed|null $initialValue
|
||||
*
|
||||
* @return mixed[]
|
||||
* @return mixed
|
||||
*/
|
||||
private function promiseReduce(array $values, callable $callback, $initialValue)
|
||||
{
|
||||
|
@ -886,11 +908,12 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
$values,
|
||||
function ($previous, $value) use ($callback) {
|
||||
$promise = $this->getPromise($previous);
|
||||
if ($promise) {
|
||||
if ($promise !== null) {
|
||||
return $promise->then(static function ($resolved) use ($callback, $value) {
|
||||
return $callback($resolved, $value);
|
||||
});
|
||||
}
|
||||
|
||||
return $callback($previous, $value);
|
||||
},
|
||||
$initialValue
|
||||
|
@ -898,37 +921,40 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
}
|
||||
|
||||
/**
|
||||
* Complete a list value by completing each item in the list with the
|
||||
* inner type
|
||||
* Complete a list value by completing each item in the list with the inner type.
|
||||
*
|
||||
* @param FieldNode[] $fieldNodes
|
||||
* @param mixed[] $path
|
||||
* @param mixed $result
|
||||
* @param FieldNode[] $fieldNodes
|
||||
* @param mixed[] $path
|
||||
* @param mixed[]|Traversable $results
|
||||
*
|
||||
* @return mixed[]|Promise
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
|
||||
private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$results)
|
||||
{
|
||||
$itemType = $returnType->getWrappedType();
|
||||
Utils::invariant(
|
||||
is_array($result) || $result instanceof Traversable,
|
||||
is_array($results) || $results instanceof Traversable,
|
||||
'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.'
|
||||
);
|
||||
$containsPromise = false;
|
||||
$i = 0;
|
||||
$completedItems = [];
|
||||
foreach ($result as $item) {
|
||||
foreach ($results as $item) {
|
||||
$fieldPath = $path;
|
||||
$fieldPath[] = $i++;
|
||||
$info->path = $fieldPath;
|
||||
$completedItem = $this->completeValueCatchingError($itemType, $fieldNodes, $info, $fieldPath, $item);
|
||||
if (! $containsPromise && $this->getPromise($completedItem)) {
|
||||
if (! $containsPromise && $this->getPromise($completedItem) !== null) {
|
||||
$containsPromise = true;
|
||||
}
|
||||
$completedItems[] = $completedItem;
|
||||
}
|
||||
return $containsPromise ? $this->exeContext->promises->all($completedItems) : $completedItems;
|
||||
|
||||
return $containsPromise
|
||||
? $this->exeContext->promiseAdapter->all($completedItems)
|
||||
: $completedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -979,7 +1005,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
$runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType);
|
||||
}
|
||||
$promise = $this->getPromise($runtimeType);
|
||||
if ($promise) {
|
||||
if ($promise !== null) {
|
||||
return $promise->then(function ($resolvedRuntimeType) use (
|
||||
$returnType,
|
||||
$fieldNodes,
|
||||
|
@ -1001,6 +1027,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
return $this->completeObjectValue(
|
||||
$this->ensureValidRuntimeType(
|
||||
$runtimeType,
|
||||
|
@ -1040,7 +1067,8 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
) {
|
||||
return $value['__typename'];
|
||||
}
|
||||
if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader) {
|
||||
|
||||
if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader !== null) {
|
||||
Warning::warnOnce(
|
||||
sprintf(
|
||||
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
|
||||
|
@ -1062,23 +1090,25 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
continue;
|
||||
}
|
||||
$promise = $this->getPromise($isTypeOfResult);
|
||||
if ($promise) {
|
||||
if ($promise !== null) {
|
||||
$promisedIsTypeOfResults[$index] = $promise;
|
||||
} elseif ($isTypeOfResult) {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
if (! empty($promisedIsTypeOfResults)) {
|
||||
return $this->exeContext->promises->all($promisedIsTypeOfResults)
|
||||
return $this->exeContext->promiseAdapter->all($promisedIsTypeOfResults)
|
||||
->then(static function ($isTypeOfResults) use ($possibleTypes) {
|
||||
foreach ($isTypeOfResults as $index => $result) {
|
||||
if ($result) {
|
||||
return $possibleTypes[$index];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1101,7 +1131,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
$isTypeOf = $returnType->isTypeOf($result, $this->exeContext->contextValue, $info);
|
||||
if ($isTypeOf !== null) {
|
||||
$promise = $this->getPromise($isTypeOf);
|
||||
if ($promise) {
|
||||
if ($promise !== null) {
|
||||
return $promise->then(function ($isTypeOfResult) use (
|
||||
$returnType,
|
||||
$fieldNodes,
|
||||
|
@ -1111,6 +1141,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
if (! $isTypeOfResult) {
|
||||
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
|
||||
}
|
||||
|
||||
return $this->collectAndExecuteSubfields(
|
||||
$returnType,
|
||||
$fieldNodes,
|
||||
|
@ -1123,6 +1154,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->collectAndExecuteSubfields(
|
||||
$returnType,
|
||||
$fieldNodes,
|
||||
|
@ -1151,7 +1183,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
/**
|
||||
* @param FieldNode[] $fieldNodes
|
||||
* @param mixed[] $path
|
||||
* @param mixed[] $result
|
||||
* @param mixed $result
|
||||
*
|
||||
* @return mixed[]|Promise|stdClass
|
||||
*
|
||||
|
@ -1164,6 +1196,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
&$result
|
||||
) {
|
||||
$subFieldNodes = $this->collectSubFields($returnType, $fieldNodes);
|
||||
|
||||
return $this->executeFields($returnType, $result, $path, $subFieldNodes);
|
||||
}
|
||||
|
||||
|
@ -1189,6 +1222,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
}
|
||||
$this->subFieldCache[$returnType][$fieldNodes] = $subFieldNodes;
|
||||
}
|
||||
|
||||
return $this->subFieldCache[$returnType][$fieldNodes];
|
||||
}
|
||||
|
||||
|
@ -1196,24 +1230,24 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
* Implements the "Evaluating selection sets" section of the spec
|
||||
* for "read" mode.
|
||||
*
|
||||
* @param mixed|null $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed[] $path
|
||||
* @param ArrayObject $fields
|
||||
*
|
||||
* @return Promise|stdClass|mixed[]
|
||||
*/
|
||||
private function executeFields(ObjectType $parentType, $source, $path, $fields)
|
||||
private function executeFields(ObjectType $parentType, $rootValue, $path, $fields)
|
||||
{
|
||||
$containsPromise = false;
|
||||
$finalResults = [];
|
||||
foreach ($fields as $responseName => $fieldNodes) {
|
||||
$fieldPath = $path;
|
||||
$fieldPath[] = $responseName;
|
||||
$result = $this->resolveField($parentType, $source, $fieldNodes, $fieldPath);
|
||||
$result = $this->resolveField($parentType, $rootValue, $fieldNodes, $fieldPath);
|
||||
if ($result === self::$UNDEFINED) {
|
||||
continue;
|
||||
}
|
||||
if (! $containsPromise && $this->getPromise($result)) {
|
||||
if (! $containsPromise && $this->getPromise($result) !== null) {
|
||||
$containsPromise = true;
|
||||
}
|
||||
$finalResults[$responseName] = $result;
|
||||
|
@ -1222,6 +1256,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
if (! $containsPromise) {
|
||||
return self::fixResultsIfEmptyArray($finalResults);
|
||||
}
|
||||
|
||||
// Otherwise, results is a map from field name to the result
|
||||
// of resolving that field, which is possibly a promise. Return
|
||||
// a promise that will return this same map, but with any
|
||||
|
@ -1241,6 +1276,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
if ($results === []) {
|
||||
return new stdClass();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
@ -1259,19 +1295,20 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
{
|
||||
$keys = array_keys($assoc);
|
||||
$valuesAndPromises = array_values($assoc);
|
||||
$promise = $this->exeContext->promises->all($valuesAndPromises);
|
||||
$promise = $this->exeContext->promiseAdapter->all($valuesAndPromises);
|
||||
|
||||
return $promise->then(static function ($values) use ($keys) {
|
||||
$resolvedResults = [];
|
||||
foreach ($values as $i => $value) {
|
||||
$resolvedResults[$keys[$i]] = $value;
|
||||
}
|
||||
|
||||
return self::fixResultsIfEmptyArray($resolvedResults);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|ObjectType|null $runtimeTypeOrName
|
||||
* @param FieldNode[] $fieldNodes
|
||||
* @param mixed $result
|
||||
*
|
||||
* @return ObjectType
|
||||
|
@ -1282,9 +1319,9 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
ResolveInfo $info,
|
||||
&$result
|
||||
) {
|
||||
$runtimeType = is_string($runtimeTypeOrName) ?
|
||||
$this->exeContext->schema->getType($runtimeTypeOrName) :
|
||||
$runtimeTypeOrName;
|
||||
$runtimeType = is_string($runtimeTypeOrName)
|
||||
? $this->exeContext->schema->getType($runtimeTypeOrName)
|
||||
: $runtimeTypeOrName;
|
||||
if (! $runtimeType instanceof ObjectType) {
|
||||
throw new InvariantViolation(
|
||||
sprintf(
|
||||
|
@ -1318,6 +1355,7 @@ class ReferenceExecutor implements ExecutorImplementation
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $runtimeType;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ class Values
|
|||
),
|
||||
[$varDefNode]
|
||||
);
|
||||
} elseif ($varDefNode->defaultValue) {
|
||||
} elseif ($varDefNode->defaultValue !== null) {
|
||||
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
|
||||
}
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ class Values
|
|||
$argType = $argumentDefinition->getType();
|
||||
$argumentValueNode = $argumentValueMap[$name] ?? null;
|
||||
|
||||
if (! $argumentValueNode) {
|
||||
if ($argumentValueNode === null) {
|
||||
if ($argumentDefinition->defaultValueExists()) {
|
||||
$coercedValues[$name] = $argumentDefinition->defaultValue;
|
||||
} elseif ($argType instanceof NonNull) {
|
||||
|
@ -209,7 +209,7 @@ class Values
|
|||
} elseif ($argumentValueNode instanceof VariableNode) {
|
||||
$variableName = $argumentValueNode->name->value;
|
||||
|
||||
if ($variableValues && array_key_exists($variableName, $variableValues)) {
|
||||
if ($variableValues !== null && array_key_exists($variableName, $variableValues)) {
|
||||
// Note: this does not check that this variable value is correct.
|
||||
// This assumes that this query has been validated and the variable
|
||||
// usage here is of the correct type.
|
||||
|
@ -273,6 +273,7 @@ class Values
|
|||
return $error->getMessage();
|
||||
},
|
||||
$errors
|
||||
) : [];
|
||||
)
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ class Collector
|
|||
foreach ($documentNode->definitions as $definitionNode) {
|
||||
/** @var DefinitionNode|Node $definitionNode */
|
||||
|
||||
if ($definitionNode->kind === NodeKind::OPERATION_DEFINITION) {
|
||||
if ($definitionNode instanceof OperationDefinitionNode) {
|
||||
/** @var OperationDefinitionNode $definitionNode */
|
||||
if ($operationName === null && $this->operation !== null) {
|
||||
$hasMultipleAssumedOperations = true;
|
||||
|
@ -74,7 +74,7 @@ class Collector
|
|||
) {
|
||||
$this->operation = $definitionNode;
|
||||
}
|
||||
} elseif ($definitionNode->kind === NodeKind::FRAGMENT_DEFINITION) {
|
||||
} elseif ($definitionNode instanceof FragmentDefinitionNode) {
|
||||
/** @var FragmentDefinitionNode $definitionNode */
|
||||
$this->fragments[$definitionNode->name->value] = $definitionNode;
|
||||
}
|
||||
|
@ -86,11 +86,13 @@ class Collector
|
|||
} else {
|
||||
$this->runtime->addError(new Error('Must provide an operation.'));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($hasMultipleAssumedOperations) {
|
||||
$this->runtime->addError(new Error('Must provide operation name if query contains multiple operations.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -194,24 +196,26 @@ class Collector
|
|||
}
|
||||
}
|
||||
|
||||
if ($selection->kind === NodeKind::FIELD) {
|
||||
if ($selection instanceof FieldNode) {
|
||||
/** @var FieldNode $selection */
|
||||
|
||||
$resultName = $selection->alias ? $selection->alias->value : $selection->name->value;
|
||||
$resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value;
|
||||
|
||||
if (! isset($this->fields[$resultName])) {
|
||||
$this->fields[$resultName] = [];
|
||||
}
|
||||
|
||||
$this->fields[$resultName][] = $selection;
|
||||
} elseif ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
|
||||
} elseif ($selection instanceof FragmentSpreadNode) {
|
||||
/** @var FragmentSpreadNode $selection */
|
||||
|
||||
$fragmentName = $selection->name->value;
|
||||
|
||||
if (isset($this->visitedFragments[$fragmentName])) {
|
||||
continue;
|
||||
} elseif (! isset($this->fragments[$fragmentName])) {
|
||||
}
|
||||
|
||||
if (! isset($this->fragments[$fragmentName])) {
|
||||
$this->runtime->addError(new Error(
|
||||
sprintf('Fragment "%s" does not exist.', $fragmentName),
|
||||
$selection
|
||||
|
@ -245,7 +249,7 @@ class Collector
|
|||
}
|
||||
|
||||
$this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet);
|
||||
} elseif ($selection->kind === NodeKind::INLINE_FRAGMENT) {
|
||||
} elseif ($selection instanceof InlineFragmentNode) {
|
||||
/** @var InlineFragmentNode $selection */
|
||||
|
||||
if ($selection->typeCondition !== null) {
|
||||
|
|
|
@ -147,6 +147,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
if ($emptyObjectAsStdClass && empty($array)) {
|
||||
return new stdClass();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
@ -155,6 +156,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
foreach ($value as $key => $item) {
|
||||
$array[$key] = self::resultToArray($item);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
@ -291,13 +293,17 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
$strand->stack[$strand->depth++] = $strand->current;
|
||||
$strand->current = $value;
|
||||
goto START;
|
||||
} elseif ($this->promiseAdapter->isThenable($value)) {
|
||||
} elseif ($this->isPromise($value)) {
|
||||
// !!! increment pending before calling ->then() as it may invoke the callback right away
|
||||
++$this->pending;
|
||||
|
||||
if (! $value instanceof Promise) {
|
||||
$value = $this->promiseAdapter->convertThenable($value);
|
||||
}
|
||||
|
||||
$this->promiseAdapter
|
||||
->convertThenable($value)
|
||||
->then(
|
||||
$value,
|
||||
function ($value) use ($strand) {
|
||||
$strand->success = true;
|
||||
$strand->value = $value;
|
||||
|
@ -365,6 +371,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
// short-circuit evaluation for __typename
|
||||
if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
|
||||
$ctx->result->{$ctx->shared->resultName} = $ctx->type->name;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -475,7 +482,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
private function completeValueFast(CoroutineContext $ctx, Type $type, $value, array $path, &$returnValue) : bool
|
||||
{
|
||||
// special handling of Throwable inherited from JS reference implementation, but makes no sense in this PHP
|
||||
if ($this->promiseAdapter->isThenable($value) || $value instanceof Throwable) {
|
||||
if ($this->isPromise($value) || $value instanceof Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -491,7 +498,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
|
||||
if ($type !== $this->schema->getType($type->name)) {
|
||||
$hint = '';
|
||||
if ($this->schema->getConfig()->typeLoader) {
|
||||
if ($this->schema->getConfig()->typeLoader !== null) {
|
||||
$hint = sprintf(
|
||||
'Make sure that type loader returns the same instance as defined in %s.%s',
|
||||
$ctx->type,
|
||||
|
@ -571,7 +578,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
|
||||
// !!! $value might be promise, yield to resolve
|
||||
try {
|
||||
if ($this->promiseAdapter->isThenable($value)) {
|
||||
if ($this->isPromise($value)) {
|
||||
$value = yield $value;
|
||||
}
|
||||
} catch (Throwable $reason) {
|
||||
|
@ -613,8 +620,9 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
foreach ($value as $itemValue) {
|
||||
++$index;
|
||||
|
||||
$itemPath = $path;
|
||||
$itemPath[] = $index; // !!! use arrays COW semantics
|
||||
$itemPath = $path;
|
||||
$itemPath[] = $index; // !!! use arrays COW semantics
|
||||
$ctx->resolveInfo->path = $itemPath;
|
||||
|
||||
try {
|
||||
if (! $this->completeValueFast($ctx, $itemType, $itemValue, $itemPath, $itemReturnValue)) {
|
||||
|
@ -639,7 +647,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
} else {
|
||||
if ($type !== $this->schema->getType($type->name)) {
|
||||
$hint = '';
|
||||
if ($this->schema->getConfig()->typeLoader) {
|
||||
if ($this->schema->getConfig()->typeLoader !== null) {
|
||||
$hint = sprintf(
|
||||
'Make sure that type loader returns the same instance as defined in %s.%s',
|
||||
$ctx->type,
|
||||
|
@ -813,7 +821,10 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
} else {
|
||||
$childContexts = [];
|
||||
|
||||
foreach ($this->collector->collectFields($objectType, $ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)) as $childShared) {
|
||||
foreach ($this->collector->collectFields(
|
||||
$objectType,
|
||||
$ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)
|
||||
) as $childShared) {
|
||||
/** @var CoroutineContextShared $childShared */
|
||||
|
||||
$childPath = $path;
|
||||
|
@ -897,7 +908,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
return $this->schema->getType($value['__typename']);
|
||||
}
|
||||
|
||||
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader) {
|
||||
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader !== null) {
|
||||
Warning::warnOnce(
|
||||
sprintf(
|
||||
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
|
||||
|
@ -928,4 +939,14 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
|
|||
|
||||
return $selectedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isPromise($value)
|
||||
{
|
||||
return $value instanceof Promise || $this->promiseAdapter->isThenable($value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,6 @@ class ListTypeNode extends Node implements TypeNode
|
|||
/** @var string */
|
||||
public $kind = NodeKind::LIST_TYPE;
|
||||
|
||||
/** @var Node */
|
||||
/** @var TypeNode */
|
||||
public $type;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ class Location
|
|||
$tmp = new static();
|
||||
$tmp->start = $start;
|
||||
$tmp->end = $end;
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
|
@ -68,7 +69,7 @@ class Location
|
|||
$this->endToken = $endToken;
|
||||
$this->source = $source;
|
||||
|
||||
if (! $startToken || ! $endToken) {
|
||||
if ($startToken === null || $endToken === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ abstract class Node
|
|||
|
||||
$tmp = (array) $this;
|
||||
|
||||
if ($this->loc) {
|
||||
if ($this->loc !== null) {
|
||||
$tmp['loc'] = [
|
||||
'start' => $this->loc->start,
|
||||
'end' => $this->loc->end,
|
||||
|
@ -125,7 +125,7 @@ abstract class Node
|
|||
'kind' => $node->kind,
|
||||
];
|
||||
|
||||
if ($node->loc) {
|
||||
if ($node->loc !== null) {
|
||||
$result['loc'] = [
|
||||
'start' => $node->loc->start,
|
||||
'end' => $node->loc->end,
|
||||
|
|
|
@ -105,6 +105,7 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
|
|||
if ($list instanceof self) {
|
||||
$list = $list->nodes;
|
||||
}
|
||||
|
||||
return new NodeList(array_merge($this->nodes, $list));
|
||||
}
|
||||
|
||||
|
@ -113,9 +114,8 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
|
|||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
$count = count($this->nodes);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
yield $this->offsetGet($i);
|
||||
foreach ($this->nodes as $key => $_) {
|
||||
yield $this->offsetGet($key);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,6 @@ class NonNullTypeNode extends Node implements TypeNode
|
|||
/** @var string */
|
||||
public $kind = NodeKind::NON_NULL_TYPE;
|
||||
|
||||
/** @var NameNode | ListTypeNode */
|
||||
/** @var NamedTypeNode | ListTypeNode */
|
||||
public $type;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,22 @@ use function preg_match;
|
|||
*/
|
||||
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 */
|
||||
public $source;
|
||||
|
||||
|
@ -92,7 +108,8 @@ class Lexer
|
|||
*/
|
||||
public function advance()
|
||||
{
|
||||
$this->lastToken = $this->token;
|
||||
$this->lastToken = $this->token;
|
||||
|
||||
return $this->token = $this->lookahead();
|
||||
}
|
||||
|
||||
|
@ -131,44 +148,45 @@ class Lexer
|
|||
[, $code, $bytes] = $this->readChar(true);
|
||||
|
||||
switch ($code) {
|
||||
case 33: // !
|
||||
case self::TOKEN_BANG:
|
||||
return new Token(Token::BANG, $position, $position + 1, $line, $col, $prev);
|
||||
case 35: // #
|
||||
case self::TOKEN_HASH: // #
|
||||
$this->moveStringCursor(-1, -1 * $bytes);
|
||||
|
||||
return $this->readComment($line, $col, $prev);
|
||||
case 36: // $
|
||||
case self::TOKEN_DOLLAR:
|
||||
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);
|
||||
case 40: // (
|
||||
case self::TOKEN_PAREN_L:
|
||||
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);
|
||||
case 46: // .
|
||||
case self::TOKEN_DOT: // .
|
||||
[, $charCode1] = $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);
|
||||
}
|
||||
break;
|
||||
case 58: // :
|
||||
case self::TOKEN_COLON:
|
||||
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);
|
||||
case 64: // @
|
||||
case self::TOKEN_AT:
|
||||
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);
|
||||
case 93: // ]
|
||||
case self::TOKEN_BRACKET_R:
|
||||
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);
|
||||
case 124: // |
|
||||
case self::TOKEN_PIPE:
|
||||
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);
|
||||
|
||||
// A-Z
|
||||
case 65:
|
||||
case 66:
|
||||
|
@ -227,6 +245,7 @@ class Lexer
|
|||
case 122:
|
||||
return $this->moveStringCursor(-1, -1 * $bytes)
|
||||
->readName($line, $col, $prev);
|
||||
|
||||
// -
|
||||
case 45:
|
||||
// 0-9
|
||||
|
@ -242,6 +261,7 @@ class Lexer
|
|||
case 57:
|
||||
return $this->moveStringCursor(-1, -1 * $bytes)
|
||||
->readNumber($line, $col, $prev);
|
||||
|
||||
// "
|
||||
case 34:
|
||||
[, $nextCode] = $this->readChar();
|
||||
|
@ -294,11 +314,11 @@ class Lexer
|
|||
$start = $this->position;
|
||||
[$char, $code] = $this->readChar();
|
||||
|
||||
while ($code && (
|
||||
while ($code !== null && (
|
||||
$code === 95 || // _
|
||||
$code >= 48 && $code <= 57 || // 0-9
|
||||
$code >= 65 && $code <= 90 || // A-Z
|
||||
$code >= 97 && $code <= 122 // a-z
|
||||
($code >= 48 && $code <= 57) || // 0-9
|
||||
($code >= 65 && $code <= 90) || // A-Z
|
||||
($code >= 97 && $code <= 122) // a-z
|
||||
)) {
|
||||
$value .= $char;
|
||||
[$char, $code] = $this->moveStringCursor(1, 1)->readChar();
|
||||
|
@ -564,10 +584,10 @@ class Lexer
|
|||
$prev,
|
||||
BlockString::value($value)
|
||||
);
|
||||
} else {
|
||||
// move cursor back to before the first quote
|
||||
$this->moveStringCursor(-2, -2);
|
||||
}
|
||||
|
||||
// move cursor back to before the first quote
|
||||
$this->moveStringCursor(-2, -2);
|
||||
}
|
||||
|
||||
$this->assertValidBlockStringCharacterCode($code, $this->position);
|
||||
|
@ -675,7 +695,7 @@ class Lexer
|
|||
do {
|
||||
[$char, $code, $bytes] = $this->moveStringCursor(1, $bytes)->readChar();
|
||||
$value .= $char;
|
||||
} while ($code &&
|
||||
} while ($code !== null &&
|
||||
// SourceCharacter but not LineTerminator
|
||||
($code > 0x001F || $code === 0x0009)
|
||||
);
|
||||
|
|
|
@ -439,7 +439,6 @@ class Parser
|
|||
case 'mutation':
|
||||
case 'subscription':
|
||||
return $this->parseOperationDefinition();
|
||||
|
||||
case 'fragment':
|
||||
return $this->parseFragmentDefinition();
|
||||
}
|
||||
|
@ -513,15 +512,15 @@ class Parser
|
|||
*/
|
||||
private function parseVariableDefinitions()
|
||||
{
|
||||
return $this->peek(Token::PAREN_L) ?
|
||||
$this->many(
|
||||
return $this->peek(Token::PAREN_L)
|
||||
? $this->many(
|
||||
Token::PAREN_L,
|
||||
function () {
|
||||
return $this->parseVariableDefinition();
|
||||
},
|
||||
Token::PAREN_R
|
||||
) :
|
||||
new NodeList([]);
|
||||
)
|
||||
: new NodeList([]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -593,9 +592,9 @@ class Parser
|
|||
*/
|
||||
private function parseSelection()
|
||||
{
|
||||
return $this->peek(Token::SPREAD) ?
|
||||
$this->parseFragment() :
|
||||
$this->parseField();
|
||||
return $this->peek(Token::SPREAD)
|
||||
? $this->parseFragment()
|
||||
: $this->parseField();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -635,17 +634,17 @@ class Parser
|
|||
*/
|
||||
private function parseArguments($isConst)
|
||||
{
|
||||
$parseFn = $isConst ?
|
||||
function () {
|
||||
$parseFn = $isConst
|
||||
? function () {
|
||||
return $this->parseConstArgument();
|
||||
} :
|
||||
function () {
|
||||
}
|
||||
: function () {
|
||||
return $this->parseArgument();
|
||||
};
|
||||
|
||||
return $this->peek(Token::PAREN_L) ?
|
||||
$this->many(Token::PAREN_L, $parseFn, Token::PAREN_R) :
|
||||
new NodeList([]);
|
||||
return $this->peek(Token::PAREN_L)
|
||||
? $this->many(Token::PAREN_L, $parseFn, Token::PAREN_R)
|
||||
: new NodeList([]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -827,7 +826,9 @@ class Parser
|
|||
'value' => $token->value === 'true',
|
||||
'loc' => $this->loc($token),
|
||||
]);
|
||||
} elseif ($token->value === 'null') {
|
||||
}
|
||||
|
||||
if ($token->value === 'null') {
|
||||
$this->lexer->advance();
|
||||
|
||||
return new NullValueNode([
|
||||
|
@ -1207,8 +1208,8 @@ class Parser
|
|||
do {
|
||||
$types[] = $this->parseNamedType();
|
||||
} while ($this->skip(Token::AMP) ||
|
||||
// Legacy support for the SDL?
|
||||
(! empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME))
|
||||
// Legacy support for the SDL?
|
||||
(! empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1544,7 +1545,8 @@ class Parser
|
|||
Token::BRACE_L,
|
||||
[$this, 'parseOperationTypeDefinition'],
|
||||
Token::BRACE_R
|
||||
) : [];
|
||||
)
|
||||
: [];
|
||||
if (count($directives) === 0 && count($operationTypes) === 0) {
|
||||
$this->unexpected();
|
||||
}
|
||||
|
@ -1654,9 +1656,7 @@ class Parser
|
|||
$name = $this->parseName();
|
||||
$directives = $this->parseDirectives(true);
|
||||
$types = $this->parseUnionMemberTypes();
|
||||
if (count($directives) === 0 &&
|
||||
! $types
|
||||
) {
|
||||
if (count($directives) === 0 && count($types) === 0) {
|
||||
throw $this->unexpected();
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ use GraphQL\Language\AST\IntValueNode;
|
|||
use GraphQL\Language\AST\ListTypeNode;
|
||||
use GraphQL\Language\AST\ListValueNode;
|
||||
use GraphQL\Language\AST\NamedTypeNode;
|
||||
use GraphQL\Language\AST\NameNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\NonNullTypeNode;
|
||||
|
@ -47,6 +48,7 @@ use GraphQL\Language\AST\StringValueNode;
|
|||
use GraphQL\Language\AST\UnionTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\UnionTypeExtensionNode;
|
||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||
use GraphQL\Language\AST\VariableNode;
|
||||
use GraphQL\Utils\Utils;
|
||||
use function count;
|
||||
use function implode;
|
||||
|
@ -97,11 +99,11 @@ class Printer
|
|||
$ast,
|
||||
[
|
||||
'leave' => [
|
||||
NodeKind::NAME => static function (Node $node) {
|
||||
NodeKind::NAME => static function (NameNode $node) {
|
||||
return '' . $node->value;
|
||||
},
|
||||
|
||||
NodeKind::VARIABLE => static function ($node) {
|
||||
NodeKind::VARIABLE => static function (VariableNode $node) {
|
||||
return '$' . $node->name;
|
||||
},
|
||||
|
||||
|
@ -115,6 +117,7 @@ class Printer
|
|||
$varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
|
||||
$directives = $this->join($node->directives, ' ');
|
||||
$selectionSet = $node->selectionSet;
|
||||
|
||||
// Anonymous queries with no directives or variable definitions can use
|
||||
// the query short form.
|
||||
return ! $name && ! $directives && ! $varDefs && $op === 'query'
|
||||
|
|
|
@ -11,28 +11,28 @@ namespace GraphQL\Language;
|
|||
class Token
|
||||
{
|
||||
// Each kind of token.
|
||||
const SOF = '<SOF>';
|
||||
const EOF = '<EOF>';
|
||||
const BANG = '!';
|
||||
const DOLLAR = '$';
|
||||
const AMP = '&';
|
||||
const PAREN_L = '(';
|
||||
const PAREN_R = ')';
|
||||
const SPREAD = '...';
|
||||
const COLON = ':';
|
||||
const EQUALS = '=';
|
||||
const AT = '@';
|
||||
const BRACKET_L = '[';
|
||||
const BRACKET_R = ']';
|
||||
const BRACE_L = '{';
|
||||
const PIPE = '|';
|
||||
const BRACE_R = '}';
|
||||
const NAME = 'Name';
|
||||
const INT = 'Int';
|
||||
const FLOAT = 'Float';
|
||||
const STRING = 'String';
|
||||
const BLOCK_STRING = 'BlockString';
|
||||
const COMMENT = 'Comment';
|
||||
public const SOF = '<SOF>';
|
||||
public const EOF = '<EOF>';
|
||||
public const BANG = '!';
|
||||
public const DOLLAR = '$';
|
||||
public const AMP = '&';
|
||||
public const PAREN_L = '(';
|
||||
public const PAREN_R = ')';
|
||||
public const SPREAD = '...';
|
||||
public const COLON = ':';
|
||||
public const EQUALS = '=';
|
||||
public const AT = '@';
|
||||
public const BRACKET_L = '[';
|
||||
public const BRACKET_R = ']';
|
||||
public const BRACE_L = '{';
|
||||
public const PIPE = '|';
|
||||
public const BRACE_R = '}';
|
||||
public const NAME = 'Name';
|
||||
public const INT = 'Int';
|
||||
public const FLOAT = 'Float';
|
||||
public const STRING = 'String';
|
||||
public const BLOCK_STRING = 'BlockString';
|
||||
public const COMMENT = 'Comment';
|
||||
|
||||
/**
|
||||
* The kind of Token (see one of constants above).
|
||||
|
@ -104,18 +104,15 @@ class Token
|
|||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
public function getDescription() : string
|
||||
{
|
||||
return $this->kind . ($this->value ? ' "' . $this->value . '"' : '');
|
||||
return $this->kind . ($this->value === null ? '' : ' "' . $this->value . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return (string|int|null)[]
|
||||
*/
|
||||
public function toArray()
|
||||
public function toArray() : array
|
||||
{
|
||||
return [
|
||||
'kind' => $this->kind,
|
||||
|
|
|
@ -251,8 +251,18 @@ class Visitor
|
|||
$inArray = $stack['inArray'];
|
||||
$stack = $stack['prev'];
|
||||
} else {
|
||||
$key = $parent !== null ? ($inArray ? $index : $keys[$index]) : $UNDEFINED;
|
||||
$node = $parent !== null ? ($parent instanceof NodeList || is_array($parent) ? $parent[$key] : $parent->{$key}) : $newRoot;
|
||||
$key = $parent !== null
|
||||
? ($inArray
|
||||
? $index
|
||||
: $keys[$index]
|
||||
)
|
||||
: $UNDEFINED;
|
||||
$node = $parent !== null
|
||||
? ($parent instanceof NodeList || is_array($parent)
|
||||
? $parent[$key]
|
||||
: $parent->{$key}
|
||||
)
|
||||
: $newRoot;
|
||||
if ($node === null || $node === $UNDEFINED) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL;
|
||||
|
||||
use function trigger_error;
|
||||
use const E_USER_DEPRECATED;
|
||||
|
||||
trigger_error(
|
||||
'GraphQL\Schema is moved to GraphQL\Type\Schema',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
/**
|
||||
* Schema Definition
|
||||
*
|
||||
* @deprecated moved to GraphQL\Type\Schema
|
||||
*/
|
||||
class Schema extends \GraphQL\Type\Schema
|
||||
{
|
||||
}
|
|
@ -73,10 +73,14 @@ class Helper
|
|||
}
|
||||
|
||||
if (stripos($contentType, 'application/graphql') !== false) {
|
||||
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
|
||||
$rawBody = $readRawBodyFn
|
||||
? $readRawBodyFn()
|
||||
: $this->readRawBody();
|
||||
$bodyParams = ['query' => $rawBody ?: ''];
|
||||
} elseif (stripos($contentType, 'application/json') !== false) {
|
||||
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody();
|
||||
$rawBody = $readRawBodyFn ?
|
||||
$readRawBodyFn()
|
||||
: $this->readRawBody();
|
||||
$bodyParams = json_decode($rawBody ?: '', true);
|
||||
|
||||
if (json_last_error()) {
|
||||
|
@ -272,7 +276,9 @@ class Helper
|
|||
);
|
||||
}
|
||||
|
||||
$doc = $op->queryId ? $this->loadPersistedQuery($config, $op) : $op->query;
|
||||
$doc = $op->queryId
|
||||
? $this->loadPersistedQuery($config, $op)
|
||||
: $op->query;
|
||||
|
||||
if (! $doc instanceof DocumentNode) {
|
||||
$doc = Parser::parse($doc);
|
||||
|
@ -385,13 +391,13 @@ class Helper
|
|||
*/
|
||||
private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType)
|
||||
{
|
||||
$root = $config->getRootValue();
|
||||
$rootValue = $config->getRootValue();
|
||||
|
||||
if (is_callable($root)) {
|
||||
$root = $root($params, $doc, $operationType);
|
||||
if (is_callable($rootValue)) {
|
||||
$rootValue = $rootValue($params, $doc, $operationType);
|
||||
}
|
||||
|
||||
return $root;
|
||||
return $rootValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,6 +9,7 @@ use function is_string;
|
|||
use function json_decode;
|
||||
use function json_last_error;
|
||||
use const CASE_LOWER;
|
||||
use const JSON_ERROR_NONE;
|
||||
|
||||
/**
|
||||
* Structure representing parsed HTTP parameters for GraphQL operation
|
||||
|
@ -62,13 +63,10 @@ class OperationParams
|
|||
* Creates an instance from given array
|
||||
*
|
||||
* @param mixed[] $params
|
||||
* @param bool $readonly
|
||||
*
|
||||
* @return OperationParams
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public static function create(array $params, $readonly = false)
|
||||
public static function create(array $params, bool $readonly = false) : OperationParams
|
||||
{
|
||||
$instance = new static();
|
||||
|
||||
|
@ -96,7 +94,7 @@ class OperationParams
|
|||
}
|
||||
|
||||
$tmp = json_decode($params[$param], true);
|
||||
if (json_last_error()) {
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -108,7 +106,7 @@ class OperationParams
|
|||
$instance->operation = $params['operationname'];
|
||||
$instance->variables = $params['variables'];
|
||||
$instance->extensions = $params['extensions'];
|
||||
$instance->readOnly = (bool) $readonly;
|
||||
$instance->readOnly = $readonly;
|
||||
|
||||
// Apollo server/client compatibility: look for the queryid in extensions
|
||||
if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) {
|
||||
|
|
|
@ -225,15 +225,11 @@ class ServerConfig
|
|||
/**
|
||||
* Allow batching queries (disabled by default)
|
||||
*
|
||||
* @param bool $enableBatching
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function setQueryBatching($enableBatching)
|
||||
public function setQueryBatching(bool $enableBatching) : self
|
||||
{
|
||||
$this->queryBatching = (bool) $enableBatching;
|
||||
$this->queryBatching = $enableBatching;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -151,6 +151,7 @@ class StandardServer
|
|||
StreamInterface $writableBodyStream
|
||||
) {
|
||||
$result = $this->executePsrRequest($request);
|
||||
|
||||
return $this->helper->toPsrResponse($result, $response, $writableBodyStream);
|
||||
}
|
||||
|
||||
|
@ -165,6 +166,7 @@ class StandardServer
|
|||
public function executePsrRequest(ServerRequestInterface $request)
|
||||
{
|
||||
$parsedBody = $this->helper->parsePsrRequest($request);
|
||||
|
||||
return $this->executeRequest($parsedBody);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,9 +11,6 @@ use GraphQL\Language\AST\Node;
|
|||
use GraphQL\Utils\Utils;
|
||||
use function is_bool;
|
||||
|
||||
/**
|
||||
* Class BooleanType
|
||||
*/
|
||||
class BooleanType extends ScalarType
|
||||
{
|
||||
/** @var string */
|
||||
|
@ -23,11 +20,14 @@ class BooleanType extends ScalarType
|
|||
public $description = 'The `Boolean` scalar type represents `true` or `false`.';
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* Coerce the given value to a boolean.
|
||||
*
|
||||
* @return bool
|
||||
* The GraphQL spec leaves this up to the implementations, so we just do what
|
||||
* PHP does natively to make this intuitive for developers.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function serialize($value)
|
||||
public function serialize($value) : bool
|
||||
{
|
||||
return (bool) $value;
|
||||
}
|
||||
|
@ -58,11 +58,11 @@ class BooleanType extends ScalarType
|
|||
*/
|
||||
public function parseLiteral($valueNode, ?array $variables = null)
|
||||
{
|
||||
if ($valueNode instanceof BooleanValueNode) {
|
||||
return (bool) $valueNode->value;
|
||||
if (! $valueNode instanceof BooleanValueNode) {
|
||||
// Intentionally without message, as all information already in wrapped Exception
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
// Intentionally without message, as all information already in wrapped Exception
|
||||
throw new Exception();
|
||||
return $valueNode->value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,6 @@ use function call_user_func;
|
|||
use function is_callable;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Class CustomScalarType
|
||||
*/
|
||||
class CustomScalarType extends ScalarType
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -8,24 +8,19 @@ use GraphQL\Language\AST\DirectiveDefinitionNode;
|
|||
use GraphQL\Language\DirectiveLocation;
|
||||
use GraphQL\Utils\Utils;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class Directive
|
||||
*/
|
||||
class Directive
|
||||
{
|
||||
public const DEFAULT_DEPRECATION_REASON = 'No longer supported';
|
||||
|
||||
const INCLUDE_NAME = 'include';
|
||||
const IF_ARGUMENT_NAME = 'if';
|
||||
const SKIP_NAME = 'skip';
|
||||
const DEPRECATED_NAME = 'deprecated';
|
||||
const REASON_ARGUMENT_NAME = 'reason';
|
||||
public const INCLUDE_NAME = 'include';
|
||||
public const IF_ARGUMENT_NAME = 'if';
|
||||
public const SKIP_NAME = 'skip';
|
||||
public const DEPRECATED_NAME = 'deprecated';
|
||||
public const REASON_ARGUMENT_NAME = 'reason';
|
||||
|
||||
/** @var Directive[] */
|
||||
/** @var Directive[]|null */
|
||||
public static $internalDirectives;
|
||||
|
||||
// Schema Definitions
|
||||
|
@ -40,7 +35,7 @@ class Directive
|
|||
public $locations;
|
||||
|
||||
/** @var FieldArgument[] */
|
||||
public $args;
|
||||
public $args = [];
|
||||
|
||||
/** @var DirectiveDefinitionNode|null */
|
||||
public $astNode;
|
||||
|
@ -80,15 +75,16 @@ class Directive
|
|||
public static function includeDirective()
|
||||
{
|
||||
$internal = self::getInternalDirectives();
|
||||
|
||||
return $internal['include'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Directive[]
|
||||
*/
|
||||
public static function getInternalDirectives()
|
||||
public static function getInternalDirectives() : array
|
||||
{
|
||||
if (! self::$internalDirectives) {
|
||||
if (self::$internalDirectives === null) {
|
||||
self::$internalDirectives = [
|
||||
'include' => new self([
|
||||
'name' => self::INCLUDE_NAME,
|
||||
|
@ -140,24 +136,30 @@ class Directive
|
|||
]),
|
||||
];
|
||||
}
|
||||
|
||||
return self::$internalDirectives;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Directive
|
||||
*/
|
||||
public static function skipDirective()
|
||||
{
|
||||
$internal = self::getInternalDirectives();
|
||||
|
||||
return $internal['skip'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Directive
|
||||
*/
|
||||
public static function deprecatedDirective()
|
||||
{
|
||||
$internal = self::getInternalDirectives();
|
||||
|
||||
return $internal['deprecated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
|
|
|
@ -19,9 +19,6 @@ use function is_int;
|
|||
use function is_string;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Class EnumType
|
||||
*/
|
||||
class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType
|
||||
{
|
||||
/** @var EnumTypeDefinitionNode|null */
|
||||
|
@ -33,7 +30,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
|
|||
/** @var MixedStore<mixed, EnumValueDefinition> */
|
||||
private $valueLookup;
|
||||
|
||||
/** @var \ArrayObject<string, EnumValueDefinition> */
|
||||
/** @var ArrayObject<string, EnumValueDefinition> */
|
||||
private $nameLookup;
|
||||
|
||||
/** @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()
|
||||
{
|
||||
|
|
|
@ -6,9 +6,6 @@ namespace GraphQL\Type\Definition;
|
|||
|
||||
use GraphQL\Language\AST\EnumValueDefinitionNode;
|
||||
|
||||
/**
|
||||
* Class EnumValueDefinition
|
||||
*/
|
||||
class EnumValueDefinition
|
||||
{
|
||||
/** @var string */
|
||||
|
|
|
@ -11,9 +11,6 @@ use function is_array;
|
|||
use function is_string;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Class FieldArgument
|
||||
*/
|
||||
class FieldArgument
|
||||
{
|
||||
/** @var string */
|
||||
|
|
|
@ -81,7 +81,7 @@ class FieldDefinition
|
|||
|
||||
$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)
|
||||
|
|
|
@ -12,9 +12,6 @@ use GraphQL\Language\AST\Node;
|
|||
use GraphQL\Utils\Utils;
|
||||
use function is_numeric;
|
||||
|
||||
/**
|
||||
* Class FloatType
|
||||
*/
|
||||
class FloatType extends ScalarType
|
||||
{
|
||||
/** @var string */
|
||||
|
|
|
@ -15,9 +15,6 @@ use function is_callable;
|
|||
use function is_string;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Class InputObjectType
|
||||
*/
|
||||
class InputObjectType extends Type implements InputType, NullableType, NamedType
|
||||
{
|
||||
/** @var InputObjectTypeDefinitionNode|null */
|
||||
|
@ -72,7 +69,9 @@ class InputObjectType extends Type implements InputType, NullableType, NamedType
|
|||
if ($this->fields === null) {
|
||||
$this->fields = [];
|
||||
$fields = $this->config['fields'] ?? [];
|
||||
$fields = is_callable($fields) ? call_user_func($fields) : $fields;
|
||||
$fields = is_callable($fields)
|
||||
? call_user_func($fields)
|
||||
: $fields;
|
||||
|
||||
if (! is_array($fields)) {
|
||||
throw new InvariantViolation(
|
||||
|
|
|
@ -14,9 +14,6 @@ use function intval;
|
|||
use function is_bool;
|
||||
use function is_numeric;
|
||||
|
||||
/**
|
||||
* Class IntType
|
||||
*/
|
||||
class IntType extends ScalarType
|
||||
{
|
||||
// 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 sprintf;
|
||||
|
||||
/**
|
||||
* Class InterfaceType
|
||||
*/
|
||||
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
|
||||
{
|
||||
/** @var InterfaceTypeDefinitionNode|null */
|
||||
|
|
|
@ -4,9 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
/**
|
||||
* Class ListOfType
|
||||
*/
|
||||
class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType
|
||||
{
|
||||
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */
|
||||
|
@ -20,15 +17,9 @@ class ListOfType extends Type implements WrappingType, OutputType, NullableType,
|
|||
$this->ofType = Type::assertType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function toString()
|
||||
public function toString() : string
|
||||
{
|
||||
$type = $this->ofType;
|
||||
$str = $type instanceof Type ? $type->toString() : (string) $type;
|
||||
|
||||
return '[' . $str . ']';
|
||||
return '[' . $this->ofType->toString() . ']';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,6 +31,8 @@ class ListOfType extends Type implements WrappingType, OutputType, NullableType,
|
|||
{
|
||||
$type = $this->ofType;
|
||||
|
||||
return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) : $type;
|
||||
return $recurse && $type instanceof WrappingType
|
||||
? $type->getWrappedType($recurse)
|
||||
: $type;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,6 @@ namespace GraphQL\Type\Definition;
|
|||
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
/**
|
||||
* Class NonNull
|
||||
*/
|
||||
class NonNull extends Type implements WrappingType, OutputType, InputType
|
||||
{
|
||||
/** @var NullableType */
|
||||
|
@ -69,6 +66,8 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
|
|||
{
|
||||
$type = $this->ofType;
|
||||
|
||||
return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) : $type;
|
||||
return $recurse && $type instanceof WrappingType
|
||||
? $type->getWrappedType($recurse)
|
||||
: $type;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,7 +168,7 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
|
|||
|
||||
private function getInterfaceMap()
|
||||
{
|
||||
if (! $this->interfaceMap) {
|
||||
if ($this->interfaceMap === null) {
|
||||
$this->interfaceMap = [];
|
||||
foreach ($this->getInterfaces() as $interface) {
|
||||
$this->interfaceMap[$interface->name] = $interface;
|
||||
|
@ -185,7 +185,9 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
|
|||
{
|
||||
if ($this->interfaces === null) {
|
||||
$interfaces = $this->config['interfaces'] ?? [];
|
||||
$interfaces = is_callable($interfaces) ? call_user_func($interfaces) : $interfaces;
|
||||
$interfaces = is_callable($interfaces)
|
||||
? call_user_func($interfaces)
|
||||
: $interfaces;
|
||||
|
||||
if ($interfaces !== null && ! is_array($interfaces)) {
|
||||
throw new InvariantViolation(
|
||||
|
@ -200,19 +202,21 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
|
|||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $value
|
||||
* @param mixed $value
|
||||
* @param mixed[]|null $context
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function isTypeOf($value, $context, ResolveInfo $info)
|
||||
{
|
||||
return isset($this->config['isTypeOf']) ? call_user_func(
|
||||
$this->config['isTypeOf'],
|
||||
$value,
|
||||
$context,
|
||||
$info
|
||||
) : null;
|
||||
return isset($this->config['isTypeOf'])
|
||||
? call_user_func(
|
||||
$this->config['isTypeOf'],
|
||||
$value,
|
||||
$context,
|
||||
$info
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
242
src/Type/Definition/QueryPlan.php
Normal file
242
src/Type/Definition/QueryPlan.php
Normal file
|
@ -0,0 +1,242 @@
|
|||
<?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[]
|
||||
*
|
||||
* $parentType InterfaceType|ObjectType.
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $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;
|
||||
}
|
||||
}
|
|
@ -15,12 +15,13 @@ use function array_merge_recursive;
|
|||
|
||||
/**
|
||||
* Structure containing information useful for field resolution process.
|
||||
* Passed as 3rd argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md).
|
||||
*
|
||||
* Passed as 4th argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md).
|
||||
*/
|
||||
class ResolveInfo
|
||||
{
|
||||
/**
|
||||
* The name of the field being resolved
|
||||
* The name of the field being resolved.
|
||||
*
|
||||
* @api
|
||||
* @var string
|
||||
|
@ -36,7 +37,7 @@ class ResolveInfo
|
|||
public $fieldNodes = [];
|
||||
|
||||
/**
|
||||
* Expected return type of the field being resolved
|
||||
* Expected return type of the field being resolved.
|
||||
*
|
||||
* @api
|
||||
* @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull
|
||||
|
@ -44,7 +45,7 @@ class ResolveInfo
|
|||
public $returnType;
|
||||
|
||||
/**
|
||||
* Parent type of the field being resolved
|
||||
* Parent type of the field being resolved.
|
||||
*
|
||||
* @api
|
||||
* @var ObjectType
|
||||
|
@ -52,7 +53,7 @@ class ResolveInfo
|
|||
public $parentType;
|
||||
|
||||
/**
|
||||
* Path to this field from the very root value
|
||||
* Path to this field from the very root value.
|
||||
*
|
||||
* @api
|
||||
* @var string[][]
|
||||
|
@ -60,15 +61,15 @@ class ResolveInfo
|
|||
public $path;
|
||||
|
||||
/**
|
||||
* Instance of a schema used for execution
|
||||
* Instance of a schema used for execution.
|
||||
*
|
||||
* @api
|
||||
* @var Schema|null
|
||||
* @var Schema
|
||||
*/
|
||||
public $schema;
|
||||
|
||||
/**
|
||||
* AST of all fragments defined in query
|
||||
* AST of all fragments defined in query.
|
||||
*
|
||||
* @api
|
||||
* @var FragmentDefinitionNode[]
|
||||
|
@ -76,15 +77,15 @@ class ResolveInfo
|
|||
public $fragments = [];
|
||||
|
||||
/**
|
||||
* Root value passed to query execution
|
||||
* Root value passed to query execution.
|
||||
*
|
||||
* @api
|
||||
* @var mixed|null
|
||||
* @var mixed
|
||||
*/
|
||||
public $rootValue;
|
||||
|
||||
/**
|
||||
* AST of operation definition node (query, mutation)
|
||||
* AST of operation definition node (query, mutation).
|
||||
*
|
||||
* @api
|
||||
* @var OperationDefinitionNode|null
|
||||
|
@ -92,13 +93,16 @@ class ResolveInfo
|
|||
public $operation;
|
||||
|
||||
/**
|
||||
* Array of variables passed to query execution
|
||||
* Array of variables passed to query execution.
|
||||
*
|
||||
* @api
|
||||
* @var mixed[]
|
||||
*/
|
||||
public $variableValues = [];
|
||||
|
||||
/** @var QueryPlan */
|
||||
private $queryPlan;
|
||||
|
||||
/**
|
||||
* @param FieldNode[] $fieldNodes
|
||||
* @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType
|
||||
|
@ -109,7 +113,7 @@ class ResolveInfo
|
|||
*/
|
||||
public function __construct(
|
||||
string $fieldName,
|
||||
$fieldNodes,
|
||||
iterable $fieldNodes,
|
||||
$returnType,
|
||||
ObjectType $parentType,
|
||||
array $path,
|
||||
|
@ -133,7 +137,7 @@ class ResolveInfo
|
|||
|
||||
/**
|
||||
* Helper method that returns names of all fields selected in query for
|
||||
* $this->fieldName up to $depth levels
|
||||
* $this->fieldName up to $depth levels.
|
||||
*
|
||||
* Example:
|
||||
* query MyQuery{
|
||||
|
@ -174,11 +178,30 @@ class ResolveInfo
|
|||
|
||||
/** @var FieldNode $fieldNode */
|
||||
foreach ($this->fieldNodes as $fieldNode) {
|
||||
$fields = array_merge_recursive($fields, $this->foldSelectionSet($fieldNode->selectionSet, $depth));
|
||||
$fields = array_merge_recursive(
|
||||
$fields,
|
||||
$this->foldSelectionSet($fieldNode->selectionSet, $depth)
|
||||
);
|
||||
}
|
||||
|
||||
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[]
|
||||
*/
|
||||
|
@ -207,6 +230,7 @@ class ResolveInfo
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,9 +14,6 @@ use function is_object;
|
|||
use function is_scalar;
|
||||
use function method_exists;
|
||||
|
||||
/**
|
||||
* Class StringType
|
||||
*/
|
||||
class StringType extends ScalarType
|
||||
{
|
||||
/** @var string */
|
||||
|
|
|
@ -194,6 +194,7 @@ abstract class Type implements JsonSerializable
|
|||
public static function getInternalTypes()
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated. Use Type::getStandardTypes() instead', E_USER_DEPRECATED);
|
||||
|
||||
return self::getStandardTypes();
|
||||
}
|
||||
|
||||
|
@ -344,7 +345,9 @@ abstract class Type implements JsonSerializable
|
|||
*/
|
||||
public static function getNullableType($type)
|
||||
{
|
||||
return $type instanceof NonNull ? $type->getWrappedType() : $type;
|
||||
return $type instanceof NonNull
|
||||
? $type->getWrappedType()
|
||||
: $type;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,9 +14,6 @@ use function is_callable;
|
|||
use function is_string;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Class UnionType
|
||||
*/
|
||||
class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
|
||||
{
|
||||
/** @var UnionTypeDefinitionNode */
|
||||
|
|
|
@ -488,7 +488,9 @@ EOD;
|
|||
'type' => [
|
||||
'type' => Type::nonNull(self::_type()),
|
||||
'resolve' => static function ($value) {
|
||||
return method_exists($value, 'getType') ? $value->getType() : $value->type;
|
||||
return method_exists($value, 'getType')
|
||||
? $value->getType()
|
||||
: $value->type;
|
||||
},
|
||||
],
|
||||
'defaultValue' => [
|
||||
|
@ -693,7 +695,7 @@ EOD;
|
|||
return self::$map['__DirectiveLocation'];
|
||||
}
|
||||
|
||||
public static function schemaMetaFieldDef()
|
||||
public static function schemaMetaFieldDef() : FieldDefinition
|
||||
{
|
||||
if (! isset(self::$map[self::SCHEMA_FIELD_NAME])) {
|
||||
self::$map[self::SCHEMA_FIELD_NAME] = FieldDefinition::create([
|
||||
|
@ -715,7 +717,7 @@ EOD;
|
|||
return self::$map[self::SCHEMA_FIELD_NAME];
|
||||
}
|
||||
|
||||
public static function typeMetaFieldDef()
|
||||
public static function typeMetaFieldDef() : FieldDefinition
|
||||
{
|
||||
if (! isset(self::$map[self::TYPE_FIELD_NAME])) {
|
||||
self::$map[self::TYPE_FIELD_NAME] = FieldDefinition::create([
|
||||
|
@ -734,7 +736,7 @@ EOD;
|
|||
return self::$map[self::TYPE_FIELD_NAME];
|
||||
}
|
||||
|
||||
public static function typeNameMetaFieldDef()
|
||||
public static function typeNameMetaFieldDef() : FieldDefinition
|
||||
{
|
||||
if (! isset(self::$map[self::TYPE_NAME_FIELD_NAME])) {
|
||||
self::$map[self::TYPE_NAME_FIELD_NAME] = FieldDefinition::create([
|
||||
|
|
|
@ -42,7 +42,7 @@ class SchemaConfig
|
|||
/** @var Directive[] */
|
||||
public $directives;
|
||||
|
||||
/** @var callable */
|
||||
/** @var callable|null */
|
||||
public $typeLoader;
|
||||
|
||||
/** @var SchemaDefinitionNode */
|
||||
|
|
|
@ -29,6 +29,7 @@ use GraphQL\Type\Definition\NonNull;
|
|||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use GraphQL\Type\Validation\InputObjectCircularRefs;
|
||||
use GraphQL\Utils\TypeComparators;
|
||||
use GraphQL\Utils\Utils;
|
||||
use function array_filter;
|
||||
|
@ -48,9 +49,13 @@ class SchemaValidationContext
|
|||
/** @var Schema */
|
||||
private $schema;
|
||||
|
||||
/** @var InputObjectCircularRefs */
|
||||
private $inputObjectCircularRefs;
|
||||
|
||||
public function __construct(Schema $schema)
|
||||
{
|
||||
$this->schema = $schema;
|
||||
$this->schema = $schema;
|
||||
$this->inputObjectCircularRefs = new InputObjectCircularRefs($this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,7 +104,7 @@ class SchemaValidationContext
|
|||
* @param string $message
|
||||
* @param Node[]|Node|TypeNode|TypeDefinitionNode|null $nodes
|
||||
*/
|
||||
private function reportError($message, $nodes = null)
|
||||
public function reportError($message, $nodes = null)
|
||||
{
|
||||
$nodes = array_filter($nodes && is_array($nodes) ? $nodes : [$nodes]);
|
||||
$this->addError(new Error($message, $nodes));
|
||||
|
@ -247,7 +252,7 @@ class SchemaValidationContext
|
|||
if (! $type instanceof NamedType) {
|
||||
$this->reportError(
|
||||
'Expected GraphQL named type but got: ' . Utils::printSafe($type) . '.',
|
||||
is_object($type) ? $type->astNode : null
|
||||
$type instanceof Type ? $type->astNode : null
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
@ -275,6 +280,9 @@ class SchemaValidationContext
|
|||
} elseif ($type instanceof InputObjectType) {
|
||||
// Ensure Input Object fields are valid.
|
||||
$this->validateInputFields($type);
|
||||
|
||||
// Ensure Input Objects do not contain non-nullable circular references
|
||||
$this->inputObjectCircularRefs->validate($type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -760,8 +768,9 @@ class SchemaValidationContext
|
|||
);
|
||||
}
|
||||
|
||||
return $union->astNode ?
|
||||
$union->astNode->types : null;
|
||||
return $union->astNode
|
||||
? $union->astNode->types
|
||||
: null;
|
||||
}
|
||||
|
||||
private function validateEnumValues(EnumType $enumType)
|
||||
|
@ -816,8 +825,9 @@ class SchemaValidationContext
|
|||
);
|
||||
}
|
||||
|
||||
return $enum->astNode ?
|
||||
$enum->astNode->values : null;
|
||||
return $enum->astNode
|
||||
? $enum->astNode->values
|
||||
: null;
|
||||
}
|
||||
|
||||
private function validateInputFields(InputObjectType $inputObj)
|
||||
|
|
105
src/Type/Validation/InputObjectCircularRefs.php
Normal file
105
src/Type/Validation/InputObjectCircularRefs.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Type\Validation;
|
||||
|
||||
use GraphQL\Language\AST\InputValueDefinitionNode;
|
||||
use GraphQL\Type\Definition\InputObjectField;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\SchemaValidationContext;
|
||||
use function array_map;
|
||||
use function array_pop;
|
||||
use function array_slice;
|
||||
use function count;
|
||||
use function implode;
|
||||
|
||||
class InputObjectCircularRefs
|
||||
{
|
||||
/** @var SchemaValidationContext */
|
||||
private $schemaValidationContext;
|
||||
|
||||
/**
|
||||
* Tracks already visited types to maintain O(N) and to ensure that cycles
|
||||
* are not redundantly reported.
|
||||
*
|
||||
* @var InputObjectType[]
|
||||
*/
|
||||
private $visitedTypes = [];
|
||||
|
||||
/** @var InputObjectField[] */
|
||||
private $fieldPath = [];
|
||||
|
||||
/**
|
||||
* Position in the type path.
|
||||
*
|
||||
* [string $typeName => int $index]
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
private $fieldPathIndexByTypeName = [];
|
||||
|
||||
public function __construct(SchemaValidationContext $schemaValidationContext)
|
||||
{
|
||||
$this->schemaValidationContext = $schemaValidationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* This does a straight-forward DFS to find cycles.
|
||||
* It does not terminate when a cycle was found but continues to explore
|
||||
* the graph to find all possible cycles.
|
||||
*/
|
||||
public function validate(InputObjectType $inputObj) : void
|
||||
{
|
||||
if (isset($this->visitedTypes[$inputObj->name])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->visitedTypes[$inputObj->name] = true;
|
||||
$this->fieldPathIndexByTypeName[$inputObj->name] = count($this->fieldPath);
|
||||
|
||||
$fieldMap = $inputObj->getFields();
|
||||
foreach ($fieldMap as $fieldName => $field) {
|
||||
$type = $field->getType();
|
||||
|
||||
if ($type instanceof NonNull) {
|
||||
$fieldType = $type->getWrappedType();
|
||||
|
||||
// If the type of the field is anything else then a non-nullable input object,
|
||||
// there is no chance of an unbreakable cycle
|
||||
if ($fieldType instanceof InputObjectType) {
|
||||
$this->fieldPath[] = $field;
|
||||
|
||||
if (! isset($this->fieldPathIndexByTypeName[$fieldType->name])) {
|
||||
$this->validate($fieldType);
|
||||
} else {
|
||||
$cycleIndex = $this->fieldPathIndexByTypeName[$fieldType->name];
|
||||
$cyclePath = array_slice($this->fieldPath, $cycleIndex);
|
||||
$fieldNames = array_map(
|
||||
static function (InputObjectField $field) : string {
|
||||
return $field->name;
|
||||
},
|
||||
$cyclePath
|
||||
);
|
||||
|
||||
$this->schemaValidationContext->reportError(
|
||||
'Cannot reference Input Object "' . $fieldType->name . '" within itself '
|
||||
. 'through a series of non-null fields: "' . implode('.', $fieldNames) . '".',
|
||||
array_map(
|
||||
static function (InputObjectField $field) : ?InputValueDefinitionNode {
|
||||
return $field->astNode;
|
||||
},
|
||||
$cyclePath
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
array_pop($this->fieldPath);
|
||||
}
|
||||
|
||||
unset($this->fieldPathIndexByTypeName[$inputObj->name]);
|
||||
}
|
||||
}
|
|
@ -183,7 +183,7 @@ class AST
|
|||
$valuesNodes[] = $itemNode;
|
||||
}
|
||||
|
||||
return new ListValueNode(['values' => $valuesNodes]);
|
||||
return new ListValueNode(['values' => new NodeList($valuesNodes)]);
|
||||
}
|
||||
|
||||
return self::astFromValue($value, $itemType);
|
||||
|
@ -235,7 +235,7 @@ class AST
|
|||
]);
|
||||
}
|
||||
|
||||
return new ObjectValueNode(['fields' => $fieldNodes]);
|
||||
return new ObjectValueNode(['fields' => new NodeList($fieldNodes)]);
|
||||
}
|
||||
|
||||
if ($type instanceof ScalarType || $type instanceof EnumType) {
|
||||
|
@ -322,7 +322,7 @@ class AST
|
|||
*
|
||||
* @api
|
||||
*/
|
||||
public static function valueFromAST($valueNode, InputType $type, ?array $variables = null)
|
||||
public static function valueFromAST($valueNode, Type $type, ?array $variables = null)
|
||||
{
|
||||
$undefined = Utils::undefined();
|
||||
|
||||
|
@ -353,6 +353,7 @@ class AST
|
|||
// No valid return value.
|
||||
return $undefined;
|
||||
}
|
||||
|
||||
// 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
|
||||
// is of the correct type.
|
||||
|
|
|
@ -17,7 +17,6 @@ use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
|
|||
use GraphQL\Language\AST\ListTypeNode;
|
||||
use GraphQL\Language\AST\NamedTypeNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\NonNullTypeNode;
|
||||
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
|
||||
|
@ -144,7 +143,8 @@ class ASTDefinitionBuilder
|
|||
// Note: While this could make assertions to get the correctly typed
|
||||
// value, that would throw immediately while type system validation
|
||||
// with validateSchema() will produce more actionable results.
|
||||
$type = $this->internalBuildWrappedType($value->type);
|
||||
$type = $this->internalBuildWrappedType($value->type);
|
||||
|
||||
$config = [
|
||||
'name' => $value->name->value,
|
||||
'type' => $type,
|
||||
|
@ -244,23 +244,20 @@ class ASTDefinitionBuilder
|
|||
*
|
||||
* @throws Error
|
||||
*/
|
||||
private function makeSchemaDef($def)
|
||||
private function makeSchemaDef(Node $def)
|
||||
{
|
||||
if (! $def) {
|
||||
throw new Error('def must be defined.');
|
||||
}
|
||||
switch ($def->kind) {
|
||||
case NodeKind::OBJECT_TYPE_DEFINITION:
|
||||
switch (true) {
|
||||
case $def instanceof ObjectTypeDefinitionNode:
|
||||
return $this->makeTypeDef($def);
|
||||
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
||||
case $def instanceof InterfaceTypeDefinitionNode:
|
||||
return $this->makeInterfaceDef($def);
|
||||
case NodeKind::ENUM_TYPE_DEFINITION:
|
||||
case $def instanceof EnumTypeDefinitionNode:
|
||||
return $this->makeEnumDef($def);
|
||||
case NodeKind::UNION_TYPE_DEFINITION:
|
||||
case $def instanceof UnionTypeDefinitionNode:
|
||||
return $this->makeUnionDef($def);
|
||||
case NodeKind::SCALAR_TYPE_DEFINITION:
|
||||
case $def instanceof ScalarTypeDefinitionNode:
|
||||
return $this->makeScalarDef($def);
|
||||
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
||||
case $def instanceof InputObjectTypeDefinitionNode:
|
||||
return $this->makeInputObjectDef($def);
|
||||
default:
|
||||
throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
|
||||
|
@ -397,8 +394,8 @@ class ASTDefinitionBuilder
|
|||
function ($typeNode) {
|
||||
return $this->buildType($typeNode);
|
||||
}
|
||||
) :
|
||||
[],
|
||||
)
|
||||
: [],
|
||||
'astNode' => $def,
|
||||
]);
|
||||
}
|
||||
|
@ -430,62 +427,48 @@ class ASTDefinitionBuilder
|
|||
}
|
||||
|
||||
/**
|
||||
* @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeExtensionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode $def
|
||||
* @param mixed[] $config
|
||||
* @param mixed[] $config
|
||||
*
|
||||
* @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
private function makeSchemaDefFromConfig($def, array $config)
|
||||
private function makeSchemaDefFromConfig(Node $def, array $config)
|
||||
{
|
||||
if (! $def) {
|
||||
throw new Error('def must be defined.');
|
||||
}
|
||||
switch ($def->kind) {
|
||||
case NodeKind::OBJECT_TYPE_DEFINITION:
|
||||
switch (true) {
|
||||
case $def instanceof ObjectTypeDefinitionNode:
|
||||
return new ObjectType($config);
|
||||
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
||||
case $def instanceof InterfaceTypeDefinitionNode:
|
||||
return new InterfaceType($config);
|
||||
case NodeKind::ENUM_TYPE_DEFINITION:
|
||||
case $def instanceof EnumTypeDefinitionNode:
|
||||
return new EnumType($config);
|
||||
case NodeKind::UNION_TYPE_DEFINITION:
|
||||
case $def instanceof UnionTypeDefinitionNode:
|
||||
return new UnionType($config);
|
||||
case NodeKind::SCALAR_TYPE_DEFINITION:
|
||||
case $def instanceof ScalarTypeDefinitionNode:
|
||||
return new CustomScalarType($config);
|
||||
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
||||
case $def instanceof InputObjectTypeDefinitionNode:
|
||||
return new InputObjectType($config);
|
||||
default:
|
||||
throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypeNode|ListTypeNode|NonNullTypeNode $typeNode
|
||||
*
|
||||
* @return TypeNode
|
||||
*/
|
||||
private function getNamedTypeNode(TypeNode $typeNode)
|
||||
private function getNamedTypeNode(TypeNode $typeNode) : TypeNode
|
||||
{
|
||||
$namedType = $typeNode;
|
||||
while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) {
|
||||
while ($namedType instanceof ListTypeNode || $namedType instanceof NonNullTypeNode) {
|
||||
$namedType = $namedType->type;
|
||||
}
|
||||
|
||||
return $namedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
|
||||
*
|
||||
* @return Type
|
||||
*/
|
||||
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode)
|
||||
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode) : Type
|
||||
{
|
||||
if ($inputTypeNode->kind === NodeKind::LIST_TYPE) {
|
||||
if ($inputTypeNode instanceof ListTypeNode) {
|
||||
return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type));
|
||||
}
|
||||
if ($inputTypeNode->kind === NodeKind::NON_NULL_TYPE) {
|
||||
if ($inputTypeNode instanceof NonNullTypeNode) {
|
||||
$wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
|
||||
|
||||
return Type::nonNull(NonNull::assertNullableType($wrappedType));
|
||||
|
|
|
@ -112,27 +112,31 @@ class BreakingChangesFinder
|
|||
* @return string[][]
|
||||
*/
|
||||
public static function findTypesThatChangedKind(
|
||||
Schema $oldSchema,
|
||||
Schema $newSchema
|
||||
) {
|
||||
$oldTypeMap = $oldSchema->getTypeMap();
|
||||
$newTypeMap = $newSchema->getTypeMap();
|
||||
Schema $schemaA,
|
||||
Schema $schemaB
|
||||
) : iterable {
|
||||
$schemaATypeMap = $schemaA->getTypeMap();
|
||||
$schemaBTypeMap = $schemaB->getTypeMap();
|
||||
|
||||
$breakingChanges = [];
|
||||
foreach ($oldTypeMap as $typeName => $oldType) {
|
||||
if (! isset($newTypeMap[$typeName])) {
|
||||
foreach ($schemaATypeMap as $typeName => $schemaAType) {
|
||||
if (! isset($schemaBTypeMap[$typeName])) {
|
||||
continue;
|
||||
}
|
||||
$newType = $newTypeMap[$typeName];
|
||||
if ($oldType instanceof $newType) {
|
||||
$schemaBType = $schemaBTypeMap[$typeName];
|
||||
if ($schemaAType instanceof $schemaBType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oldTypeKindName = self::typeKindName($oldType);
|
||||
$newTypeKindName = self::typeKindName($newType);
|
||||
$breakingChanges[] = [
|
||||
if ($schemaBType instanceof $schemaAType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$schemaATypeKindName = self::typeKindName($schemaAType);
|
||||
$schemaBTypeKindName = self::typeKindName($schemaBType);
|
||||
$breakingChanges[] = [
|
||||
'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
|
||||
foreach ($newTypeFields[$fieldName]->args as $newArgDef) {
|
||||
foreach ($newTypeFields[$fieldName]->args as $newTypeFieldArgDef) {
|
||||
$oldArgs = $oldTypeFields[$fieldName]->args;
|
||||
$oldArgDef = Utils::find(
|
||||
$oldArgs,
|
||||
static function ($arg) use ($newArgDef) {
|
||||
return $arg->name === $newArgDef->name;
|
||||
static function ($arg) use ($newTypeFieldArgDef) {
|
||||
return $arg->name === $newTypeFieldArgDef->name;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -541,8 +545,8 @@ class BreakingChangesFinder
|
|||
}
|
||||
|
||||
$newTypeName = $newType->name;
|
||||
$newArgName = $newArgDef->name;
|
||||
if ($newArgDef->getType() instanceof NonNull) {
|
||||
$newArgName = $newTypeFieldArgDef->name;
|
||||
if ($newTypeFieldArgDef->getType() instanceof NonNull) {
|
||||
$breakingChanges[] = [
|
||||
'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED,
|
||||
'description' => "A non-null arg ${newArgName} on ${newTypeName}.${fieldName} was added",
|
||||
|
@ -664,7 +668,7 @@ class BreakingChangesFinder
|
|||
{
|
||||
$removedArgs = [];
|
||||
$newArgMap = self::getArgumentMapForDirective($newDirective);
|
||||
foreach ((array) $oldDirective->args as $arg) {
|
||||
foreach ($oldDirective->args as $arg) {
|
||||
if (isset($newArgMap[$arg->name])) {
|
||||
continue;
|
||||
}
|
||||
|
@ -723,7 +727,7 @@ class BreakingChangesFinder
|
|||
{
|
||||
$addedArgs = [];
|
||||
$oldArgMap = self::getArgumentMapForDirective($oldDirective);
|
||||
foreach ((array) $newDirective->args as $arg) {
|
||||
foreach ($newDirective->args as $arg) {
|
||||
if (isset($oldArgMap[$arg->name])) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,16 @@ declare(strict_types=1);
|
|||
namespace GraphQL\Utils;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\DirectiveDefinitionNode;
|
||||
use GraphQL\Language\AST\DocumentNode;
|
||||
use GraphQL\Language\AST\EnumTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\SchemaDefinitionNode;
|
||||
use GraphQL\Language\AST\UnionTypeDefinitionNode;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
|
@ -95,39 +101,38 @@ class BuildSchema
|
|||
|
||||
public function buildSchema()
|
||||
{
|
||||
/** @var SchemaDefinitionNode $schemaDef */
|
||||
$schemaDef = null;
|
||||
$typeDefs = [];
|
||||
$this->nodeMap = [];
|
||||
$directiveDefs = [];
|
||||
foreach ($this->ast->definitions as $d) {
|
||||
switch ($d->kind) {
|
||||
case NodeKind::SCHEMA_DEFINITION:
|
||||
if ($schemaDef) {
|
||||
foreach ($this->ast->definitions as $definition) {
|
||||
switch (true) {
|
||||
case $definition instanceof SchemaDefinitionNode:
|
||||
if ($schemaDef !== null) {
|
||||
throw new Error('Must provide only one schema definition.');
|
||||
}
|
||||
$schemaDef = $d;
|
||||
$schemaDef = $definition;
|
||||
break;
|
||||
case NodeKind::SCALAR_TYPE_DEFINITION:
|
||||
case NodeKind::OBJECT_TYPE_DEFINITION:
|
||||
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
||||
case NodeKind::ENUM_TYPE_DEFINITION:
|
||||
case NodeKind::UNION_TYPE_DEFINITION:
|
||||
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
||||
$typeName = $d->name->value;
|
||||
case $definition instanceof ScalarTypeDefinitionNode:
|
||||
case $definition instanceof ObjectTypeDefinitionNode:
|
||||
case $definition instanceof InterfaceTypeDefinitionNode:
|
||||
case $definition instanceof EnumTypeDefinitionNode:
|
||||
case $definition instanceof UnionTypeDefinitionNode:
|
||||
case $definition instanceof InputObjectTypeDefinitionNode:
|
||||
$typeName = $definition->name->value;
|
||||
if (! empty($this->nodeMap[$typeName])) {
|
||||
throw new Error(sprintf('Type "%s" was defined more than once.', $typeName));
|
||||
}
|
||||
$typeDefs[] = $d;
|
||||
$this->nodeMap[$typeName] = $d;
|
||||
$typeDefs[] = $definition;
|
||||
$this->nodeMap[$typeName] = $definition;
|
||||
break;
|
||||
case NodeKind::DIRECTIVE_DEFINITION:
|
||||
$directiveDefs[] = $d;
|
||||
case $definition instanceof DirectiveDefinitionNode:
|
||||
$directiveDefs[] = $definition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$operationTypes = $schemaDef
|
||||
$operationTypes = $schemaDef !== null
|
||||
? $this->getOperationTypes($schemaDef)
|
||||
: [
|
||||
'query' => isset($this->nodeMap['Query']) ? 'Query' : null,
|
||||
|
@ -154,9 +159,10 @@ class BuildSchema
|
|||
// If specified directives were not explicitly declared, add them.
|
||||
$skip = array_reduce(
|
||||
$directives,
|
||||
static function ($hasSkip, $directive) {
|
||||
static function (bool $hasSkip, Directive $directive) : bool {
|
||||
return $hasSkip || $directive->name === 'skip';
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
if (! $skip) {
|
||||
$directives[] = Directive::skipDirective();
|
||||
|
@ -164,9 +170,10 @@ class BuildSchema
|
|||
|
||||
$include = array_reduce(
|
||||
$directives,
|
||||
static function ($hasInclude, $directive) {
|
||||
static function (bool $hasInclude, Directive $directive) : bool {
|
||||
return $hasInclude || $directive->name === 'include';
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
if (! $include) {
|
||||
$directives[] = Directive::includeDirective();
|
||||
|
@ -174,9 +181,10 @@ class BuildSchema
|
|||
|
||||
$deprecated = array_reduce(
|
||||
$directives,
|
||||
static function ($hasDeprecated, $directive) {
|
||||
static function (bool $hasDeprecated, Directive $directive) : bool {
|
||||
return $hasDeprecated || $directive->name === 'deprecated';
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
if (! $deprecated) {
|
||||
$directives[] = Directive::deprecatedDirective();
|
||||
|
|
|
@ -21,9 +21,7 @@ use function is_string;
|
|||
* Similar to PHP array, but allows any type of data to act as key (including arrays, objects, scalars)
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
* Class MixedStore
|
||||
* (yet this should rarely be the case and should be avoided when possible)
|
||||
*/
|
||||
class MixedStore implements ArrayAccess
|
||||
{
|
||||
|
|
|
@ -7,13 +7,16 @@ namespace GraphQL\Utils;
|
|||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\DirectiveDefinitionNode;
|
||||
use GraphQL\Language\AST\DocumentNode;
|
||||
use GraphQL\Language\AST\EnumTypeExtensionNode;
|
||||
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
|
||||
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\ObjectTypeExtensionNode;
|
||||
use GraphQL\Language\AST\SchemaDefinitionNode;
|
||||
use GraphQL\Language\AST\SchemaTypeExtensionNode;
|
||||
use GraphQL\Language\AST\TypeDefinitionNode;
|
||||
use GraphQL\Language\AST\TypeExtensionNode;
|
||||
use GraphQL\Language\AST\UnionTypeExtensionNode;
|
||||
use GraphQL\Type\Definition\CustomScalarType;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
|
@ -66,6 +69,7 @@ class SchemaExtender
|
|||
|
||||
return $type->extensionASTNodes;
|
||||
}
|
||||
|
||||
return static::$typeExtensionsMap[$name] ?? null;
|
||||
}
|
||||
|
||||
|
@ -74,8 +78,8 @@ class SchemaExtender
|
|||
*/
|
||||
protected static function checkExtensionNode(Type $type, Node $node) : void
|
||||
{
|
||||
switch ($node->kind) {
|
||||
case NodeKind::OBJECT_TYPE_EXTENSION:
|
||||
switch (true) {
|
||||
case $node instanceof ObjectTypeExtensionNode:
|
||||
if (! ($type instanceof ObjectType)) {
|
||||
throw new Error(
|
||||
'Cannot extend non-object type "' . $type->name . '".',
|
||||
|
@ -83,7 +87,7 @@ class SchemaExtender
|
|||
);
|
||||
}
|
||||
break;
|
||||
case NodeKind::INTERFACE_TYPE_EXTENSION:
|
||||
case $node instanceof InterfaceTypeExtensionNode:
|
||||
if (! ($type instanceof InterfaceType)) {
|
||||
throw new Error(
|
||||
'Cannot extend non-interface type "' . $type->name . '".',
|
||||
|
@ -91,7 +95,7 @@ class SchemaExtender
|
|||
);
|
||||
}
|
||||
break;
|
||||
case NodeKind::ENUM_TYPE_EXTENSION:
|
||||
case $node instanceof EnumTypeExtensionNode:
|
||||
if (! ($type instanceof EnumType)) {
|
||||
throw new Error(
|
||||
'Cannot extend non-enum type "' . $type->name . '".',
|
||||
|
@ -99,7 +103,7 @@ class SchemaExtender
|
|||
);
|
||||
}
|
||||
break;
|
||||
case NodeKind::UNION_TYPE_EXTENSION:
|
||||
case $node instanceof UnionTypeExtensionNode:
|
||||
if (! ($type instanceof UnionType)) {
|
||||
throw new Error(
|
||||
'Cannot extend non-union type "' . $type->name . '".',
|
||||
|
@ -107,7 +111,7 @@ class SchemaExtender
|
|||
);
|
||||
}
|
||||
break;
|
||||
case NodeKind::INPUT_OBJECT_TYPE_EXTENSION:
|
||||
case $node instanceof InputObjectTypeExtensionNode:
|
||||
if (! ($type instanceof InputObjectType)) {
|
||||
throw new Error(
|
||||
'Cannot extend non-input object type "' . $type->name . '".',
|
||||
|
@ -284,6 +288,7 @@ class SchemaExtender
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $interfaces;
|
||||
}
|
||||
|
||||
|
@ -604,7 +609,9 @@ class SchemaExtender
|
|||
}
|
||||
|
||||
$schemaExtensionASTNodes = count($schemaExtensions) > 0
|
||||
? ($schema->extensionASTNodes ? array_merge($schema->extensionASTNodes, $schemaExtensions) : $schemaExtensions)
|
||||
? ($schema->extensionASTNodes
|
||||
? array_merge($schema->extensionASTNodes, $schemaExtensions)
|
||||
: $schemaExtensions)
|
||||
: $schema->extensionASTNodes;
|
||||
|
||||
$types = array_merge(
|
||||
|
|
|
@ -153,11 +153,8 @@ class SchemaPrinter
|
|||
}
|
||||
|
||||
$subscriptionType = $schema->getSubscriptionType();
|
||||
if ($subscriptionType && $subscriptionType->name !== 'Subscription') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return ! $subscriptionType || $subscriptionType->name === 'Subscription';
|
||||
}
|
||||
|
||||
private static function printDirective($directive, $options) : string
|
||||
|
@ -356,8 +353,8 @@ class SchemaPrinter
|
|||
private static function printObject(ObjectType $type, array $options) : string
|
||||
{
|
||||
$interfaces = $type->getInterfaces();
|
||||
$implementedInterfaces = ! empty($interfaces) ?
|
||||
' implements ' . implode(
|
||||
$implementedInterfaces = ! empty($interfaces)
|
||||
? ' implements ' . implode(
|
||||
' & ',
|
||||
array_map(
|
||||
static function ($i) {
|
||||
|
@ -365,7 +362,8 @@ class SchemaPrinter
|
|||
},
|
||||
$interfaces
|
||||
)
|
||||
) : '';
|
||||
)
|
||||
: '';
|
||||
|
||||
return self::printDescription($options, $type) .
|
||||
sprintf("type %s%s {\n%s\n}", $type->name, $implementedInterfaces, self::printFields($options, $type));
|
||||
|
|
|
@ -86,18 +86,12 @@ class TypeComparators
|
|||
|
||||
// If superType type is an abstract type, maybeSubType type may be a currently
|
||||
// possible object type.
|
||||
if (Type::isAbstractType($superType) &&
|
||||
return Type::isAbstractType($superType) &&
|
||||
$maybeSubType instanceof ObjectType &&
|
||||
$schema->isPossibleType(
|
||||
$superType,
|
||||
$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;
|
||||
}
|
||||
|
||||
/** @var $typeB ObjectType */
|
||||
// Determine if the latter type is a possible concrete type of the former.
|
||||
return $schema->isPossibleType($typeA, $typeB);
|
||||
}
|
||||
|
||||
if ($typeB instanceof AbstractType) {
|
||||
/** @var $typeA ObjectType */
|
||||
// Determine if the former type is a possible concrete type of the latter.
|
||||
return $schema->isPossibleType($typeB, $typeA);
|
||||
}
|
||||
|
|
|
@ -6,12 +6,21 @@ namespace GraphQL\Utils;
|
|||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Error\Warning;
|
||||
use GraphQL\Language\AST\ArgumentNode;
|
||||
use GraphQL\Language\AST\DirectiveNode;
|
||||
use GraphQL\Language\AST\EnumValueNode;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||
use GraphQL\Language\AST\InlineFragmentNode;
|
||||
use GraphQL\Language\AST\ListTypeNode;
|
||||
use GraphQL\Language\AST\ListValueNode;
|
||||
use GraphQL\Language\AST\NamedTypeNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\NonNullTypeNode;
|
||||
use GraphQL\Language\AST\ObjectFieldNode;
|
||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||
use GraphQL\Language\AST\SelectionSetNode;
|
||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||
use GraphQL\Type\Definition\CompositeType;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
|
@ -28,6 +37,7 @@ use GraphQL\Type\Definition\UnionType;
|
|||
use GraphQL\Type\Definition\WrappingType;
|
||||
use GraphQL\Type\Introspection;
|
||||
use GraphQL\Type\Schema;
|
||||
use SplStack;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_pop;
|
||||
|
@ -35,24 +45,21 @@ use function count;
|
|||
use function is_array;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Class TypeInfo
|
||||
*/
|
||||
class TypeInfo
|
||||
{
|
||||
/** @var Schema */
|
||||
private $schema;
|
||||
|
||||
/** @var \SplStack<OutputType> */
|
||||
/** @var SplStack<OutputType> */
|
||||
private $typeStack;
|
||||
|
||||
/** @var \SplStack<CompositeType> */
|
||||
/** @var SplStack<CompositeType> */
|
||||
private $parentTypeStack;
|
||||
|
||||
/** @var \SplStack<InputType> */
|
||||
/** @var SplStack<InputType> */
|
||||
private $inputTypeStack;
|
||||
|
||||
/** @var \SplStack<FieldDefinition> */
|
||||
/** @var SplStack<FieldDefinition> */
|
||||
private $fieldDefStack;
|
||||
|
||||
/** @var Directive */
|
||||
|
@ -155,6 +162,7 @@ class TypeInfo
|
|||
if (! $alreadyInMap) {
|
||||
$typeMap[$i] = $type;
|
||||
}
|
||||
|
||||
return $typeMap;
|
||||
}
|
||||
|
||||
|
@ -178,7 +186,7 @@ class TypeInfo
|
|||
$nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
|
||||
}
|
||||
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
|
||||
foreach ((array) $type->getFields() as $fieldName => $field) {
|
||||
foreach ($type->getFields() as $fieldName => $field) {
|
||||
if (! empty($field->args)) {
|
||||
$fieldArgTypes = array_map(
|
||||
static function (FieldArgument $arg) {
|
||||
|
@ -193,12 +201,12 @@ class TypeInfo
|
|||
}
|
||||
}
|
||||
if ($type instanceof InputObjectType) {
|
||||
foreach ((array) $type->getFields() as $fieldName => $field) {
|
||||
foreach ($type->getFields() as $fieldName => $field) {
|
||||
$nestedTypes[] = $field->getType();
|
||||
}
|
||||
}
|
||||
foreach ($nestedTypes as $type) {
|
||||
$typeMap = self::extractTypes($type, $typeMap);
|
||||
foreach ($nestedTypes as $nestedType) {
|
||||
$typeMap = self::extractTypes($nestedType, $typeMap);
|
||||
}
|
||||
|
||||
return $typeMap;
|
||||
|
@ -255,13 +263,13 @@ class TypeInfo
|
|||
// any assumptions of a valid schema to ensure runtime types are properly
|
||||
// checked before continuing since TypeInfo is used as part of validation
|
||||
// which occurs before guarantees of schema and document validity.
|
||||
switch ($node->kind) {
|
||||
case NodeKind::SELECTION_SET:
|
||||
switch (true) {
|
||||
case $node instanceof SelectionSetNode:
|
||||
$namedType = Type::getNamedType($this->getType());
|
||||
$this->parentTypeStack[] = Type::isCompositeType($namedType) ? $namedType : null;
|
||||
break;
|
||||
|
||||
case NodeKind::FIELD:
|
||||
case $node instanceof FieldNode:
|
||||
$parentType = $this->getParentType();
|
||||
$fieldDef = null;
|
||||
if ($parentType) {
|
||||
|
@ -275,11 +283,11 @@ class TypeInfo
|
|||
$this->typeStack[] = Type::isOutputType($fieldType) ? $fieldType : null;
|
||||
break;
|
||||
|
||||
case NodeKind::DIRECTIVE:
|
||||
case $node instanceof DirectiveNode:
|
||||
$this->directive = $schema->getDirective($node->name->value);
|
||||
break;
|
||||
|
||||
case NodeKind::OPERATION_DEFINITION:
|
||||
case $node instanceof OperationDefinitionNode:
|
||||
$type = null;
|
||||
if ($node->operation === 'query') {
|
||||
$type = $schema->getQueryType();
|
||||
|
@ -291,22 +299,24 @@ class TypeInfo
|
|||
$this->typeStack[] = Type::isOutputType($type) ? $type : null;
|
||||
break;
|
||||
|
||||
case NodeKind::INLINE_FRAGMENT:
|
||||
case NodeKind::FRAGMENT_DEFINITION:
|
||||
case $node instanceof InlineFragmentNode:
|
||||
case $node instanceof FragmentDefinitionNode:
|
||||
$typeConditionNode = $node->typeCondition;
|
||||
$outputType = $typeConditionNode ? self::typeFromAST(
|
||||
$schema,
|
||||
$typeConditionNode
|
||||
) : Type::getNamedType($this->getType());
|
||||
$outputType = $typeConditionNode
|
||||
? self::typeFromAST(
|
||||
$schema,
|
||||
$typeConditionNode
|
||||
)
|
||||
: Type::getNamedType($this->getType());
|
||||
$this->typeStack[] = Type::isOutputType($outputType) ? $outputType : null;
|
||||
break;
|
||||
|
||||
case NodeKind::VARIABLE_DEFINITION:
|
||||
case $node instanceof VariableDefinitionNode:
|
||||
$inputType = self::typeFromAST($schema, $node->type);
|
||||
$this->inputTypeStack[] = Type::isInputType($inputType) ? $inputType : null; // push
|
||||
break;
|
||||
|
||||
case NodeKind::ARGUMENT:
|
||||
case $node instanceof ArgumentNode:
|
||||
$fieldOrDirective = $this->getDirective() ?: $this->getFieldDef();
|
||||
$argDef = $argType = null;
|
||||
if ($fieldOrDirective) {
|
||||
|
@ -324,7 +334,7 @@ class TypeInfo
|
|||
$this->inputTypeStack[] = Type::isInputType($argType) ? $argType : null;
|
||||
break;
|
||||
|
||||
case NodeKind::LST:
|
||||
case $node instanceof ListValueNode:
|
||||
$listType = Type::getNullableType($this->getInputType());
|
||||
$itemType = $listType instanceof ListOfType
|
||||
? $listType->getWrappedType()
|
||||
|
@ -332,7 +342,7 @@ class TypeInfo
|
|||
$this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null;
|
||||
break;
|
||||
|
||||
case NodeKind::OBJECT_FIELD:
|
||||
case $node instanceof ObjectFieldNode:
|
||||
$objectType = Type::getNamedType($this->getInputType());
|
||||
$fieldType = null;
|
||||
$inputFieldType = null;
|
||||
|
@ -344,7 +354,7 @@ class TypeInfo
|
|||
$this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null;
|
||||
break;
|
||||
|
||||
case NodeKind::ENUM:
|
||||
case $node instanceof EnumValueNode:
|
||||
$enumType = Type::getNamedType($this->getInputType());
|
||||
$enumValue = null;
|
||||
if ($enumType instanceof EnumType) {
|
||||
|
@ -458,37 +468,37 @@ class TypeInfo
|
|||
|
||||
public function leave(Node $node)
|
||||
{
|
||||
switch ($node->kind) {
|
||||
case NodeKind::SELECTION_SET:
|
||||
switch (true) {
|
||||
case $node instanceof SelectionSetNode:
|
||||
array_pop($this->parentTypeStack);
|
||||
break;
|
||||
|
||||
case NodeKind::FIELD:
|
||||
case $node instanceof FieldNode:
|
||||
array_pop($this->fieldDefStack);
|
||||
array_pop($this->typeStack);
|
||||
break;
|
||||
|
||||
case NodeKind::DIRECTIVE:
|
||||
case $node instanceof DirectiveNode:
|
||||
$this->directive = null;
|
||||
break;
|
||||
|
||||
case NodeKind::OPERATION_DEFINITION:
|
||||
case NodeKind::INLINE_FRAGMENT:
|
||||
case NodeKind::FRAGMENT_DEFINITION:
|
||||
case $node instanceof OperationDefinitionNode:
|
||||
case $node instanceof InlineFragmentNode:
|
||||
case $node instanceof FragmentDefinitionNode:
|
||||
array_pop($this->typeStack);
|
||||
break;
|
||||
case NodeKind::VARIABLE_DEFINITION:
|
||||
case $node instanceof VariableDefinitionNode:
|
||||
array_pop($this->inputTypeStack);
|
||||
break;
|
||||
case NodeKind::ARGUMENT:
|
||||
case $node instanceof ArgumentNode:
|
||||
$this->argument = null;
|
||||
array_pop($this->inputTypeStack);
|
||||
break;
|
||||
case NodeKind::LST:
|
||||
case NodeKind::OBJECT_FIELD:
|
||||
case $node instanceof ListValueNode:
|
||||
case $node instanceof ObjectFieldNode:
|
||||
array_pop($this->inputTypeStack);
|
||||
break;
|
||||
case NodeKind::ENUM:
|
||||
case $node instanceof EnumValueNode:
|
||||
$this->enumValue = null;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -264,8 +264,8 @@ class Utils
|
|||
$grouped = [];
|
||||
foreach ($traversable as $key => $value) {
|
||||
$newKeys = (array) $keyFn($value, $key);
|
||||
foreach ($newKeys as $key) {
|
||||
$grouped[$key][] = $value;
|
||||
foreach ($newKeys as $newKey) {
|
||||
$grouped[$newKey][] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -199,7 +199,7 @@ class Value
|
|||
}
|
||||
|
||||
$suggestions = Utils::suggestionList(
|
||||
$fieldName,
|
||||
(string) $fieldName,
|
||||
array_keys($fields)
|
||||
);
|
||||
$didYouMean = $suggestions
|
||||
|
|
|
@ -52,6 +52,6 @@ class DisableIntrospection extends QuerySecurityRule
|
|||
|
||||
protected function isEnabled()
|
||||
{
|
||||
return $this->isEnabled !== static::DISABLED;
|
||||
return $this->isEnabled !== self::DISABLED;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ namespace GraphQL\Validator\Rules;
|
|||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\ArgumentNode;
|
||||
use GraphQL\Language\AST\DirectiveNode;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
|
@ -34,7 +36,7 @@ class KnownArgumentNames extends ValidationRule
|
|||
}
|
||||
|
||||
$argumentOf = $ancestors[count($ancestors) - 1];
|
||||
if ($argumentOf->kind === NodeKind::FIELD) {
|
||||
if ($argumentOf instanceof FieldNode) {
|
||||
$fieldDef = $context->getFieldDef();
|
||||
$parentType = $context->getParentType();
|
||||
if ($fieldDef && $parentType) {
|
||||
|
@ -56,7 +58,7 @@ class KnownArgumentNames extends ValidationRule
|
|||
[$node]
|
||||
));
|
||||
}
|
||||
} elseif ($argumentOf->kind === NodeKind::DIRECTIVE) {
|
||||
} elseif ($argumentOf instanceof DirectiveNode) {
|
||||
$directive = $context->getDirective();
|
||||
if ($directive) {
|
||||
$context->reportError(new Error(
|
||||
|
|
|
@ -7,10 +7,31 @@ namespace GraphQL\Validator\Rules;
|
|||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\DirectiveDefinitionNode;
|
||||
use GraphQL\Language\AST\DirectiveNode;
|
||||
use GraphQL\Language\AST\EnumTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\EnumTypeExtensionNode;
|
||||
use GraphQL\Language\AST\EnumValueDefinitionNode;
|
||||
use GraphQL\Language\AST\FieldDefinitionNode;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||
use GraphQL\Language\AST\InlineFragmentNode;
|
||||
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
|
||||
use GraphQL\Language\AST\InputValueDefinitionNode;
|
||||
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\ObjectTypeExtensionNode;
|
||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\ScalarTypeExtensionNode;
|
||||
use GraphQL\Language\AST\SchemaDefinitionNode;
|
||||
use GraphQL\Language\AST\SchemaTypeExtensionNode;
|
||||
use GraphQL\Language\AST\UnionTypeDefinitionNode;
|
||||
use GraphQL\Language\AST\UnionTypeExtensionNode;
|
||||
use GraphQL\Language\DirectiveLocation;
|
||||
use GraphQL\Validator\ValidationContext;
|
||||
use function array_map;
|
||||
|
@ -96,8 +117,8 @@ class KnownDirectives extends ValidationRule
|
|||
private function getDirectiveLocationForASTPath(array $ancestors)
|
||||
{
|
||||
$appliedTo = $ancestors[count($ancestors) - 1];
|
||||
switch ($appliedTo->kind) {
|
||||
case NodeKind::OPERATION_DEFINITION:
|
||||
switch (true) {
|
||||
case $appliedTo instanceof OperationDefinitionNode:
|
||||
switch ($appliedTo->operation) {
|
||||
case 'query':
|
||||
return DirectiveLocation::QUERY;
|
||||
|
@ -107,40 +128,40 @@ class KnownDirectives extends ValidationRule
|
|||
return DirectiveLocation::SUBSCRIPTION;
|
||||
}
|
||||
break;
|
||||
case NodeKind::FIELD:
|
||||
case $appliedTo instanceof FieldNode:
|
||||
return DirectiveLocation::FIELD;
|
||||
case NodeKind::FRAGMENT_SPREAD:
|
||||
case $appliedTo instanceof FragmentSpreadNode:
|
||||
return DirectiveLocation::FRAGMENT_SPREAD;
|
||||
case NodeKind::INLINE_FRAGMENT:
|
||||
case $appliedTo instanceof InlineFragmentNode:
|
||||
return DirectiveLocation::INLINE_FRAGMENT;
|
||||
case NodeKind::FRAGMENT_DEFINITION:
|
||||
case $appliedTo instanceof FragmentDefinitionNode:
|
||||
return DirectiveLocation::FRAGMENT_DEFINITION;
|
||||
case NodeKind::SCHEMA_DEFINITION:
|
||||
case NodeKind::SCHEMA_EXTENSION:
|
||||
case $appliedTo instanceof SchemaDefinitionNode:
|
||||
case $appliedTo instanceof SchemaTypeExtensionNode:
|
||||
return DirectiveLocation::SCHEMA;
|
||||
case NodeKind::SCALAR_TYPE_DEFINITION:
|
||||
case NodeKind::SCALAR_TYPE_EXTENSION:
|
||||
case $appliedTo instanceof ScalarTypeDefinitionNode:
|
||||
case $appliedTo instanceof ScalarTypeExtensionNode:
|
||||
return DirectiveLocation::SCALAR;
|
||||
case NodeKind::OBJECT_TYPE_DEFINITION:
|
||||
case NodeKind::OBJECT_TYPE_EXTENSION:
|
||||
case $appliedTo instanceof ObjectTypeDefinitionNode:
|
||||
case $appliedTo instanceof ObjectTypeExtensionNode:
|
||||
return DirectiveLocation::OBJECT;
|
||||
case NodeKind::FIELD_DEFINITION:
|
||||
case $appliedTo instanceof FieldDefinitionNode:
|
||||
return DirectiveLocation::FIELD_DEFINITION;
|
||||
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
||||
case NodeKind::INTERFACE_TYPE_EXTENSION:
|
||||
case $appliedTo instanceof InterfaceTypeDefinitionNode:
|
||||
case $appliedTo instanceof InterfaceTypeExtensionNode:
|
||||
return DirectiveLocation::IFACE;
|
||||
case NodeKind::UNION_TYPE_DEFINITION:
|
||||
case NodeKind::UNION_TYPE_EXTENSION:
|
||||
case $appliedTo instanceof UnionTypeDefinitionNode:
|
||||
case $appliedTo instanceof UnionTypeExtensionNode:
|
||||
return DirectiveLocation::UNION;
|
||||
case NodeKind::ENUM_TYPE_DEFINITION:
|
||||
case NodeKind::ENUM_TYPE_EXTENSION:
|
||||
case $appliedTo instanceof EnumTypeDefinitionNode:
|
||||
case $appliedTo instanceof EnumTypeExtensionNode:
|
||||
return DirectiveLocation::ENUM;
|
||||
case NodeKind::ENUM_VALUE_DEFINITION:
|
||||
case $appliedTo instanceof EnumValueDefinitionNode:
|
||||
return DirectiveLocation::ENUM_VALUE;
|
||||
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
||||
case NodeKind::INPUT_OBJECT_TYPE_EXTENSION:
|
||||
case $appliedTo instanceof InputObjectTypeDefinitionNode:
|
||||
case $appliedTo instanceof InputObjectTypeExtensionNode:
|
||||
return DirectiveLocation::INPUT_OBJECT;
|
||||
case NodeKind::INPUT_VALUE_DEFINITION:
|
||||
case $appliedTo instanceof InputValueDefinitionNode:
|
||||
$parentNode = $ancestors[count($ancestors) - 3];
|
||||
|
||||
return $parentNode instanceof InputObjectTypeDefinitionNode
|
||||
|
|
|
@ -30,7 +30,7 @@ class LoneAnonymousOperation extends ValidationRule
|
|||
$tmp = Utils::filter(
|
||||
$node->definitions,
|
||||
static function (Node $definition) {
|
||||
return $definition->kind === NodeKind::OPERATION_DEFINITION;
|
||||
return $definition instanceof OperationDefinitionNode;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -19,12 +19,14 @@ class LoneSchemaDefinition extends ValidationRule
|
|||
public function getVisitor(ValidationContext $context)
|
||||
{
|
||||
$oldSchema = $context->getSchema();
|
||||
$alreadyDefined = $oldSchema !== null ? (
|
||||
$oldSchema->getAstNode() ||
|
||||
$oldSchema->getQueryType() ||
|
||||
$oldSchema->getMutationType() ||
|
||||
$oldSchema->getSubscriptionType()
|
||||
) : false;
|
||||
$alreadyDefined = $oldSchema !== null
|
||||
? (
|
||||
$oldSchema->getAstNode() ||
|
||||
$oldSchema->getQueryType() ||
|
||||
$oldSchema->getMutationType() ||
|
||||
$oldSchema->getSubscriptionType()
|
||||
)
|
||||
: false;
|
||||
|
||||
$schemaDefinitionsCount = 0;
|
||||
|
||||
|
@ -32,6 +34,7 @@ class LoneSchemaDefinition extends ValidationRule
|
|||
NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount) {
|
||||
if ($alreadyDefined !== false) {
|
||||
$context->reportError(new Error('Cannot define a new schema within a schema extension.', $node));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@ use GraphQL\Validator\ValidationContext;
|
|||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Class NoUndefinedVariables
|
||||
*
|
||||
* A GraphQL operation is only valid if all variables encountered, both directly
|
||||
* and via fragment spreads, are defined by that operation.
|
||||
*/
|
||||
|
|
|
@ -473,24 +473,24 @@ class OverlappingFieldsCanBeMerged extends ValidationRule
|
|||
private function doTypesConflict(OutputType $type1, OutputType $type2)
|
||||
{
|
||||
if ($type1 instanceof ListOfType) {
|
||||
return $type2 instanceof ListOfType ?
|
||||
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
|
||||
true;
|
||||
return $type2 instanceof ListOfType
|
||||
? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
|
||||
: true;
|
||||
}
|
||||
if ($type2 instanceof ListOfType) {
|
||||
return $type1 instanceof ListOfType ?
|
||||
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
|
||||
true;
|
||||
return $type1 instanceof ListOfType
|
||||
? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
|
||||
: true;
|
||||
}
|
||||
if ($type1 instanceof NonNull) {
|
||||
return $type2 instanceof NonNull ?
|
||||
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
|
||||
true;
|
||||
return $type2 instanceof NonNull
|
||||
? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
|
||||
: true;
|
||||
}
|
||||
if ($type2 instanceof NonNull) {
|
||||
return $type1 instanceof NonNull ?
|
||||
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) :
|
||||
true;
|
||||
return $type1 instanceof NonNull
|
||||
? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
|
||||
: true;
|
||||
}
|
||||
if (Type::isLeafType($type1) || Type::isLeafType($type2)) {
|
||||
return $type1 !== $type2;
|
||||
|
|
|
@ -66,13 +66,15 @@ class ProvidedRequiredArgumentsOnDirectives extends ValidationRule
|
|||
}
|
||||
|
||||
$requiredArgsMap[$def->name->value] = Utils::keyMap(
|
||||
$arguments ? array_filter($arguments, static function (Node $argument) : bool {
|
||||
return $argument instanceof NonNullTypeNode &&
|
||||
(
|
||||
! isset($argument->defaultValue) ||
|
||||
$argument->defaultValue === null
|
||||
);
|
||||
}) : [],
|
||||
$arguments
|
||||
? array_filter($arguments, static function (Node $argument) : bool {
|
||||
return $argument instanceof NonNullTypeNode &&
|
||||
(
|
||||
! isset($argument->defaultValue) ||
|
||||
$argument->defaultValue === null
|
||||
);
|
||||
})
|
||||
: [],
|
||||
static function (NamedTypeNode $argument) : string {
|
||||
return $argument->name->value;
|
||||
}
|
||||
|
|
|
@ -110,9 +110,8 @@ class QueryComplexity extends QuerySecurityRule
|
|||
|
||||
private function nodeComplexity(Node $node, $complexity = 0)
|
||||
{
|
||||
switch ($node->kind) {
|
||||
case NodeKind::FIELD:
|
||||
/** @var FieldNode $node */
|
||||
switch (true) {
|
||||
case $node instanceof FieldNode:
|
||||
// default values
|
||||
$args = [];
|
||||
$complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN;
|
||||
|
@ -143,16 +142,14 @@ class QueryComplexity extends QuerySecurityRule
|
|||
$complexity += call_user_func_array($complexityFn, [$childrenComplexity, $args]);
|
||||
break;
|
||||
|
||||
case NodeKind::INLINE_FRAGMENT:
|
||||
/** @var InlineFragmentNode $node */
|
||||
case $node instanceof InlineFragmentNode:
|
||||
// node has children?
|
||||
if (isset($node->selectionSet)) {
|
||||
$complexity = $this->fieldComplexity($node, $complexity);
|
||||
}
|
||||
break;
|
||||
|
||||
case NodeKind::FRAGMENT_SPREAD:
|
||||
/** @var FragmentSpreadNode $node */
|
||||
case $node instanceof FragmentSpreadNode:
|
||||
$fragment = $this->getFragment($node);
|
||||
|
||||
if ($fragment !== null) {
|
||||
|
@ -206,10 +203,12 @@ class QueryComplexity extends QuerySecurityRule
|
|||
$directive = Directive::includeDirective();
|
||||
/** @var bool $directiveArgsIf */
|
||||
$directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues)['if'];
|
||||
|
||||
return ! $directiveArgsIf;
|
||||
}
|
||||
$directive = Directive::skipDirective();
|
||||
$directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues);
|
||||
|
||||
return $directiveArgsIf['if'];
|
||||
}
|
||||
}
|
||||
|
@ -282,6 +281,6 @@ class QueryComplexity extends QuerySecurityRule
|
|||
|
||||
protected function isEnabled()
|
||||
{
|
||||
return $this->getMaxQueryComplexity() !== static::DISABLED;
|
||||
return $this->getMaxQueryComplexity() !== self::DISABLED;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,9 @@ declare(strict_types=1);
|
|||
namespace GraphQL\Validator\Rules;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||
use GraphQL\Language\AST\InlineFragmentNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||
|
@ -57,9 +60,8 @@ class QueryDepth extends QuerySecurityRule
|
|||
|
||||
private function nodeDepth(Node $node, $depth = 0, $maxDepth = 0)
|
||||
{
|
||||
switch ($node->kind) {
|
||||
case NodeKind::FIELD:
|
||||
/** @var FieldNode $node */
|
||||
switch (true) {
|
||||
case $node instanceof FieldNode:
|
||||
// node has children?
|
||||
if ($node->selectionSet !== null) {
|
||||
// update maxDepth if needed
|
||||
|
@ -70,16 +72,14 @@ class QueryDepth extends QuerySecurityRule
|
|||
}
|
||||
break;
|
||||
|
||||
case NodeKind::INLINE_FRAGMENT:
|
||||
/** @var InlineFragmentNode $node */
|
||||
case $node instanceof InlineFragmentNode:
|
||||
// node has children?
|
||||
if ($node->selectionSet !== null) {
|
||||
$maxDepth = $this->fieldDepth($node, $depth, $maxDepth);
|
||||
}
|
||||
break;
|
||||
|
||||
case NodeKind::FRAGMENT_SPREAD:
|
||||
/** @var FragmentSpreadNode $node */
|
||||
case $node instanceof FragmentSpreadNode:
|
||||
$fragment = $this->getFragment($node);
|
||||
|
||||
if ($fragment !== null) {
|
||||
|
@ -98,8 +98,6 @@ class QueryDepth extends QuerySecurityRule
|
|||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
|
@ -115,6 +113,6 @@ class QueryDepth extends QuerySecurityRule
|
|||
|
||||
protected function isEnabled()
|
||||
{
|
||||
return $this->getMaxQueryDepth() !== static::DISABLED;
|
||||
return $this->getMaxQueryDepth() !== self::DISABLED;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue