Compare commits

..

No commits in common. "master" and "v0.13.0" have entirely different histories.

140 changed files with 1039 additions and 2880 deletions

1
.gitattributes vendored
View file

@ -2,7 +2,6 @@
* text eol=lf
/benchmarks export-ignore
/tests export-ignore
/examples export-ignore
/tools export-ignore
.gitattributes export-ignore
.gitignore export-ignore

1
.gitignore vendored
View file

@ -4,4 +4,3 @@ composer.phar
phpcs.xml
phpstan.neon
vendor/
/.idea

View file

@ -20,7 +20,7 @@ build:
tools:
external_code_coverage:
timeout: 900
timeout: 600
build_failure_conditions:
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed

View file

@ -4,16 +4,12 @@ language: php
php:
- 7.1
- 7.2
- 7.3
- 7.4snapshot
- nightly
env:
matrix:
- EXECUTOR= DEPENDENCIES=--prefer-lowest
- EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest
- EXECUTOR=
- EXECUTOR=coroutine
- EXECUTOR=
cache:
@ -30,13 +26,13 @@ script: ./vendor/bin/phpunit --group default,ReactPromise
jobs:
allow_failures:
- php: 7.4snapshot
- php: nightly
include:
- stage: Test
env: DEPENDENCIES=low
install:
- travis_retry composer update --prefer-dist {$DEPENDENCIES}
- travis_retry composer update --prefer-dist --prefer-lowest
- stage: Test
env: COVERAGE
@ -44,12 +40,10 @@ jobs:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
script:
- ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor.cov
- EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor-coroutine.cov
- ./vendor/bin/phpunit --coverage-clover clover.xml
after_script:
- ./vendor/bin/phpcov merge /tmp/coverage --clover /tmp/clover.xml
- wget https://github.com/scrutinizer-ci/ocular/releases/download/1.5.2/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover /tmp/clover.xml
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover clover.xml
- stage: Code Quality
php: 7.1
@ -63,4 +57,3 @@ jobs:
env: STATIC_ANALYSIS
install: travis_retry composer install --prefer-dist
script: composer static-analysis

View file

@ -1,26 +1,4 @@
# 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
- Minor tweaks and fixes
## v0.13.0
This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details.

View file

@ -1,6 +1,7 @@
# 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.
@ -10,14 +11,12 @@ 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`.
* 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.
* Check your changes using `composer check-all`
* Send a pull request
## Using GraphQL PHP from a Git checkout
```sh
git clone https://github.com/webonyx/graphql-php.git
cd graphql-php
composer install
```
@ -30,26 +29,28 @@ 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
The coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard).
Coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard). To run the inspection:
Run the inspections:
```sh
./vendor/bin/phpcs
```
Apply automatic code style fixes:
Auto-fixing:
```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](https://github.com/phpbench/phpbench).
Benchmarks are run via phpbench:
```sh
./vendor/bin/phpbench run .
```

View file

@ -14,7 +14,7 @@ composer require webonyx/graphql-php
```
## Documentation
Full documentation is available on the [Documentation site](https://webonyx.github.io/graphql-php/) as well
Full documentation is available on the [Documentation site](http://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,29 +24,11 @@ by the Facebook engineering team.
There are several ready examples in the [examples](examples/) folder of the distribution with specific
README file per example.
## Contributors
## Contribute
Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute.
This project exists thanks to [all the people](https://github.com/webonyx/graphql-php/graphs/contributors) who contribute. [[Contribute](CONTRIBUTING.md)].
## Old README.md
Here is a [link to the old README.md](https://github.com/webonyx/graphql-php/blob/v0.9.14/README.md).
## 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).
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).

View file

@ -1,8 +1,3 @@
## 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
@ -83,9 +78,6 @@ Parser::parse($source, [ 'allowLegacySDLImplementsInterfaces' => true])
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
- `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`)
### Breaking: new constructors
`GraphQL\Type\Definition\ResolveInfo` now takes 10 arguments instead of one array.
## Upgrade v0.11.x > v0.12.x

View file

@ -152,7 +152,7 @@ class SchemaGenerator
];
}
public function resolveField($objectValue, $args, $context, $resolveInfo)
public function resolveField($value, $args, $context, $resolveInfo)
{
return $resolveInfo->fieldName . '-value';
}

View file

@ -9,17 +9,16 @@
"API"
],
"require": {
"php": "^7.1||^8.0",
"php": "^7.1",
"ext-json": "*",
"ext-mbstring": "*"
},
"require-dev": {
"doctrine/coding-standard": "^6.0",
"doctrine/coding-standard": "^5.0",
"phpbench/phpbench": "^0.14.0",
"phpstan/phpstan": "^0.11.12",
"phpstan/phpstan-phpunit": "^0.11.2",
"phpstan/phpstan-strict-rules": "^0.11.1",
"phpunit/phpcov": "^5.0",
"phpstan/phpstan": "0.10.5",
"phpstan/phpstan-phpunit": "0.10.0",
"phpstan/phpstan-strict-rules": "0.10.1",
"phpunit/phpunit": "^7.2",
"psr/http-message": "^1.0",
"react/promise": "2.*"

View file

@ -1,26 +1,22 @@
# Integrations
* [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
* [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)
# GraphQL PHP Tools
* [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)_
* 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.
# General GraphQL Tools
* [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
* [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).

View file

@ -103,25 +103,23 @@ for a field you simply override this default resolver.
**graphql-php** provides following default field resolver:
```php
<?php
function defaultFieldResolver($objectValue, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
function defaultFieldResolver($source, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
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};
}
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};
}
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).
@ -163,6 +161,7 @@ $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

View file

@ -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 }"
}
]
```

View file

@ -54,8 +54,8 @@ $queryType = new ObjectType([
'args' => [
'message' => Type::nonNull(Type::string()),
],
'resolve' => function ($rootValue, $args) {
return $rootValue['prefix'] . $args['message'];
'resolve' => function ($root, $args) {
return $root['prefix'] . $args['message'];
}
],
],

View file

@ -7,7 +7,7 @@
# About GraphQL
GraphQL is a modern way to build HTTP APIs consumed by the web and mobile clients.
It is intended to be an alternative to REST and SOAP APIs (even for **existing applications**).
It is intended to be a replacement for REST and SOAP APIs (even for **existing applications**).
GraphQL itself is a [specification](https://github.com/facebook/graphql) designed by Facebook
engineers. Various implementations of this specification were written
@ -44,8 +44,8 @@ existing PHP frameworks, add support for Relay, etc.
## Current Status
The first version of this library (v0.1) was released on August 10th 2015.
The current version supports all features described by GraphQL specification
as well as some experimental features like
The current version (v0.10) supports all features described by GraphQL specification
(including April 2016 add-ons) as well as some experimental features like
[Schema Language parser](type-system/type-language.md) and
[Schema printer](reference.md#graphqlutilsschemaprinter).

View file

@ -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 object value with the field's name).
* value on the source 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 4th argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md).
Passed as 3rd argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md).
**Class Props:**
```php

View file

@ -158,7 +158,7 @@ $heroType = new ObjectType([
'args' => [
'episode' => Type::nonNull($enumType)
],
'resolve' => function($hero, $args) {
'resolve' => function($_value, $args) {
return $args['episode'] === 5 ? true : false;
}
]

View file

@ -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. Use the internal value if specifying a default for an **enum** type
defaultValue | `scalar` | Default value of this input field
# 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' => $filters,
'type' => Type::nonNull($filters),
'defaultValue' => [
'popular' => true
]

View file

@ -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($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
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
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. Use the internal value if specifying a default for an **enum** type
defaultValue | `scalar` | Default value for this argument
# Shorthand field definitions
Fields can be also defined in **shorthand** notation (with only **name** and **type** options):

View file

@ -62,7 +62,7 @@ $mutationType = new ObjectType([
'episode' => $episodeEnum,
'review' => $reviewInputObject
],
'resolve' => function($rootValue, $args) {
'resolve' => function($val, $args) {
// TODOC
}
]

View file

@ -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) . ";\n");
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true));
} else {
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
}

View file

@ -19,8 +19,8 @@ try {
'args' => [
'message' => ['type' => Type::string()],
],
'resolve' => function ($rootValue, $args) {
return $rootValue['prefix'] . $args['message'];
'resolve' => function ($root, $args) {
return $root['prefix'] . $args['message'];
}
],
],
@ -35,7 +35,7 @@ try {
'x' => ['type' => Type::int()],
'y' => ['type' => Type::int()],
],
'resolve' => function ($calc, $args) {
'resolve' => function ($root, $args) {
return $args['x'] + $args['y'];
},
],

View file

@ -35,12 +35,12 @@ class CommentType extends ObjectType
Types::htmlField('body')
];
},
'resolveField' => function($comment, $args, $context, ResolveInfo $info) {
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) {
return $this->{$method}($comment, $args, $context, $info);
return $this->{$method}($value, $args, $context, $info);
} else {
return $comment->{$info->fieldName};
return $value->{$info->fieldName};
}
}
];

View file

@ -57,8 +57,8 @@ class QueryType extends ObjectType
],
'hello' => Type::string()
],
'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) {
return $this->{$info->fieldName}($rootValue, $args, $context, $info);
'resolveField' => function($val, $args, $context, ResolveInfo $info) {
return $this->{$info->fieldName}($val, $args, $context, $info);
}
];
parent::__construct($config);

View file

@ -75,12 +75,12 @@ class StoryType extends ObjectType
'interfaces' => [
Types::node()
],
'resolveField' => function($story, $args, $context, ResolveInfo $info) {
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) {
return $this->{$method}($story, $args, $context, $info);
return $this->{$method}($value, $args, $context, $info);
} else {
return $story->{$info->fieldName};
return $value->{$info->fieldName};
}
}
];

View file

@ -44,12 +44,12 @@ class UserType extends ObjectType
'interfaces' => [
Types::node()
],
'resolveField' => function($user, $args, $context, ResolveInfo $info) {
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) {
return $this->{$method}($user, $args, $context, $info);
return $this->{$method}($value, $args, $context, $info);
} else {
return $user->{$info->fieldName};
return $value->{$info->fieldName};
}
}
];

View file

@ -1,12 +1,12 @@
<?php
interface Resolver {
public function resolve($rootValue, $args, $context);
public function resolve($root, $args, $context);
}
class Addition implements Resolver
{
public function resolve($rootValue, $args, $context)
public function resolve($root, $args, $context)
{
return $args['x'] + $args['y'];
}
@ -14,22 +14,22 @@ class Addition implements Resolver
class Echoer implements Resolver
{
public function resolve($rootValue, $args, $context)
public function resolve($root, $args, $context)
{
return $rootValue['prefix'].$args['message'];
return $root['prefix'].$args['message'];
}
}
return [
'sum' => function($rootValue, $args, $context) {
'sum' => function($root, $args, $context) {
$sum = new Addition();
return $sum->resolve($rootValue, $args, $context);
return $sum->resolve($root, $args, $context);
},
'echo' => function($rootValue, $args, $context) {
'echo' => function($root, $args, $context) {
$echo = new Echoer();
return $echo->resolve($rootValue, $args, $context);
return $echo->resolve($root, $args, $context);
},
'prefix' => 'You said: ',
];

View file

@ -19,8 +19,8 @@ try {
'args' => [
'message' => ['type' => Type::string()],
],
'resolve' => function ($rootValue, $args) {
return $rootValue['prefix'] . $args['message'];
'resolve' => function ($root, $args) {
return $root['prefix'] . $args['message'];
}
],
],
@ -35,7 +35,7 @@ try {
'x' => ['type' => Type::int()],
'y' => ['type' => Type::int()],
],
'resolve' => function ($calc, $args) {
'resolve' => function ($root, $args) {
return $args['x'] + $args['y'];
},
],

View file

@ -8,8 +8,6 @@ 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

View file

@ -11,7 +11,7 @@ use Throwable;
class Deferred
{
/** @var SplQueue|null */
/** @var SplQueue */
private static $queue;
/** @var callable */
@ -20,6 +20,21 @@ class Deferred
/** @var SyncPromise */
public $promise;
public static function getQueue()
{
return self::$queue ?: self::$queue = new SplQueue();
}
public static function runQueue()
{
$q = self::$queue;
while ($q && ! $q->isEmpty()) {
/** @var self $dfd */
$dfd = $q->dequeue();
$dfd->run();
}
}
public function __construct(callable $callback)
{
$this->callback = $callback;
@ -27,25 +42,6 @@ class Deferred
self::getQueue()->enqueue($this);
}
public static function getQueue() : SplQueue
{
if (self::$queue === null) {
self::$queue = new SplQueue();
}
return self::$queue;
}
public static function runQueue() : void
{
$queue = self::getQueue();
while (! $queue->isEmpty()) {
/** @var self $dequeuedNodeValue */
$dequeuedNodeValue = $queue->dequeue();
$dequeuedNodeValue->run();
}
}
public function then($onFulfilled = null, $onRejected = null)
{
return $this->promise->then($onFulfilled, $onRejected);

View file

@ -122,13 +122,13 @@ class Error extends Exception implements JsonSerializable, ClientAware
if ($previous instanceof ClientAware) {
$this->isClientSafe = $previous->isClientSafe();
$this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL;
$this->category = $previous->getCategory() ?: static::CATEGORY_INTERNAL;
} elseif ($previous) {
$this->isClientSafe = false;
$this->category = self::CATEGORY_INTERNAL;
$this->category = static::CATEGORY_INTERNAL;
} else {
$this->isClientSafe = true;
$this->category = self::CATEGORY_GRAPHQL;
$this->category = static::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;

View file

@ -231,7 +231,7 @@ class FormattedError
*
* @param mixed[] $formattedError
* @param Throwable $e
* @param bool|int $debug
* @param bool $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|int $debug
* @param bool $debug
*
* @return callable|callable
*/

View file

@ -7,6 +7,8 @@ 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

View file

@ -7,6 +7,8 @@ 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

View file

@ -4,8 +4,6 @@ declare(strict_types=1);
namespace GraphQL\Error;
use GraphQL\Exception\InvalidArgument;
use function is_int;
use function trigger_error;
use const E_USER_WARNING;
@ -17,12 +15,12 @@ use const E_USER_WARNING;
*/
final class Warning
{
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;
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;
/** @var int */
private static $enableWarnings = self::ALL;
@ -39,7 +37,7 @@ final class Warning
*
* @api
*/
public static function setWarningHandler(?callable $warningHandler = null) : void
public static function setWarningHandler(?callable $warningHandler = null)
{
self::$warningHandler = $warningHandler;
}
@ -56,16 +54,16 @@ final class Warning
*
* @api
*/
public static function suppress($suppress = true) : void
public static function suppress($suppress = true)
{
if ($suppress === true) {
self::$enableWarnings = 0;
} elseif ($suppress === false) {
self::$enableWarnings = self::ALL;
} elseif (is_int($suppress)) {
self::$enableWarnings &= ~$suppress;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress);
$suppress = (int) $suppress;
self::$enableWarnings &= ~$suppress;
}
}
@ -81,22 +79,22 @@ final class Warning
*
* @api
*/
public static function enable($enable = true) : void
public static function enable($enable = true)
{
if ($enable === true) {
self::$enableWarnings = self::ALL;
} elseif ($enable === false) {
self::$enableWarnings = 0;
} elseif (is_int($enable)) {
self::$enableWarnings |= $enable;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable);
$enable = (int) $enable;
self::$enableWarnings |= $enable;
}
}
public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
public static function warnOnce($errorMessage, $warningId, $messageLevel = null)
{
if (self::$warningHandler !== null) {
if (self::$warningHandler) {
$fn = self::$warningHandler;
$fn($errorMessage, $warningId);
} elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) {
@ -105,9 +103,9 @@ final class Warning
}
}
public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
public static function warn($errorMessage, $warningId, $messageLevel = null)
{
if (self::$warningHandler !== null) {
if (self::$warningHandler) {
$fn = self::$warningHandler;
$fn($errorMessage, $warningId);
} elseif ((self::$enableWarnings & $warningId) > 0) {

View file

@ -1,20 +0,0 @@
<?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)));
}
}

View file

@ -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 $promiseAdapter;
public $promises;
public function __construct(
$schema,
$fragments,
$rootValue,
$root,
$contextValue,
$operation,
$variableValues,
$variables,
$errors,
$fieldResolver,
$promiseAdapter
) {
$this->schema = $schema;
$this->fragments = $fragments;
$this->rootValue = $rootValue;
$this->rootValue = $root;
$this->contextValue = $contextValue;
$this->operation = $operation;
$this->variableValues = $variableValues;
$this->variableValues = $variables;
$this->errors = $errors ?: [];
$this->fieldResolver = $fieldResolver;
$this->promiseAdapter = $promiseAdapter;
$this->promises = $promiseAdapter;
}
public function addError(Error $error)

View file

@ -154,7 +154,7 @@ class ExecutionResult implements JsonSerializable
}
if (! empty($this->extensions)) {
$result['extensions'] = $this->extensions;
$result['extensions'] = (array) $this->extensions;
}
return $result;

View file

@ -74,7 +74,7 @@ class Executor
* execution are collected in `$result->errors`.
*
* @param mixed|null $rootValue
* @param mixed|null $contextValue
* @param mixed[]|null $contextValue
* @param mixed[]|ArrayAccess|null $variableValues
* @param string|null $operationName
*
@ -119,8 +119,8 @@ class Executor
*
* Useful for async PHP platforms.
*
* @param mixed|null $rootValue
* @param mixed|null $contextValue
* @param mixed[]|null $rootValue
* @param mixed[]|null $contextValue
* @param mixed[]|null $variableValues
* @param string|null $operationName
*
@ -157,33 +157,31 @@ class Executor
/**
* If a resolve function is not given, then a default resolve behavior is used
* which takes the property of the root value of the same name as the field
* which takes the property of the source object 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 $objectValue
* @param mixed[] $args
* @param mixed|null $context
* @param mixed $source
* @param mixed[] $args
* @param mixed[]|null $context
*
* @return mixed|null
*/
public static function defaultFieldResolver($objectValue, $args, $context, ResolveInfo $info)
public static function defaultFieldResolver($source, $args, $context, ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
if (is_array($objectValue) || $objectValue instanceof ArrayAccess) {
if (isset($objectValue[$fieldName])) {
$property = $objectValue[$fieldName];
if (is_array($source) || $source instanceof ArrayAccess) {
if (isset($source[$fieldName])) {
$property = $source[$fieldName];
}
} elseif (is_object($objectValue)) {
if (isset($objectValue->{$fieldName})) {
$property = $objectValue->{$fieldName};
} elseif (is_object($source)) {
if (isset($source->{$fieldName})) {
$property = $source->{$fieldName};
}
}
return $property instanceof Closure
? $property($objectValue, $args, $context, $info)
: $property;
return $property instanceof Closure ? $property($source, $args, $context, $info) : $property;
}
}

View file

@ -13,6 +13,8 @@ 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)
*/
@ -38,16 +40,16 @@ class SyncPromise
*/
private $waiting = [];
public static function runQueue() : void
public static function runQueue()
{
$q = self::$queue;
while ($q !== null && ! $q->isEmpty()) {
while ($q && ! $q->isEmpty()) {
$task = $q->dequeue();
$task();
}
}
public function resolve($value) : self
public function resolve($value)
{
switch ($this->state) {
case self::PENDING:
@ -83,7 +85,7 @@ class SyncPromise
return $this;
}
public function reject($reason) : self
public function reject($reason)
{
if (! $reason instanceof Exception && ! $reason instanceof Throwable) {
throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
@ -107,7 +109,7 @@ class SyncPromise
return $this;
}
private function enqueueWaitingPromises() : void
private function enqueueWaitingPromises()
{
Utils::invariant(
$this->state !== self::PENDING,
@ -116,7 +118,7 @@ class SyncPromise
foreach ($this->waiting as $descriptor) {
self::getQueue()->enqueue(function () use ($descriptor) {
/** @var self $promise */
/** @var $promise self */
[$promise, $onFulfilled, $onRejected] = $descriptor;
if ($this->state === self::FULFILLED) {
@ -145,17 +147,17 @@ class SyncPromise
$this->waiting = [];
}
public static function getQueue() : SplQueue
public static function getQueue()
{
return self::$queue ?: self::$queue = new SplQueue();
}
public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
{
if ($this->state === self::REJECTED && $onRejected === null) {
if ($this->state === self::REJECTED && ! $onRejected) {
return $this;
}
if ($this->state === self::FULFILLED && $onFulfilled === null) {
if ($this->state === self::FULFILLED && ! $onFulfilled) {
return $this;
}
$tmp = new self();

View file

@ -160,9 +160,7 @@ class SyncPromiseAdapter implements PromiseAdapter
if ($syncPromise->state === SyncPromise::FULFILLED) {
return $syncPromise->result;
}
if ($syncPromise->state === SyncPromise::REJECTED) {
} elseif ($syncPromise->state === SyncPromise::REJECTED) {
throw $syncPromise->result;
}

View file

@ -115,8 +115,8 @@ class ReferenceExecutor implements ExecutorImplementation
* Constructs an ExecutionContext object from the arguments passed to
* execute, which we will pass throughout the other execution methods.
*
* @param mixed $rootValue
* @param mixed $contextValue
* @param mixed[] $rootValue
* @param mixed[] $contextValue
* @param mixed[]|Traversable $rawVariableValues
* @param string|null $operationName
*
@ -134,30 +134,30 @@ class ReferenceExecutor implements ExecutorImplementation
) {
$errors = [];
$fragments = [];
/** @var OperationDefinitionNode|null $operation */
/** @var OperationDefinitionNode $operation */
$operation = null;
$hasMultipleAssumedOperations = false;
foreach ($documentNode->definitions as $definition) {
switch (true) {
case $definition instanceof OperationDefinitionNode:
if ($operationName === null && $operation !== null) {
switch ($definition->kind) {
case NodeKind::OPERATION_DEFINITION:
if (! $operationName && $operation) {
$hasMultipleAssumedOperations = true;
}
if ($operationName === null ||
if (! $operationName ||
(isset($definition->name) && $definition->name->value === $operationName)) {
$operation = $definition;
}
break;
case $definition instanceof FragmentDefinitionNode:
case NodeKind::FRAGMENT_DEFINITION:
$fragments[$definition->name->value] = $definition;
break;
}
}
if ($operation === null) {
if ($operationName === null) {
$errors[] = new Error('Must provide an operation.');
} else {
if (! $operation) {
if ($operationName) {
$errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName));
} else {
$errors[] = new Error('Must provide an operation.');
}
} elseif ($hasMultipleAssumedOperations) {
$errors[] = new Error(
@ -165,7 +165,7 @@ class ReferenceExecutor implements ExecutorImplementation
);
}
$variableValues = null;
if ($operation !== null) {
if ($operation) {
[$coercionErrors, $coercedVariableValues] = Values::getVariableValues(
$schema,
$operation->variableDefinitions ?: [],
@ -182,7 +182,6 @@ 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,
@ -199,7 +198,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
@ -207,12 +206,11 @@ 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->promiseAdapter->createFulfilled($result);
: $this->exeContext->promises->createFulfilled($result);
}
/**
@ -230,14 +228,13 @@ 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[]
*/
@ -252,26 +249,21 @@ 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) {
if ($error instanceof Error) {
$this->exeContext->addError($error);
return $this->exeContext->promiseAdapter->createFulfilled(null);
}
$this->exeContext->addError($error);
return $this->exeContext->promises->createFulfilled(null);
}
);
}
return $result;
} catch (Error $error) {
$this->exeContext->addError($error);
return null;
}
}
@ -288,33 +280,30 @@ class ReferenceExecutor implements ExecutorImplementation
switch ($operation->operation) {
case 'query':
$queryType = $schema->getQueryType();
if ($queryType === null) {
if (! $queryType) {
throw new Error(
'Schema does not define the required query root type.',
[$operation]
);
}
return $queryType;
case 'mutation':
$mutationType = $schema->getMutationType();
if ($mutationType === null) {
if (! $mutationType) {
throw new Error(
'Schema is not configured for mutations.',
[$operation]
);
}
return $mutationType;
case 'subscription':
$subscriptionType = $schema->getSubscriptionType();
if ($subscriptionType === null) {
if (! $subscriptionType) {
throw new Error(
'Schema is not configured for subscriptions.',
[$operation]
);
}
return $subscriptionType;
default:
throw new Error(
@ -345,8 +334,8 @@ class ReferenceExecutor implements ExecutorImplementation
) {
$exeContext = $this->exeContext;
foreach ($selectionSet->selections as $selection) {
switch (true) {
case $selection instanceof FieldNode:
switch ($selection->kind) {
case NodeKind::FIELD:
if (! $this->shouldIncludeNode($selection)) {
break;
}
@ -356,7 +345,7 @@ class ReferenceExecutor implements ExecutorImplementation
}
$fields[$name][] = $selection;
break;
case $selection instanceof InlineFragmentNode:
case NodeKind::INLINE_FRAGMENT:
if (! $this->shouldIncludeNode($selection) ||
! $this->doesFragmentConditionMatch($selection, $runtimeType)
) {
@ -369,7 +358,7 @@ class ReferenceExecutor implements ExecutorImplementation
$visitedFragmentNames
);
break;
case $selection instanceof FragmentSpreadNode:
case NodeKind::FRAGMENT_SPREAD:
$fragName = $selection->name->value;
if (! empty($visitedFragmentNames[$fragName]) || ! $this->shouldIncludeNode($selection)) {
break;
@ -377,7 +366,7 @@ class ReferenceExecutor implements ExecutorImplementation
$visitedFragmentNames[$fragName] = true;
/** @var FragmentDefinitionNode|null $fragment */
$fragment = $exeContext->fragments[$fragName] ?? null;
if ($fragment === null || ! $this->doesFragmentConditionMatch($fragment, $runtimeType)) {
if (! $fragment || ! $this->doesFragmentConditionMatch($fragment, $runtimeType)) {
break;
}
$this->collectFields(
@ -389,7 +378,6 @@ class ReferenceExecutor implements ExecutorImplementation
break;
}
}
return $fields;
}
@ -398,8 +386,10 @@ class ReferenceExecutor implements ExecutorImplementation
* directives, where @skip has higher precedence than @include.
*
* @param FragmentSpreadNode|FieldNode|InlineFragmentNode $node
*
* @return bool
*/
private function shouldIncludeNode($node) : bool
private function shouldIncludeNode($node)
{
$variableValues = $this->exeContext->variableValues;
$skipDirective = Directive::skipDirective();
@ -417,16 +407,20 @@ class ReferenceExecutor implements ExecutorImplementation
$node,
$variableValues
);
return ! isset($include['if']) || $include['if'] !== false;
if (isset($include['if']) && $include['if'] === false) {
return false;
}
return true;
}
/**
* Implements the logic to compute the key of a given fields entry
*
* @return string
*/
private static function getFieldEntryKey(FieldNode $node) : string
private static function getFieldEntryKey(FieldNode $node)
{
return $node->alias === null ? $node->name->value : $node->alias->value;
return $node->alias ? $node->alias->value : $node->name->value;
}
/**
@ -451,7 +445,6 @@ class ReferenceExecutor implements ExecutorImplementation
if ($conditionalType instanceof AbstractType) {
return $this->exeContext->schema->isPossibleType($conditionalType, $type);
}
return false;
}
@ -459,34 +452,32 @@ class ReferenceExecutor implements ExecutorImplementation
* Implements the "Evaluating selection sets" section of the spec
* for "write" mode.
*
* @param mixed $rootValue
* @param mixed[] $sourceValue
* @param mixed[] $path
* @param ArrayObject $fields
*
* @return Promise|stdClass|mixed[]
*/
private function executeFieldsSerially(ObjectType $parentType, $rootValue, $path, $fields)
private function executeFieldsSerially(ObjectType $parentType, $sourceValue, $path, $fields)
{
$result = $this->promiseReduce(
array_keys($fields->getArrayCopy()),
function ($results, $responseName) use ($path, $parentType, $rootValue, $fields) {
function ($results, $responseName) use ($path, $parentType, $sourceValue, $fields) {
$fieldNodes = $fields[$responseName];
$fieldPath = $path;
$fieldPath[] = $responseName;
$result = $this->resolveField($parentType, $rootValue, $fieldNodes, $fieldPath);
$result = $this->resolveField($parentType, $sourceValue, $fieldNodes, $fieldPath);
if ($result === self::$UNDEFINED) {
return $results;
}
$promise = $this->getPromise($result);
if ($promise !== null) {
if ($promise) {
return $promise->then(static function ($resolvedResult) use ($responseName, $results) {
$results[$responseName] = $resolvedResult;
return $results;
});
}
$results[$responseName] = $result;
return $results;
},
[]
@ -496,38 +487,32 @@ class ReferenceExecutor implements ExecutorImplementation
return self::fixResultsIfEmptyArray($resolvedResults);
});
}
return self::fixResultsIfEmptyArray($result);
}
/**
* Resolves the field on the given root value.
* 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.
*
* 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 object|null $source
* @param FieldNode[] $fieldNodes
* @param mixed[] $path
*
* @return mixed[]|Exception|mixed|null
*/
private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $path)
private function resolveField(ObjectType $parentType, $source, $fieldNodes, $path)
{
$exeContext = $this->exeContext;
$fieldNode = $fieldNodes[0];
$fieldName = $fieldNode->name->value;
$fieldDef = $this->getFieldDef($exeContext->schema, $parentType, $fieldName);
if ($fieldDef === null) {
if (! $fieldDef) {
return self::$UNDEFINED;
}
$returnType = $fieldDef->getType();
// 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
// The resolve function's optional third argument is a collection of
// information about the current execution state.
$info = new ResolveInfo(
$fieldName,
@ -548,13 +533,17 @@ 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,
$rootValue,
$source,
$context,
$info
);
@ -565,21 +554,23 @@ 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, string $fieldName) : ?FieldDefinition
private function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName)
{
static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef;
$schemaMetaFieldDef = $schemaMetaFieldDef ?: Introspection::schemaMetaFieldDef();
@ -587,45 +578,39 @@ class ReferenceExecutor implements ExecutorImplementation
$typeNameMetaFieldDef = $typeNameMetaFieldDef ?: Introspection::typeNameMetaFieldDef();
if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) {
return $schemaMetaFieldDef;
}
if ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) {
} elseif ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) {
return $typeMetaFieldDef;
}
if ($fieldName === $typeNameMetaFieldDef->name) {
} elseif ($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 $rootValue
* @param mixed $source
* @param mixed $context
* @param ResolveInfo $info
*
* @return Throwable|Promise|mixed
*/
private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $rootValue, $context, $info)
private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $source, $context, $info)
{
try {
// Build a map of arguments from the field.arguments AST, using the
// Build hash 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($rootValue, $args, $context, $info);
return $resolveFn($source, $args, $context, $info);
} catch (Exception $error) {
return $error;
} catch (Throwable $error) {
@ -673,23 +658,20 @@ class ReferenceExecutor implements ExecutorImplementation
$result
);
$promise = $this->getPromise($completed);
if ($promise !== null) {
if ($promise) {
return $promise->then(
null,
function ($error) use ($exeContext) {
$exeContext->addError($error);
return $this->exeContext->promiseAdapter->createFulfilled(null);
return $this->exeContext->promises->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;
}
}
@ -722,11 +704,11 @@ class ReferenceExecutor implements ExecutorImplementation
$result
);
$promise = $this->getPromise($completed);
if ($promise !== null) {
if ($promise) {
return $promise->then(
null,
function ($error) use ($fieldNodes, $path) {
return $this->exeContext->promiseAdapter->createRejected(Error::createLocatedError(
return $this->exeContext->promises->createRejected(Error::createLocatedError(
$error,
$fieldNodes,
$path
@ -734,7 +716,6 @@ class ReferenceExecutor implements ExecutorImplementation
}
);
}
return $completed;
} catch (Exception $error) {
throw Error::createLocatedError($error, $fieldNodes, $path);
@ -782,7 +763,7 @@ class ReferenceExecutor implements ExecutorImplementation
) {
$promise = $this->getPromise($result);
// If result is a Promise, apply-lift over completeValue.
if ($promise !== null) {
if ($promise) {
return $promise->then(function (&$resolved) use ($returnType, $fieldNodes, $info, $path) {
return $this->completeValue($returnType, $fieldNodes, $info, $path, $resolved);
});
@ -805,7 +786,6 @@ class ReferenceExecutor implements ExecutorImplementation
'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.'
);
}
return $completed;
}
// If result is null-like, return null.
@ -820,7 +800,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 !== null) {
if ($this->exeContext->schema->getConfig()->typeLoader) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$info->parentType,
@ -858,7 +838,7 @@ class ReferenceExecutor implements ExecutorImplementation
*/
private function isPromise($value)
{
return $value instanceof Promise || $this->exeContext->promiseAdapter->isThenable($value);
return $value instanceof Promise || $this->exeContext->promises->isThenable($value);
}
/**
@ -874,19 +854,17 @@ class ReferenceExecutor implements ExecutorImplementation
if ($value === null || $value instanceof Promise) {
return $value;
}
if ($this->exeContext->promiseAdapter->isThenable($value)) {
$promise = $this->exeContext->promiseAdapter->convertThenable($value);
if ($this->exeContext->promises->isThenable($value)) {
$promise = $this->exeContext->promises->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->promiseAdapter),
get_class($this->exeContext->promises),
Utils::printSafe($promise)
));
}
return $promise;
}
return null;
}
@ -900,7 +878,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)
{
@ -908,12 +886,11 @@ class ReferenceExecutor implements ExecutorImplementation
$values,
function ($previous, $value) use ($callback) {
$promise = $this->getPromise($previous);
if ($promise !== null) {
if ($promise) {
return $promise->then(static function ($resolved) use ($callback, $value) {
return $callback($resolved, $value);
});
}
return $callback($previous, $value);
},
$initialValue
@ -921,40 +898,37 @@ 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[]|Traversable $results
* @param FieldNode[] $fieldNodes
* @param mixed[] $path
* @param mixed $result
*
* @return mixed[]|Promise
*
* @throws Exception
*/
private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$results)
private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
{
$itemType = $returnType->getWrappedType();
Utils::invariant(
is_array($results) || $results instanceof Traversable,
is_array($result) || $result instanceof Traversable,
'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.'
);
$containsPromise = false;
$i = 0;
$completedItems = [];
foreach ($results as $item) {
foreach ($result as $item) {
$fieldPath = $path;
$fieldPath[] = $i++;
$info->path = $fieldPath;
$completedItem = $this->completeValueCatchingError($itemType, $fieldNodes, $info, $fieldPath, $item);
if (! $containsPromise && $this->getPromise($completedItem) !== null) {
if (! $containsPromise && $this->getPromise($completedItem)) {
$containsPromise = true;
}
$completedItems[] = $completedItem;
}
return $containsPromise
? $this->exeContext->promiseAdapter->all($completedItems)
: $completedItems;
return $containsPromise ? $this->exeContext->promises->all($completedItems) : $completedItems;
}
/**
@ -1005,7 +979,7 @@ class ReferenceExecutor implements ExecutorImplementation
$runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType);
}
$promise = $this->getPromise($runtimeType);
if ($promise !== null) {
if ($promise) {
return $promise->then(function ($resolvedRuntimeType) use (
$returnType,
$fieldNodes,
@ -1027,7 +1001,6 @@ class ReferenceExecutor implements ExecutorImplementation
);
});
}
return $this->completeObjectValue(
$this->ensureValidRuntimeType(
$runtimeType,
@ -1067,8 +1040,7 @@ class ReferenceExecutor implements ExecutorImplementation
) {
return $value['__typename'];
}
if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader !== null) {
if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader) {
Warning::warnOnce(
sprintf(
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
@ -1090,25 +1062,23 @@ class ReferenceExecutor implements ExecutorImplementation
continue;
}
$promise = $this->getPromise($isTypeOfResult);
if ($promise !== null) {
if ($promise) {
$promisedIsTypeOfResults[$index] = $promise;
} elseif ($isTypeOfResult) {
return $type;
}
}
if (! empty($promisedIsTypeOfResults)) {
return $this->exeContext->promiseAdapter->all($promisedIsTypeOfResults)
return $this->exeContext->promises->all($promisedIsTypeOfResults)
->then(static function ($isTypeOfResults) use ($possibleTypes) {
foreach ($isTypeOfResults as $index => $result) {
if ($result) {
return $possibleTypes[$index];
}
}
return null;
});
}
return null;
}
@ -1131,7 +1101,7 @@ class ReferenceExecutor implements ExecutorImplementation
$isTypeOf = $returnType->isTypeOf($result, $this->exeContext->contextValue, $info);
if ($isTypeOf !== null) {
$promise = $this->getPromise($isTypeOf);
if ($promise !== null) {
if ($promise) {
return $promise->then(function ($isTypeOfResult) use (
$returnType,
$fieldNodes,
@ -1141,7 +1111,6 @@ class ReferenceExecutor implements ExecutorImplementation
if (! $isTypeOfResult) {
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
}
return $this->collectAndExecuteSubfields(
$returnType,
$fieldNodes,
@ -1154,7 +1123,6 @@ class ReferenceExecutor implements ExecutorImplementation
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
}
}
return $this->collectAndExecuteSubfields(
$returnType,
$fieldNodes,
@ -1183,7 +1151,7 @@ class ReferenceExecutor implements ExecutorImplementation
/**
* @param FieldNode[] $fieldNodes
* @param mixed[] $path
* @param mixed $result
* @param mixed[] $result
*
* @return mixed[]|Promise|stdClass
*
@ -1196,7 +1164,6 @@ class ReferenceExecutor implements ExecutorImplementation
&$result
) {
$subFieldNodes = $this->collectSubFields($returnType, $fieldNodes);
return $this->executeFields($returnType, $result, $path, $subFieldNodes);
}
@ -1222,7 +1189,6 @@ class ReferenceExecutor implements ExecutorImplementation
}
$this->subFieldCache[$returnType][$fieldNodes] = $subFieldNodes;
}
return $this->subFieldCache[$returnType][$fieldNodes];
}
@ -1230,24 +1196,24 @@ class ReferenceExecutor implements ExecutorImplementation
* Implements the "Evaluating selection sets" section of the spec
* for "read" mode.
*
* @param mixed $rootValue
* @param mixed|null $source
* @param mixed[] $path
* @param ArrayObject $fields
*
* @return Promise|stdClass|mixed[]
*/
private function executeFields(ObjectType $parentType, $rootValue, $path, $fields)
private function executeFields(ObjectType $parentType, $source, $path, $fields)
{
$containsPromise = false;
$finalResults = [];
foreach ($fields as $responseName => $fieldNodes) {
$fieldPath = $path;
$fieldPath[] = $responseName;
$result = $this->resolveField($parentType, $rootValue, $fieldNodes, $fieldPath);
$result = $this->resolveField($parentType, $source, $fieldNodes, $fieldPath);
if ($result === self::$UNDEFINED) {
continue;
}
if (! $containsPromise && $this->getPromise($result) !== null) {
if (! $containsPromise && $this->getPromise($result)) {
$containsPromise = true;
}
$finalResults[$responseName] = $result;
@ -1256,7 +1222,6 @@ 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
@ -1276,7 +1241,6 @@ class ReferenceExecutor implements ExecutorImplementation
if ($results === []) {
return new stdClass();
}
return $results;
}
@ -1295,20 +1259,19 @@ class ReferenceExecutor implements ExecutorImplementation
{
$keys = array_keys($assoc);
$valuesAndPromises = array_values($assoc);
$promise = $this->exeContext->promiseAdapter->all($valuesAndPromises);
$promise = $this->exeContext->promises->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
@ -1319,9 +1282,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(
@ -1355,7 +1318,6 @@ class ReferenceExecutor implements ExecutorImplementation
)
);
}
return $runtimeType;
}
}

View file

@ -92,7 +92,7 @@ class Values
),
[$varDefNode]
);
} elseif ($varDefNode->defaultValue !== null) {
} elseif ($varDefNode->defaultValue) {
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
}
}
@ -196,7 +196,7 @@ class Values
$argType = $argumentDefinition->getType();
$argumentValueNode = $argumentValueMap[$name] ?? null;
if ($argumentValueNode === null) {
if (! $argumentValueNode) {
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 !== null && array_key_exists($variableName, $variableValues)) {
if ($variableValues && 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,7 +273,6 @@ class Values
return $error->getMessage();
},
$errors
)
: [];
) : [];
}
}

View file

@ -64,7 +64,7 @@ class Collector
foreach ($documentNode->definitions as $definitionNode) {
/** @var DefinitionNode|Node $definitionNode */
if ($definitionNode instanceof OperationDefinitionNode) {
if ($definitionNode->kind === NodeKind::OPERATION_DEFINITION) {
/** @var OperationDefinitionNode $definitionNode */
if ($operationName === null && $this->operation !== null) {
$hasMultipleAssumedOperations = true;
@ -74,7 +74,7 @@ class Collector
) {
$this->operation = $definitionNode;
}
} elseif ($definitionNode instanceof FragmentDefinitionNode) {
} elseif ($definitionNode->kind === NodeKind::FRAGMENT_DEFINITION) {
/** @var FragmentDefinitionNode $definitionNode */
$this->fragments[$definitionNode->name->value] = $definitionNode;
}
@ -86,13 +86,11 @@ 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;
}
@ -196,26 +194,24 @@ class Collector
}
}
if ($selection instanceof FieldNode) {
if ($selection->kind === NodeKind::FIELD) {
/** @var FieldNode $selection */
$resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value;
$resultName = $selection->alias ? $selection->alias->value : $selection->name->value;
if (! isset($this->fields[$resultName])) {
$this->fields[$resultName] = [];
}
$this->fields[$resultName][] = $selection;
} elseif ($selection instanceof FragmentSpreadNode) {
} elseif ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
/** @var FragmentSpreadNode $selection */
$fragmentName = $selection->name->value;
if (isset($this->visitedFragments[$fragmentName])) {
continue;
}
if (! isset($this->fragments[$fragmentName])) {
} elseif (! isset($this->fragments[$fragmentName])) {
$this->runtime->addError(new Error(
sprintf('Fragment "%s" does not exist.', $fragmentName),
$selection
@ -249,7 +245,7 @@ class Collector
}
$this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet);
} elseif ($selection instanceof InlineFragmentNode) {
} elseif ($selection->kind === NodeKind::INLINE_FRAGMENT) {
/** @var InlineFragmentNode $selection */
if ($selection->typeCondition !== null) {

View file

@ -147,16 +147,14 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($emptyObjectAsStdClass && empty($array)) {
return new stdClass();
}
return $array;
}
if (is_array($value)) {
$array = [];
foreach ($value as $key => $item) {
$array[$key] = self::resultToArray($item);
foreach ($value as $item) {
$array[] = self::resultToArray($item);
}
return $array;
}
@ -293,17 +291,13 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$strand->stack[$strand->depth++] = $strand->current;
$strand->current = $value;
goto START;
} elseif ($this->isPromise($value)) {
} elseif ($this->promiseAdapter->isThenable($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;
@ -371,7 +365,6 @@ 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;
}
@ -482,7 +475,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->isPromise($value) || $value instanceof Throwable) {
if ($this->promiseAdapter->isThenable($value) || $value instanceof Throwable) {
return false;
}
@ -498,7 +491,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($type !== $this->schema->getType($type->name)) {
$hint = '';
if ($this->schema->getConfig()->typeLoader !== null) {
if ($this->schema->getConfig()->typeLoader) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type,
@ -578,7 +571,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
// !!! $value might be promise, yield to resolve
try {
if ($this->isPromise($value)) {
if ($this->promiseAdapter->isThenable($value)) {
$value = yield $value;
}
} catch (Throwable $reason) {
@ -620,9 +613,8 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
foreach ($value as $itemValue) {
++$index;
$itemPath = $path;
$itemPath[] = $index; // !!! use arrays COW semantics
$ctx->resolveInfo->path = $itemPath;
$itemPath = $path;
$itemPath[] = $index; // !!! use arrays COW semantics
try {
if (! $this->completeValueFast($ctx, $itemType, $itemValue, $itemPath, $itemReturnValue)) {
@ -647,7 +639,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
} else {
if ($type !== $this->schema->getType($type->name)) {
$hint = '';
if ($this->schema->getConfig()->typeLoader !== null) {
if ($this->schema->getConfig()->typeLoader) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type,
@ -821,10 +813,7 @@ 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;
@ -908,7 +897,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
return $this->schema->getType($value['__typename']);
}
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader !== null) {
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader) {
Warning::warnOnce(
sprintf(
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
@ -939,14 +928,4 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
return $selectedType;
}
/**
* @param mixed $value
*
* @return bool
*/
private function isPromise($value)
{
return $value instanceof Promise || $this->promiseAdapter->isThenable($value);
}
}

View file

@ -9,6 +9,6 @@ class ListTypeNode extends Node implements TypeNode
/** @var string */
public $kind = NodeKind::LIST_TYPE;
/** @var TypeNode */
/** @var Node */
public $type;
}

View file

@ -59,7 +59,6 @@ class Location
$tmp = new static();
$tmp->start = $start;
$tmp->end = $end;
return $tmp;
}
@ -69,7 +68,7 @@ class Location
$this->endToken = $endToken;
$this->source = $source;
if ($startToken === null || $endToken === null) {
if (! $startToken || ! $endToken) {
return;
}

View file

@ -40,7 +40,7 @@ abstract class Node
public $loc;
/**
* @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars
* @param (string|NameNode|NodeList|SelectionSetNode|Location|null)[] $vars
*/
public function __construct(array $vars)
{
@ -106,7 +106,7 @@ abstract class Node
$tmp = (array) $this;
if ($this->loc !== null) {
if ($this->loc) {
$tmp['loc'] = [
'start' => $this->loc->start,
'end' => $this->loc->end,
@ -125,7 +125,7 @@ abstract class Node
'kind' => $node->kind,
];
if ($node->loc !== null) {
if ($node->loc) {
$result['loc'] = [
'start' => $node->loc->start,
'end' => $node->loc->end,

View file

@ -105,7 +105,6 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
if ($list instanceof self) {
$list = $list->nodes;
}
return new NodeList(array_merge($this->nodes, $list));
}
@ -114,8 +113,9 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
*/
public function getIterator()
{
foreach ($this->nodes as $key => $_) {
yield $this->offsetGet($key);
$count = count($this->nodes);
for ($i = 0; $i < $count; $i++) {
yield $this->offsetGet($i);
}
}

View file

@ -9,6 +9,6 @@ class NonNullTypeNode extends Node implements TypeNode
/** @var string */
public $kind = NodeKind::NON_NULL_TYPE;
/** @var NamedTypeNode | ListTypeNode */
/** @var NameNode | ListTypeNode */
public $type;
}

View file

@ -23,22 +23,6 @@ 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;
@ -108,8 +92,7 @@ class Lexer
*/
public function advance()
{
$this->lastToken = $this->token;
$this->lastToken = $this->token;
return $this->token = $this->lookahead();
}
@ -148,45 +131,44 @@ class Lexer
[, $code, $bytes] = $this->readChar(true);
switch ($code) {
case self::TOKEN_BANG:
case 33: // !
return new Token(Token::BANG, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_HASH: // #
case 35: // #
$this->moveStringCursor(-1, -1 * $bytes);
return $this->readComment($line, $col, $prev);
case self::TOKEN_DOLLAR:
case 36: // $
return new Token(Token::DOLLAR, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_AMP:
case 38: // &
return new Token(Token::AMP, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_PAREN_L:
case 40: // (
return new Token(Token::PAREN_L, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_PAREN_R:
case 41: // )
return new Token(Token::PAREN_R, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_DOT: // .
case 46: // .
[, $charCode1] = $this->readChar(true);
[, $charCode2] = $this->readChar(true);
if ($charCode1 === self::TOKEN_DOT && $charCode2 === self::TOKEN_DOT) {
if ($charCode1 === 46 && $charCode2 === 46) {
return new Token(Token::SPREAD, $position, $position + 3, $line, $col, $prev);
}
break;
case self::TOKEN_COLON:
case 58: // :
return new Token(Token::COLON, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_EQUALS:
case 61: // =
return new Token(Token::EQUALS, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_AT:
case 64: // @
return new Token(Token::AT, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_BRACKET_L:
case 91: // [
return new Token(Token::BRACKET_L, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_BRACKET_R:
case 93: // ]
return new Token(Token::BRACKET_R, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_BRACE_L:
case 123: // {
return new Token(Token::BRACE_L, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_PIPE:
case 124: // |
return new Token(Token::PIPE, $position, $position + 1, $line, $col, $prev);
case self::TOKEN_BRACE_R:
case 125: // }
return new Token(Token::BRACE_R, $position, $position + 1, $line, $col, $prev);
// A-Z
case 65:
case 66:
@ -245,7 +227,6 @@ class Lexer
case 122:
return $this->moveStringCursor(-1, -1 * $bytes)
->readName($line, $col, $prev);
// -
case 45:
// 0-9
@ -261,7 +242,6 @@ class Lexer
case 57:
return $this->moveStringCursor(-1, -1 * $bytes)
->readNumber($line, $col, $prev);
// "
case 34:
[, $nextCode] = $this->readChar();
@ -314,11 +294,11 @@ class Lexer
$start = $this->position;
[$char, $code] = $this->readChar();
while ($code !== null && (
while ($code && (
$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();
@ -584,10 +564,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);
@ -695,7 +675,7 @@ class Lexer
do {
[$char, $code, $bytes] = $this->moveStringCursor(1, $bytes)->readChar();
$value .= $char;
} while ($code !== null &&
} while ($code &&
// SourceCharacter but not LineTerminator
($code > 0x001F || $code === 0x0009)
);

View file

@ -439,6 +439,7 @@ class Parser
case 'mutation':
case 'subscription':
return $this->parseOperationDefinition();
case 'fragment':
return $this->parseFragmentDefinition();
}
@ -512,15 +513,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([]);
}
/**
@ -592,9 +593,9 @@ class Parser
*/
private function parseSelection()
{
return $this->peek(Token::SPREAD)
? $this->parseFragment()
: $this->parseField();
return $this->peek(Token::SPREAD) ?
$this->parseFragment() :
$this->parseField();
}
/**
@ -634,17 +635,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([]);
}
/**
@ -826,9 +827,7 @@ class Parser
'value' => $token->value === 'true',
'loc' => $this->loc($token),
]);
}
if ($token->value === 'null') {
} elseif ($token->value === 'null') {
$this->lexer->advance();
return new NullValueNode([
@ -1208,8 +1207,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))
);
}
@ -1545,8 +1544,7 @@ class Parser
Token::BRACE_L,
[$this, 'parseOperationTypeDefinition'],
Token::BRACE_R
)
: [];
) : [];
if (count($directives) === 0 && count($operationTypes) === 0) {
$this->unexpected();
}
@ -1656,7 +1654,9 @@ class Parser
$name = $this->parseName();
$directives = $this->parseDirectives(true);
$types = $this->parseUnionMemberTypes();
if (count($directives) === 0 && count($types) === 0) {
if (count($directives) === 0 &&
! $types
) {
throw $this->unexpected();
}

View file

@ -28,7 +28,6 @@ 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;
@ -48,7 +47,6 @@ 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;
@ -99,11 +97,11 @@ class Printer
$ast,
[
'leave' => [
NodeKind::NAME => static function (NameNode $node) {
NodeKind::NAME => static function (Node $node) {
return '' . $node->value;
},
NodeKind::VARIABLE => static function (VariableNode $node) {
NodeKind::VARIABLE => static function ($node) {
return '$' . $node->name;
},
@ -117,7 +115,6 @@ 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'

View file

@ -11,28 +11,28 @@ namespace GraphQL\Language;
class Token
{
// Each kind of token.
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';
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';
/**
* The kind of Token (see one of constants above).
@ -104,15 +104,18 @@ class Token
$this->value = $value;
}
public function getDescription() : string
/**
* @return string
*/
public function getDescription()
{
return $this->kind . ($this->value === null ? '' : ' "' . $this->value . '"');
return $this->kind . ($this->value ? ' "' . $this->value . '"' : '');
}
/**
* @return (string|int|null)[]
*/
public function toArray() : array
public function toArray()
{
return [
'kind' => $this->kind,

View file

@ -251,18 +251,8 @@ 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;
}

22
src/Schema.php Normal file
View file

@ -0,0 +1,22 @@
<?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
{
}

View file

@ -73,14 +73,10 @@ 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()) {
@ -276,9 +272,7 @@ 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);
@ -391,13 +385,13 @@ class Helper
*/
private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType)
{
$rootValue = $config->getRootValue();
$root = $config->getRootValue();
if (is_callable($rootValue)) {
$rootValue = $rootValue($params, $doc, $operationType);
if (is_callable($root)) {
$root = $root($params, $doc, $operationType);
}
return $rootValue;
return $root;
}
/**

View file

@ -9,7 +9,6 @@ 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
@ -47,12 +46,6 @@ class OperationParams
*/
public $variables;
/**
* @api
* @var mixed[]|null
*/
public $extensions;
/** @var mixed[] */
private $originalInput;
@ -63,10 +56,13 @@ class OperationParams
* Creates an instance from given array
*
* @param mixed[] $params
* @param bool $readonly
*
* @return OperationParams
*
* @api
*/
public static function create(array $params, bool $readonly = false) : OperationParams
public static function create(array $params, $readonly = false)
{
$instance = new static();
@ -80,38 +76,24 @@ class OperationParams
'id' => null, // alias to queryid
'operationname' => null,
'variables' => null,
'extensions' => null,
];
if ($params['variables'] === '') {
$params['variables'] = null;
}
// Some parameters could be provided as serialized JSON.
foreach (['extensions', 'variables'] as $param) {
if (! is_string($params[$param])) {
continue;
if (is_string($params['variables'])) {
$tmp = json_decode($params['variables'], true);
if (! json_last_error()) {
$params['variables'] = $tmp;
}
$tmp = json_decode($params[$param], true);
if (json_last_error() !== JSON_ERROR_NONE) {
continue;
}
$params[$param] = $tmp;
}
$instance->query = $params['query'];
$instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id'];
$instance->operation = $params['operationname'];
$instance->variables = $params['variables'];
$instance->extensions = $params['extensions'];
$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)) {
$instance->queryId = $instance->extensions['persistedQuery']['sha256Hash'];
}
$instance->query = $params['query'];
$instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id'];
$instance->operation = $params['operationname'];
$instance->variables = $params['variables'];
$instance->readOnly = (bool) $readonly;
return $instance;
}

View file

@ -225,11 +225,15 @@ class ServerConfig
/**
* Allow batching queries (disabled by default)
*
* @param bool $enableBatching
*
* @return self
*
* @api
*/
public function setQueryBatching(bool $enableBatching) : self
public function setQueryBatching($enableBatching)
{
$this->queryBatching = $enableBatching;
$this->queryBatching = (bool) $enableBatching;
return $this;
}

View file

@ -151,7 +151,6 @@ class StandardServer
StreamInterface $writableBodyStream
) {
$result = $this->executePsrRequest($request);
return $this->helper->toPsrResponse($result, $response, $writableBodyStream);
}
@ -166,7 +165,6 @@ class StandardServer
public function executePsrRequest(ServerRequestInterface $request)
{
$parsedBody = $this->helper->parsePsrRequest($request);
return $this->executeRequest($parsedBody);
}

View file

@ -11,6 +11,9 @@ use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils;
use function is_bool;
/**
* Class BooleanType
*/
class BooleanType extends ScalarType
{
/** @var string */
@ -20,14 +23,11 @@ class BooleanType extends ScalarType
public $description = 'The `Boolean` scalar type represents `true` or `false`.';
/**
* Coerce the given value to a boolean.
*
* 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
*
* @return bool
*/
public function serialize($value) : bool
public function serialize($value)
{
return (bool) $value;
}
@ -58,11 +58,11 @@ class BooleanType extends ScalarType
*/
public function parseLiteral($valueNode, ?array $variables = null)
{
if (! $valueNode instanceof BooleanValueNode) {
// Intentionally without message, as all information already in wrapped Exception
throw new Exception();
if ($valueNode instanceof BooleanValueNode) {
return (bool) $valueNode->value;
}
return $valueNode->value;
// Intentionally without message, as all information already in wrapped Exception
throw new Exception();
}
}

View file

@ -12,6 +12,9 @@ use function call_user_func;
use function is_callable;
use function sprintf;
/**
* Class CustomScalarType
*/
class CustomScalarType extends ScalarType
{
/**

View file

@ -8,19 +8,24 @@ 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';
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';
const INCLUDE_NAME = 'include';
const IF_ARGUMENT_NAME = 'if';
const SKIP_NAME = 'skip';
const DEPRECATED_NAME = 'deprecated';
const REASON_ARGUMENT_NAME = 'reason';
/** @var Directive[]|null */
/** @var Directive[] */
public static $internalDirectives;
// Schema Definitions
@ -35,7 +40,7 @@ class Directive
public $locations;
/** @var FieldArgument[] */
public $args = [];
public $args;
/** @var DirectiveDefinitionNode|null */
public $astNode;
@ -75,16 +80,15 @@ class Directive
public static function includeDirective()
{
$internal = self::getInternalDirectives();
return $internal['include'];
}
/**
* @return Directive[]
*/
public static function getInternalDirectives() : array
public static function getInternalDirectives()
{
if (self::$internalDirectives === null) {
if (! self::$internalDirectives) {
self::$internalDirectives = [
'include' => new self([
'name' => self::INCLUDE_NAME,
@ -136,30 +140,24 @@ 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
*/

View file

@ -19,7 +19,10 @@ use function is_int;
use function is_string;
use function sprintf;
class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType
/**
* Class EnumType
*/
class EnumType extends Type implements InputType, OutputType, LeafType, NamedType
{
/** @var EnumTypeDefinitionNode|null */
public $astNode;
@ -30,7 +33,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[] */
@ -68,7 +71,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
}
/**
* @return ArrayObject<string, EnumValueDefinition>
* @return \ArrayObject<string, EnumValueDefinition>
*/
private function getNameLookup()
{

View file

@ -6,6 +6,9 @@ namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\EnumValueDefinitionNode;
/**
* Class EnumValueDefinition
*/
class EnumValueDefinition
{
/** @var string */

View file

@ -11,6 +11,9 @@ use function is_array;
use function is_string;
use function sprintf;
/**
* Class FieldArgument
*/
class FieldArgument
{
/** @var string */

View file

@ -81,7 +81,7 @@ class FieldDefinition
$this->config = $config;
$this->complexityFn = $config['complexity'] ?? self::DEFAULT_COMPLEXITY_FN;
$this->complexityFn = $config['complexity'] ?? static::DEFAULT_COMPLEXITY_FN;
}
public static function defineFieldMap(Type $type, $fields)

View file

@ -12,6 +12,9 @@ use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils;
use function is_numeric;
/**
* Class FloatType
*/
class FloatType extends ScalarType
{
/** @var string */

View file

@ -15,7 +15,10 @@ use function is_callable;
use function is_string;
use function sprintf;
class InputObjectType extends Type implements InputType, NullableType, NamedType
/**
* Class InputObjectType
*/
class InputObjectType extends Type implements InputType, NamedType
{
/** @var InputObjectTypeDefinitionNode|null */
public $astNode;
@ -69,9 +72,7 @@ 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(

View file

@ -14,6 +14,9 @@ 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

View file

@ -12,7 +12,10 @@ use function is_callable;
use function is_string;
use function sprintf;
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
/**
* Class InterfaceType
*/
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NamedType
{
/** @var InterfaceTypeDefinitionNode|null */
public $astNode;
@ -104,7 +107,7 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
* @param object $objectValue
* @param mixed[] $context
*
* @return Type|null
* @return callable|null
*/
public function resolveType($objectValue, $context, ResolveInfo $info)
{

View file

@ -4,7 +4,10 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition;
class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType
/**
* Class ListOfType
*/
class ListOfType extends Type implements WrappingType, OutputType, InputType
{
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */
public $ofType;
@ -17,9 +20,15 @@ class ListOfType extends Type implements WrappingType, OutputType, NullableType,
$this->ofType = Type::assertType($type);
}
public function toString() : string
/**
* @return string
*/
public function toString()
{
return '[' . $this->ofType->toString() . ']';
$type = $this->ofType;
$str = $type instanceof Type ? $type->toString() : (string) $type;
return '[' . $str . ']';
}
/**
@ -31,8 +40,6 @@ 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;
}
}

View file

@ -4,15 +4,22 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition;
use Exception;
use GraphQL\Error\InvariantViolation;
use GraphQL\Utils\Utils;
/**
* Class NonNull
*/
class NonNull extends Type implements WrappingType, OutputType, InputType
{
/** @var NullableType */
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType */
private $ofType;
/**
* @param NullableType $type
* @param callable|Type $type
*
* @throws Exception
*/
public function __construct($type)
{
@ -22,7 +29,7 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
/**
* @param mixed $type
*
* @return NullableType
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
*/
public static function assertNullableType($type)
{
@ -60,14 +67,14 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
/**
* @param bool $recurse
*
* @return Type
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
*
* @throws InvariantViolation
*/
public function getWrappedType($recurse = false)
{
$type = $this->ofType;
return $recurse && $type instanceof WrappingType
? $type->getWrappedType($recurse)
: $type;
return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) : $type;
}
}

View file

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL\Type\Definition;
/*
export type GraphQLNullableType =
| GraphQLScalarType
| GraphQLObjectType
| GraphQLInterfaceType
| GraphQLUnionType
| GraphQLEnumType
| GraphQLInputObjectType
| GraphQLList<any>;
*/
interface NullableType
{
}

View file

@ -54,7 +54,7 @@ use function sprintf;
* }
* ]);
*/
class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType
class ObjectType extends Type implements OutputType, CompositeType, NamedType
{
/** @var ObjectTypeDefinitionNode|null */
public $astNode;
@ -168,7 +168,7 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
private function getInterfaceMap()
{
if ($this->interfaceMap === null) {
if (! $this->interfaceMap) {
$this->interfaceMap = [];
foreach ($this->getInterfaces() as $interface) {
$this->interfaceMap[$interface->name] = $interface;
@ -185,9 +185,7 @@ 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(
@ -202,21 +200,19 @@ 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;
}
/**

View file

@ -1,242 +0,0 @@
<?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;
}
}

View file

@ -15,16 +15,15 @@ use function array_merge_recursive;
/**
* Structure containing information useful for field resolution process.
*
* Passed as 4th argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md).
* Passed as 3rd 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
* @var string|null
*/
public $fieldName;
@ -32,12 +31,12 @@ class ResolveInfo
* AST of all nodes referencing this field in the query.
*
* @api
* @var FieldNode[]
* @var FieldNode[]|null
*/
public $fieldNodes = [];
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
@ -45,47 +44,47 @@ class ResolveInfo
public $returnType;
/**
* Parent type of the field being resolved.
* Parent type of the field being resolved
*
* @api
* @var ObjectType
* @var ObjectType|null
*/
public $parentType;
/**
* Path to this field from the very root value.
* Path to this field from the very root value
*
* @api
* @var string[][]
* @var string[]
*/
public $path;
/**
* Instance of a schema used for execution.
* Instance of a schema used for execution
*
* @api
* @var Schema
* @var Schema|null
*/
public $schema;
/**
* AST of all fragments defined in query.
* AST of all fragments defined in query
*
* @api
* @var FragmentDefinitionNode[]
* @var FragmentDefinitionNode[]|null
*/
public $fragments = [];
public $fragments;
/**
* Root value passed to query execution.
* Root value passed to query execution
*
* @api
* @var mixed
* @var mixed|null
*/
public $rootValue;
/**
* AST of operation definition node (query, mutation).
* AST of operation definition node (query, mutation)
*
* @api
* @var OperationDefinitionNode|null
@ -93,35 +92,24 @@ class ResolveInfo
public $operation;
/**
* Array of variables passed to query execution.
* Array of variables passed to query execution
*
* @api
* @var mixed[]
* @var mixed[]|null
*/
public $variableValues = [];
public $variableValues;
/** @var QueryPlan */
private $queryPlan;
/**
* @param FieldNode[] $fieldNodes
* @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType
* @param string[][] $path
* @param FragmentDefinitionNode[] $fragments
* @param mixed|null $rootValue
* @param mixed[] $variableValues
*/
public function __construct(
string $fieldName,
iterable $fieldNodes,
$fieldNodes,
$returnType,
ObjectType $parentType,
array $path,
$path,
Schema $schema,
array $fragments,
$fragments,
$rootValue,
?OperationDefinitionNode $operation,
array $variableValues
$variableValues
) {
$this->fieldName = $fieldName;
$this->fieldNodes = $fieldNodes;
@ -137,7 +125,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{
@ -178,30 +166,11 @@ 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[]
*/
@ -230,7 +199,6 @@ class ResolveInfo
);
}
}
return $fields;
}
}

View file

@ -27,7 +27,7 @@ use function is_string;
* }
* }
*/
abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NullableType, NamedType
abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NamedType
{
/** @var ScalarTypeDefinitionNode|null */
public $astNode;

View file

@ -14,6 +14,9 @@ use function is_object;
use function is_scalar;
use function method_exists;
/**
* Class StringType
*/
class StringType extends ScalarType
{
/** @var string */

View file

@ -137,7 +137,7 @@ abstract class Type implements JsonSerializable
}
/**
* @param NullableType $wrappedType
* @param ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType $wrappedType
*
* @return NonNull
*
@ -194,7 +194,6 @@ abstract class Type implements JsonSerializable
public static function getInternalTypes()
{
trigger_error(__METHOD__ . ' is deprecated. Use Type::getStandardTypes() instead', E_USER_DEPRECATED);
return self::getStandardTypes();
}
@ -339,15 +338,13 @@ abstract class Type implements JsonSerializable
/**
* @param Type $type
*
* @return NullableType
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType
*
* @api
*/
public static function getNullableType($type)
{
return $type instanceof NonNull
? $type->getWrappedType()
: $type;
return $type instanceof NonNull ? $type->getWrappedType() : $type;
}
/**

View file

@ -14,7 +14,10 @@ use function is_callable;
use function is_string;
use function sprintf;
class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
/**
* Class UnionType
*/
class UnionType extends Type implements AbstractType, OutputType, CompositeType, NamedType
{
/** @var UnionTypeDefinitionNode */
public $astNode;

View file

@ -488,9 +488,7 @@ 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' => [
@ -695,7 +693,7 @@ EOD;
return self::$map['__DirectiveLocation'];
}
public static function schemaMetaFieldDef() : FieldDefinition
public static function schemaMetaFieldDef()
{
if (! isset(self::$map[self::SCHEMA_FIELD_NAME])) {
self::$map[self::SCHEMA_FIELD_NAME] = FieldDefinition::create([
@ -717,7 +715,7 @@ EOD;
return self::$map[self::SCHEMA_FIELD_NAME];
}
public static function typeMetaFieldDef() : FieldDefinition
public static function typeMetaFieldDef()
{
if (! isset(self::$map[self::TYPE_FIELD_NAME])) {
self::$map[self::TYPE_FIELD_NAME] = FieldDefinition::create([
@ -736,7 +734,7 @@ EOD;
return self::$map[self::TYPE_FIELD_NAME];
}
public static function typeNameMetaFieldDef() : FieldDefinition
public static function typeNameMetaFieldDef()
{
if (! isset(self::$map[self::TYPE_NAME_FIELD_NAME])) {
self::$map[self::TYPE_NAME_FIELD_NAME] = FieldDefinition::create([

View file

@ -42,7 +42,7 @@ class SchemaConfig
/** @var Directive[] */
public $directives;
/** @var callable|null */
/** @var callable */
public $typeLoader;
/** @var SchemaDefinitionNode */

View file

@ -29,7 +29,6 @@ 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;
@ -49,13 +48,9 @@ class SchemaValidationContext
/** @var Schema */
private $schema;
/** @var InputObjectCircularRefs */
private $inputObjectCircularRefs;
public function __construct(Schema $schema)
{
$this->schema = $schema;
$this->inputObjectCircularRefs = new InputObjectCircularRefs($this);
$this->schema = $schema;
}
/**
@ -104,7 +99,7 @@ class SchemaValidationContext
* @param string $message
* @param Node[]|Node|TypeNode|TypeDefinitionNode|null $nodes
*/
public function reportError($message, $nodes = null)
private function reportError($message, $nodes = null)
{
$nodes = array_filter($nodes && is_array($nodes) ? $nodes : [$nodes]);
$this->addError(new Error($message, $nodes));
@ -252,7 +247,7 @@ class SchemaValidationContext
if (! $type instanceof NamedType) {
$this->reportError(
'Expected GraphQL named type but got: ' . Utils::printSafe($type) . '.',
$type instanceof Type ? $type->astNode : null
is_object($type) ? $type->astNode : null
);
continue;
}
@ -280,9 +275,6 @@ 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);
}
}
}
@ -768,9 +760,8 @@ class SchemaValidationContext
);
}
return $union->astNode
? $union->astNode->types
: null;
return $union->astNode ?
$union->astNode->types : null;
}
private function validateEnumValues(EnumType $enumType)
@ -825,9 +816,8 @@ class SchemaValidationContext
);
}
return $enum->astNode
? $enum->astNode->values
: null;
return $enum->astNode ?
$enum->astNode->values : null;
}
private function validateInputFields(InputObjectType $inputObj)

View file

@ -1,105 +0,0 @@
<?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]);
}
}

View file

@ -183,7 +183,7 @@ class AST
$valuesNodes[] = $itemNode;
}
return new ListValueNode(['values' => new NodeList($valuesNodes)]);
return new ListValueNode(['values' => $valuesNodes]);
}
return self::astFromValue($value, $itemType);
@ -235,7 +235,7 @@ class AST
]);
}
return new ObjectValueNode(['fields' => new NodeList($fieldNodes)]);
return new ObjectValueNode(['fields' => $fieldNodes]);
}
if ($type instanceof ScalarType || $type instanceof EnumType) {
@ -322,7 +322,7 @@ class AST
*
* @api
*/
public static function valueFromAST($valueNode, Type $type, ?array $variables = null)
public static function valueFromAST($valueNode, InputType $type, ?array $variables = null)
{
$undefined = Utils::undefined();
@ -353,7 +353,6 @@ 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.

View file

@ -17,6 +17,7 @@ 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;
@ -143,8 +144,7 @@ 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,20 +244,23 @@ class ASTDefinitionBuilder
*
* @throws Error
*/
private function makeSchemaDef(Node $def)
private function makeSchemaDef($def)
{
switch (true) {
case $def instanceof ObjectTypeDefinitionNode:
if (! $def) {
throw new Error('def must be defined.');
}
switch ($def->kind) {
case NodeKind::OBJECT_TYPE_DEFINITION:
return $this->makeTypeDef($def);
case $def instanceof InterfaceTypeDefinitionNode:
case NodeKind::INTERFACE_TYPE_DEFINITION:
return $this->makeInterfaceDef($def);
case $def instanceof EnumTypeDefinitionNode:
case NodeKind::ENUM_TYPE_DEFINITION:
return $this->makeEnumDef($def);
case $def instanceof UnionTypeDefinitionNode:
case NodeKind::UNION_TYPE_DEFINITION:
return $this->makeUnionDef($def);
case $def instanceof ScalarTypeDefinitionNode:
case NodeKind::SCALAR_TYPE_DEFINITION:
return $this->makeScalarDef($def);
case $def instanceof InputObjectTypeDefinitionNode:
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
return $this->makeInputObjectDef($def);
default:
throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
@ -394,8 +397,8 @@ class ASTDefinitionBuilder
function ($typeNode) {
return $this->buildType($typeNode);
}
)
: [],
) :
[],
'astNode' => $def,
]);
}
@ -427,48 +430,62 @@ class ASTDefinitionBuilder
}
/**
* @param mixed[] $config
* @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeExtensionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode $def
* @param mixed[] $config
*
* @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType
*
* @throws Error
*/
private function makeSchemaDefFromConfig(Node $def, array $config)
private function makeSchemaDefFromConfig($def, array $config)
{
switch (true) {
case $def instanceof ObjectTypeDefinitionNode:
if (! $def) {
throw new Error('def must be defined.');
}
switch ($def->kind) {
case NodeKind::OBJECT_TYPE_DEFINITION:
return new ObjectType($config);
case $def instanceof InterfaceTypeDefinitionNode:
case NodeKind::INTERFACE_TYPE_DEFINITION:
return new InterfaceType($config);
case $def instanceof EnumTypeDefinitionNode:
case NodeKind::ENUM_TYPE_DEFINITION:
return new EnumType($config);
case $def instanceof UnionTypeDefinitionNode:
case NodeKind::UNION_TYPE_DEFINITION:
return new UnionType($config);
case $def instanceof ScalarTypeDefinitionNode:
case NodeKind::SCALAR_TYPE_DEFINITION:
return new CustomScalarType($config);
case $def instanceof InputObjectTypeDefinitionNode:
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
return new InputObjectType($config);
default:
throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
}
}
private function getNamedTypeNode(TypeNode $typeNode) : TypeNode
/**
* @param TypeNode|ListTypeNode|NonNullTypeNode $typeNode
*
* @return TypeNode
*/
private function getNamedTypeNode(TypeNode $typeNode)
{
$namedType = $typeNode;
while ($namedType instanceof ListTypeNode || $namedType instanceof NonNullTypeNode) {
while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) {
$namedType = $namedType->type;
}
return $namedType;
}
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode) : Type
/**
* @param TypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
*
* @return Type
*/
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode)
{
if ($inputTypeNode instanceof ListTypeNode) {
if ($inputTypeNode->kind === NodeKind::LIST_TYPE) {
return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type));
}
if ($inputTypeNode instanceof NonNullTypeNode) {
if ($inputTypeNode->kind === NodeKind::NON_NULL_TYPE) {
$wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
return Type::nonNull(NonNull::assertNullableType($wrappedType));

View file

@ -112,31 +112,27 @@ class BreakingChangesFinder
* @return string[][]
*/
public static function findTypesThatChangedKind(
Schema $schemaA,
Schema $schemaB
) : iterable {
$schemaATypeMap = $schemaA->getTypeMap();
$schemaBTypeMap = $schemaB->getTypeMap();
Schema $oldSchema,
Schema $newSchema
) {
$oldTypeMap = $oldSchema->getTypeMap();
$newTypeMap = $newSchema->getTypeMap();
$breakingChanges = [];
foreach ($schemaATypeMap as $typeName => $schemaAType) {
if (! isset($schemaBTypeMap[$typeName])) {
foreach ($oldTypeMap as $typeName => $oldType) {
if (! isset($newTypeMap[$typeName])) {
continue;
}
$schemaBType = $schemaBTypeMap[$typeName];
if ($schemaAType instanceof $schemaBType) {
$newType = $newTypeMap[$typeName];
if ($oldType instanceof $newType) {
continue;
}
if ($schemaBType instanceof $schemaAType) {
continue;
}
$schemaATypeKindName = self::typeKindName($schemaAType);
$schemaBTypeKindName = self::typeKindName($schemaBType);
$breakingChanges[] = [
$oldTypeKindName = self::typeKindName($oldType);
$newTypeKindName = self::typeKindName($newType);
$breakingChanges[] = [
'type' => self::BREAKING_CHANGE_TYPE_CHANGED_KIND,
'description' => "${typeName} changed from ${schemaATypeKindName} to ${schemaBTypeKindName}.",
'description' => "${typeName} changed from ${oldTypeKindName} to ${newTypeKindName}.",
];
}
@ -531,12 +527,12 @@ class BreakingChangesFinder
];
}
// Check if a non-null arg was added to the field
foreach ($newTypeFields[$fieldName]->args as $newTypeFieldArgDef) {
foreach ($newTypeFields[$fieldName]->args as $newArgDef) {
$oldArgs = $oldTypeFields[$fieldName]->args;
$oldArgDef = Utils::find(
$oldArgs,
static function ($arg) use ($newTypeFieldArgDef) {
return $arg->name === $newTypeFieldArgDef->name;
static function ($arg) use ($newArgDef) {
return $arg->name === $newArgDef->name;
}
);
@ -545,8 +541,8 @@ class BreakingChangesFinder
}
$newTypeName = $newType->name;
$newArgName = $newTypeFieldArgDef->name;
if ($newTypeFieldArgDef->getType() instanceof NonNull) {
$newArgName = $newArgDef->name;
if ($newArgDef->getType() instanceof NonNull) {
$breakingChanges[] = [
'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED,
'description' => "A non-null arg ${newArgName} on ${newTypeName}.${fieldName} was added",
@ -668,7 +664,7 @@ class BreakingChangesFinder
{
$removedArgs = [];
$newArgMap = self::getArgumentMapForDirective($newDirective);
foreach ($oldDirective->args as $arg) {
foreach ((array) $oldDirective->args as $arg) {
if (isset($newArgMap[$arg->name])) {
continue;
}
@ -727,7 +723,7 @@ class BreakingChangesFinder
{
$addedArgs = [];
$oldArgMap = self::getArgumentMapForDirective($oldDirective);
foreach ($newDirective->args as $arg) {
foreach ((array) $newDirective->args as $arg) {
if (isset($oldArgMap[$arg->name])) {
continue;
}

View file

@ -5,16 +5,10 @@ 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\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Type\Definition\Directive;
@ -101,38 +95,39 @@ class BuildSchema
public function buildSchema()
{
/** @var SchemaDefinitionNode $schemaDef */
$schemaDef = null;
$typeDefs = [];
$this->nodeMap = [];
$directiveDefs = [];
foreach ($this->ast->definitions as $definition) {
switch (true) {
case $definition instanceof SchemaDefinitionNode:
if ($schemaDef !== null) {
foreach ($this->ast->definitions as $d) {
switch ($d->kind) {
case NodeKind::SCHEMA_DEFINITION:
if ($schemaDef) {
throw new Error('Must provide only one schema definition.');
}
$schemaDef = $definition;
$schemaDef = $d;
break;
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;
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;
if (! empty($this->nodeMap[$typeName])) {
throw new Error(sprintf('Type "%s" was defined more than once.', $typeName));
}
$typeDefs[] = $definition;
$this->nodeMap[$typeName] = $definition;
$typeDefs[] = $d;
$this->nodeMap[$typeName] = $d;
break;
case $definition instanceof DirectiveDefinitionNode:
$directiveDefs[] = $definition;
case NodeKind::DIRECTIVE_DEFINITION:
$directiveDefs[] = $d;
break;
}
}
$operationTypes = $schemaDef !== null
$operationTypes = $schemaDef
? $this->getOperationTypes($schemaDef)
: [
'query' => isset($this->nodeMap['Query']) ? 'Query' : null,
@ -159,10 +154,9 @@ class BuildSchema
// If specified directives were not explicitly declared, add them.
$skip = array_reduce(
$directives,
static function (bool $hasSkip, Directive $directive) : bool {
static function ($hasSkip, $directive) {
return $hasSkip || $directive->name === 'skip';
},
false
}
);
if (! $skip) {
$directives[] = Directive::skipDirective();
@ -170,10 +164,9 @@ class BuildSchema
$include = array_reduce(
$directives,
static function (bool $hasInclude, Directive $directive) : bool {
static function ($hasInclude, $directive) {
return $hasInclude || $directive->name === 'include';
},
false
}
);
if (! $include) {
$directives[] = Directive::includeDirective();
@ -181,10 +174,9 @@ class BuildSchema
$deprecated = array_reduce(
$directives,
static function (bool $hasDeprecated, Directive $directive) : bool {
static function ($hasDeprecated, $directive) {
return $hasDeprecated || $directive->name === 'deprecated';
},
false
}
);
if (! $deprecated) {
$directives[] = Directive::deprecatedDirective();

View file

@ -21,7 +21,9 @@ 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 rarely be the case and should be avoided when possible)
* (yet this should be really rare case and should be avoided when possible)
*
* Class MixedStore
*/
class MixedStore implements ArrayAccess
{

View file

@ -7,16 +7,13 @@ 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;
@ -69,7 +66,6 @@ class SchemaExtender
return $type->extensionASTNodes;
}
return static::$typeExtensionsMap[$name] ?? null;
}
@ -78,8 +74,8 @@ class SchemaExtender
*/
protected static function checkExtensionNode(Type $type, Node $node) : void
{
switch (true) {
case $node instanceof ObjectTypeExtensionNode:
switch ($node->kind) {
case NodeKind::OBJECT_TYPE_EXTENSION:
if (! ($type instanceof ObjectType)) {
throw new Error(
'Cannot extend non-object type "' . $type->name . '".',
@ -87,7 +83,7 @@ class SchemaExtender
);
}
break;
case $node instanceof InterfaceTypeExtensionNode:
case NodeKind::INTERFACE_TYPE_EXTENSION:
if (! ($type instanceof InterfaceType)) {
throw new Error(
'Cannot extend non-interface type "' . $type->name . '".',
@ -95,7 +91,7 @@ class SchemaExtender
);
}
break;
case $node instanceof EnumTypeExtensionNode:
case NodeKind::ENUM_TYPE_EXTENSION:
if (! ($type instanceof EnumType)) {
throw new Error(
'Cannot extend non-enum type "' . $type->name . '".',
@ -103,7 +99,7 @@ class SchemaExtender
);
}
break;
case $node instanceof UnionTypeExtensionNode:
case NodeKind::UNION_TYPE_EXTENSION:
if (! ($type instanceof UnionType)) {
throw new Error(
'Cannot extend non-union type "' . $type->name . '".',
@ -111,7 +107,7 @@ class SchemaExtender
);
}
break;
case $node instanceof InputObjectTypeExtensionNode:
case NodeKind::INPUT_OBJECT_TYPE_EXTENSION:
if (! ($type instanceof InputObjectType)) {
throw new Error(
'Cannot extend non-input object type "' . $type->name . '".',
@ -288,7 +284,6 @@ class SchemaExtender
}
}
}
return $interfaces;
}
@ -518,14 +513,7 @@ class SchemaExtender
$schemaExtensions[] = $def;
} elseif ($def instanceof TypeDefinitionNode) {
$typeName = isset($def->name) ? $def->name->value : null;
try {
$type = $schema->getType($typeName);
} catch (Error $error) {
$type = null;
}
if ($type) {
if ($schema->getType($typeName)) {
throw new Error('Type "' . $typeName . '" already exists in the schema. It cannot also be defined in this type definition.', [$def]);
}
$typeDefinitionMap[$typeName] = $def;
@ -609,9 +597,7 @@ 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(

View file

@ -153,8 +153,11 @@ class SchemaPrinter
}
$subscriptionType = $schema->getSubscriptionType();
if ($subscriptionType && $subscriptionType->name !== 'Subscription') {
return false;
}
return ! $subscriptionType || $subscriptionType->name === 'Subscription';
return true;
}
private static function printDirective($directive, $options) : string
@ -353,8 +356,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) {
@ -362,8 +365,7 @@ class SchemaPrinter
},
$interfaces
)
)
: '';
) : '';
return self::printDescription($options, $type) .
sprintf("type %s%s {\n%s\n}", $type->name, $implementedInterfaces, self::printFields($options, $type));

View file

@ -86,12 +86,18 @@ class TypeComparators
// If superType type is an abstract type, maybeSubType type may be a currently
// possible object type.
return Type::isAbstractType($superType) &&
if (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;
}
/**
@ -125,11 +131,13 @@ 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);
}

View file

@ -6,21 +6,12 @@ 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;
@ -37,7 +28,6 @@ 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;
@ -45,21 +35,24 @@ 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 */
@ -162,7 +155,6 @@ class TypeInfo
if (! $alreadyInMap) {
$typeMap[$i] = $type;
}
return $typeMap;
}
@ -186,7 +178,7 @@ class TypeInfo
$nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
}
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
foreach ($type->getFields() as $fieldName => $field) {
foreach ((array) $type->getFields() as $fieldName => $field) {
if (! empty($field->args)) {
$fieldArgTypes = array_map(
static function (FieldArgument $arg) {
@ -201,12 +193,12 @@ class TypeInfo
}
}
if ($type instanceof InputObjectType) {
foreach ($type->getFields() as $fieldName => $field) {
foreach ((array) $type->getFields() as $fieldName => $field) {
$nestedTypes[] = $field->getType();
}
}
foreach ($nestedTypes as $nestedType) {
$typeMap = self::extractTypes($nestedType, $typeMap);
foreach ($nestedTypes as $type) {
$typeMap = self::extractTypes($type, $typeMap);
}
return $typeMap;
@ -263,13 +255,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 (true) {
case $node instanceof SelectionSetNode:
switch ($node->kind) {
case NodeKind::SELECTION_SET:
$namedType = Type::getNamedType($this->getType());
$this->parentTypeStack[] = Type::isCompositeType($namedType) ? $namedType : null;
break;
case $node instanceof FieldNode:
case NodeKind::FIELD:
$parentType = $this->getParentType();
$fieldDef = null;
if ($parentType) {
@ -283,11 +275,11 @@ class TypeInfo
$this->typeStack[] = Type::isOutputType($fieldType) ? $fieldType : null;
break;
case $node instanceof DirectiveNode:
case NodeKind::DIRECTIVE:
$this->directive = $schema->getDirective($node->name->value);
break;
case $node instanceof OperationDefinitionNode:
case NodeKind::OPERATION_DEFINITION:
$type = null;
if ($node->operation === 'query') {
$type = $schema->getQueryType();
@ -299,24 +291,22 @@ class TypeInfo
$this->typeStack[] = Type::isOutputType($type) ? $type : null;
break;
case $node instanceof InlineFragmentNode:
case $node instanceof FragmentDefinitionNode:
case NodeKind::INLINE_FRAGMENT:
case NodeKind::FRAGMENT_DEFINITION:
$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 $node instanceof VariableDefinitionNode:
case NodeKind::VARIABLE_DEFINITION:
$inputType = self::typeFromAST($schema, $node->type);
$this->inputTypeStack[] = Type::isInputType($inputType) ? $inputType : null; // push
break;
case $node instanceof ArgumentNode:
case NodeKind::ARGUMENT:
$fieldOrDirective = $this->getDirective() ?: $this->getFieldDef();
$argDef = $argType = null;
if ($fieldOrDirective) {
@ -334,7 +324,7 @@ class TypeInfo
$this->inputTypeStack[] = Type::isInputType($argType) ? $argType : null;
break;
case $node instanceof ListValueNode:
case NodeKind::LST:
$listType = Type::getNullableType($this->getInputType());
$itemType = $listType instanceof ListOfType
? $listType->getWrappedType()
@ -342,7 +332,7 @@ class TypeInfo
$this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null;
break;
case $node instanceof ObjectFieldNode:
case NodeKind::OBJECT_FIELD:
$objectType = Type::getNamedType($this->getInputType());
$fieldType = null;
$inputFieldType = null;
@ -354,7 +344,7 @@ class TypeInfo
$this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null;
break;
case $node instanceof EnumValueNode:
case NodeKind::ENUM:
$enumType = Type::getNamedType($this->getInputType());
$enumValue = null;
if ($enumType instanceof EnumType) {
@ -468,37 +458,37 @@ class TypeInfo
public function leave(Node $node)
{
switch (true) {
case $node instanceof SelectionSetNode:
switch ($node->kind) {
case NodeKind::SELECTION_SET:
array_pop($this->parentTypeStack);
break;
case $node instanceof FieldNode:
case NodeKind::FIELD:
array_pop($this->fieldDefStack);
array_pop($this->typeStack);
break;
case $node instanceof DirectiveNode:
case NodeKind::DIRECTIVE:
$this->directive = null;
break;
case $node instanceof OperationDefinitionNode:
case $node instanceof InlineFragmentNode:
case $node instanceof FragmentDefinitionNode:
case NodeKind::OPERATION_DEFINITION:
case NodeKind::INLINE_FRAGMENT:
case NodeKind::FRAGMENT_DEFINITION:
array_pop($this->typeStack);
break;
case $node instanceof VariableDefinitionNode:
case NodeKind::VARIABLE_DEFINITION:
array_pop($this->inputTypeStack);
break;
case $node instanceof ArgumentNode:
case NodeKind::ARGUMENT:
$this->argument = null;
array_pop($this->inputTypeStack);
break;
case $node instanceof ListValueNode:
case $node instanceof ObjectFieldNode:
case NodeKind::LST:
case NodeKind::OBJECT_FIELD:
array_pop($this->inputTypeStack);
break;
case $node instanceof EnumValueNode:
case NodeKind::ENUM:
$this->enumValue = null;
break;
}

View file

@ -264,8 +264,8 @@ class Utils
$grouped = [];
foreach ($traversable as $key => $value) {
$newKeys = (array) $keyFn($value, $key);
foreach ($newKeys as $newKey) {
$grouped[$newKey][] = $value;
foreach ($newKeys as $key) {
$grouped[$key][] = $value;
}
}

View file

@ -199,7 +199,7 @@ class Value
}
$suggestions = Utils::suggestionList(
(string) $fieldName,
$fieldName,
array_keys($fields)
);
$didYouMean = $suggestions

View file

@ -52,6 +52,6 @@ class DisableIntrospection extends QuerySecurityRule
protected function isEnabled()
{
return $this->isEnabled !== self::DISABLED;
return $this->isEnabled !== static::DISABLED;
}
}

View file

@ -6,8 +6,6 @@ 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;
@ -36,7 +34,7 @@ class KnownArgumentNames extends ValidationRule
}
$argumentOf = $ancestors[count($ancestors) - 1];
if ($argumentOf instanceof FieldNode) {
if ($argumentOf->kind === NodeKind::FIELD) {
$fieldDef = $context->getFieldDef();
$parentType = $context->getParentType();
if ($fieldDef && $parentType) {
@ -58,7 +56,7 @@ class KnownArgumentNames extends ValidationRule
[$node]
));
}
} elseif ($argumentOf instanceof DirectiveNode) {
} elseif ($argumentOf->kind === NodeKind::DIRECTIVE) {
$directive = $context->getDirective();
if ($directive) {
$context->reportError(new Error(

View file

@ -7,31 +7,10 @@ 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;
@ -117,8 +96,8 @@ class KnownDirectives extends ValidationRule
private function getDirectiveLocationForASTPath(array $ancestors)
{
$appliedTo = $ancestors[count($ancestors) - 1];
switch (true) {
case $appliedTo instanceof OperationDefinitionNode:
switch ($appliedTo->kind) {
case NodeKind::OPERATION_DEFINITION:
switch ($appliedTo->operation) {
case 'query':
return DirectiveLocation::QUERY;
@ -128,40 +107,40 @@ class KnownDirectives extends ValidationRule
return DirectiveLocation::SUBSCRIPTION;
}
break;
case $appliedTo instanceof FieldNode:
case NodeKind::FIELD:
return DirectiveLocation::FIELD;
case $appliedTo instanceof FragmentSpreadNode:
case NodeKind::FRAGMENT_SPREAD:
return DirectiveLocation::FRAGMENT_SPREAD;
case $appliedTo instanceof InlineFragmentNode:
case NodeKind::INLINE_FRAGMENT:
return DirectiveLocation::INLINE_FRAGMENT;
case $appliedTo instanceof FragmentDefinitionNode:
case NodeKind::FRAGMENT_DEFINITION:
return DirectiveLocation::FRAGMENT_DEFINITION;
case $appliedTo instanceof SchemaDefinitionNode:
case $appliedTo instanceof SchemaTypeExtensionNode:
case NodeKind::SCHEMA_DEFINITION:
case NodeKind::SCHEMA_EXTENSION:
return DirectiveLocation::SCHEMA;
case $appliedTo instanceof ScalarTypeDefinitionNode:
case $appliedTo instanceof ScalarTypeExtensionNode:
case NodeKind::SCALAR_TYPE_DEFINITION:
case NodeKind::SCALAR_TYPE_EXTENSION:
return DirectiveLocation::SCALAR;
case $appliedTo instanceof ObjectTypeDefinitionNode:
case $appliedTo instanceof ObjectTypeExtensionNode:
case NodeKind::OBJECT_TYPE_DEFINITION:
case NodeKind::OBJECT_TYPE_EXTENSION:
return DirectiveLocation::OBJECT;
case $appliedTo instanceof FieldDefinitionNode:
case NodeKind::FIELD_DEFINITION:
return DirectiveLocation::FIELD_DEFINITION;
case $appliedTo instanceof InterfaceTypeDefinitionNode:
case $appliedTo instanceof InterfaceTypeExtensionNode:
case NodeKind::INTERFACE_TYPE_DEFINITION:
case NodeKind::INTERFACE_TYPE_EXTENSION:
return DirectiveLocation::IFACE;
case $appliedTo instanceof UnionTypeDefinitionNode:
case $appliedTo instanceof UnionTypeExtensionNode:
case NodeKind::UNION_TYPE_DEFINITION:
case NodeKind::UNION_TYPE_EXTENSION:
return DirectiveLocation::UNION;
case $appliedTo instanceof EnumTypeDefinitionNode:
case $appliedTo instanceof EnumTypeExtensionNode:
case NodeKind::ENUM_TYPE_DEFINITION:
case NodeKind::ENUM_TYPE_EXTENSION:
return DirectiveLocation::ENUM;
case $appliedTo instanceof EnumValueDefinitionNode:
case NodeKind::ENUM_VALUE_DEFINITION:
return DirectiveLocation::ENUM_VALUE;
case $appliedTo instanceof InputObjectTypeDefinitionNode:
case $appliedTo instanceof InputObjectTypeExtensionNode:
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
case NodeKind::INPUT_OBJECT_TYPE_EXTENSION:
return DirectiveLocation::INPUT_OBJECT;
case $appliedTo instanceof InputValueDefinitionNode:
case NodeKind::INPUT_VALUE_DEFINITION:
$parentNode = $ancestors[count($ancestors) - 3];
return $parentNode instanceof InputObjectTypeDefinitionNode

View file

@ -30,7 +30,7 @@ class LoneAnonymousOperation extends ValidationRule
$tmp = Utils::filter(
$node->definitions,
static function (Node $definition) {
return $definition instanceof OperationDefinitionNode;
return $definition->kind === NodeKind::OPERATION_DEFINITION;
}
);

Some files were not shown because too many files have changed in this diff Show more