This commit is contained in:
Your Name
2021-07-26 19:46:18 +02:00
parent e7a49138bb
commit aae17f10a6
818 changed files with 70695 additions and 16408 deletions
+2
View File
@@ -0,0 +1,2 @@
coverage_clover: /tmp/coverage/*.xml
json_path: /tmp/coverage/coverage.json
+2
View File
@@ -0,0 +1,2 @@
# These are supported funding model platforms
open_collective: webonyx-graphql-php
@@ -0,0 +1,162 @@
name: CI
on:
push:
branches:
tags:
pull_request:
jobs:
build:
runs-on: ubuntu-18.04
strategy:
matrix:
php: [7.1, 7.2, 7.3, 7.4, 8.0]
env: [
'EXECUTOR= DEPENDENCIES=--prefer-lowest',
'EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest',
'EXECUTOR=',
'EXECUTOR=coroutine',
]
name: PHP ${{ matrix.php }} Test ${{ matrix.env }}
steps:
- uses: actions/checkout@v2
- name: Install PHP
uses: shivammathur/setup-php@2.9.0
with:
php-version: ${{ matrix.php }}
coverage: none
extensions: json, mbstring
- name: Get Composer Cache Directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Remove dependencies not used in this job for PHP 8 compatibility
run: |
composer remove --dev --no-update phpbench/phpbench
composer remove --dev --no-update phpstan/phpstan
composer remove --dev --no-update phpstan/phpstan-phpunit
composer remove --dev --no-update phpstan/phpstan-strict-rules
composer remove --dev --no-update doctrine/coding-standard
- name: Install Dependencies
run: composer update ${DEPENDENCIES}
- name: Run unit tests
run: |
export $ENV
./vendor/bin/phpunit --group default,ReactPromise
env:
ENV: ${{ matrix.env}}
coding-standard:
runs-on: ubuntu-18.04
name: Coding Standard
steps:
- uses: actions/checkout@v2
- name: Install PHP
uses: shivammathur/setup-php@2.9.0
with:
php-version: 7.1
coverage: none
extensions: json, mbstring
- name: Get Composer Cache Directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Dependencies
run: composer install ${DEPENDENCIES}
- name: Coding Standard
run: composer lint
phpstan:
runs-on: ubuntu-18.04
name: PHPStan
steps:
- uses: actions/checkout@v2
- name: Install PHP
uses: shivammathur/setup-php@2.9.0
with:
php-version: 7.1
coverage: none
extensions: json, mbstring
- name: Get Composer Cache Directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Dependencies
run: composer install ${DEPENDENCIES}
- name: PHPStan
run: composer stan
coverage:
runs-on: ubuntu-18.04
name: Code Coverage
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.ref }}
- name: Install PHP
uses: shivammathur/setup-php@2.9.0
with:
php-version: 7.2
coverage: pcov
extensions: json, mbstring
- name: Get Composer Cache Directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Dependencies
run: composer install ${DEPENDENCIES}
- name: Code coverage
run: |
./vendor/bin/phpunit --coverage-clover /tmp/coverage/clover_executor.xml
EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-clover /tmp/coverage/clover_executor-coroutine.xml
- name: Report to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_RUN_LOCALLY: 1
run: vendor/bin/php-coveralls --verbose
+172 -2
View File
@@ -1,8 +1,178 @@
# Changelog
### v0.13.8
#### Unreleased
#### 14.9.0
Feat:
- Add support for type config decorator in `SchemaExtender`
#### 14.8.0
Feat:
- Implement `GraphQL\Utils\AST::getOperationAST()`
#### 14.7.0
Feat:
- Allow providing field definitions as a callable and resolve them lazily
#### 14.6.4
Fix:
- Avoid crashing in `QueryPlan` when `__typename` is used in the query
#### 14.6.3
Refactoring:
- Improve performance of subtype checks
#### 14.6.2
Fix:
- Fix overly eager validation of repeatable directive usage
#### 14.6.1
Fix:
- Add fallback for `directive.isRepeatable` in `BuildClientSchema`
#### 14.6.0
Feat:
- Open ReferenceExecutor for extending
#### 14.5.1
Fix:
- Fix Input Object field shortcut definition with callable (#773)
#### 14.5.0
Feat:
- Implement support for interfaces implementing interfaces (#740), huge kudos to @Kingdutch
Deprecates:
- Constant `BreakingChangeFinder::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT`.
Use `BreakingChangeFinder::BREAKING_CHANGE_IMPLEMENTED_INTERFACE_REMOVED` instead.
Constant value also changed from `INTERFACE_REMOVED_FROM_OBJECT` to `IMPLEMENTED_INTERFACE_REMOVED`.
- Constant `BreakingChangeFinder::DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT`
Use `DANGEROUS_CHANGE_IMPLEMENTED_INTERFACE_ADDED` instead.
Constant value also changed from `INTERFACE_ADDED_TO_OBJECT` to `IMPLEMENTED_INTERFACE_ADDED`.
Refactoring:
- Reify AST node types and remove unneeded nullability (#751)
#### 14.4.1
Fix:
- Allow pushing nodes to `NodeList` via `[]=` (#767)
- Fix signature of `Error\FormattedError::prepareFormatter()` to address PHP8 deprecation (#742)
- Do not add errors key to result when errors discarded by custom error handler (#766)
#### 14.4.0
Fix:
- Fixed `SchemaPrinter` so that it uses late static bindings when extended
- Parse `DirectiveDefinitionNode->locations` as `NodeList<NamedNode>` (fixes AST::fromArray conversion) (#723)
- Parse `Parser::implementsInterfaces` as `NodeList<NamedTypeNode>` (fixes AST::fromArray conversion)
- Fix signature of `Parser::unionMemberTypes` to match actual `NodeList<NamedTypeNode>`
#### v14.3.0
Feat:
- Allow `typeLoader` to return a type thunk (#687)
Fix:
- Read getParsedBody() instead of getBody() when Request is ServerRequest (#715)
- Fix default get/set behavior on InputObjectField and FieldDefinition (#716)
#### v14.2.0
Deprecates:
- Public access to `FieldDefinition::$type` property (#702)
Fixes:
- Fix validation for input field definition directives (#714)
#### v14.1.1
Fixes:
- Handle nullable `DirectiveNode#astNode` in `SchemaValidationContext` (#708)
#### v14.1.0
New:
- Add partial parse functions for const variants (#693)
Fixes:
- Differentiate between client-safe and non-client-safe errors in scalar validation (#706)
- Proper type hints for `IntValueNode` (#691)
Refactoring:
- Ensure NamedTypeNode::$name is always a NameNode (#695)
- Visitor: simplify getVisitFn (#694)
- Replace function calls with type casts (#692)
- Fix "only booleans are allowed" errors (#659)
#### v14.0.2
- Optimize lazy types (#684)
#### v14.0.1
Bug fixes:
- Fix for: Argument defaults with integer/float values crashes introspection query (#679)
- Fix for "Invalid AST Node: false" error (#685)
- Fix double Error wrapping when parsing variables (#688)
Refactoring:
- Do not use call_user_func or call_user_func_array (#676)
- Codestyle and static analysis improvements (#648, #690)
## v14.0.0
This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details.
- **BREAKING/BUGFIX:** Strict coercion of scalar types (#278)
- **BREAKING/BUGFIX:** Spec-compliance: Fixed ambiguity with null variable values and default values (#274)
- **BREAKING:** Removed deprecated directive introspection fields (onOperation, onFragment, onField)
- **BREAKING:** `GraphQL\Deferred` now extends `GraphQL\Executor\Promise\Adapter\SyncPromise`
- **BREAKING:** renamed several types of dangerous/breaking changes (returned by `BreakingChangesFinder`)
- **BREAKING:** Renamed `GraphQL\Error\Debug` to `GraphQL\Error\DebugFlag`.
- **BREAKING:** Debug flags in `GraphQL\Executor\ExecutionResult`, `GraphQL\Error\FormattedError` and `GraphQL\Server\ServerConfig` do not accept `boolean` value anymore but `int` only.
- **BREAKING:** `$positions` in `GraphQL\Error\Error` constructor are not nullable anymore. Same can be expressed by passing an empty array.
Notable features and improvements:
- Compliant with the GraphQL specification [June 2018 Edition](https://spec.graphql.org/June2018/)
- Support repeatable directives (#643)
- Perf: support lazy type definitions (#557)
- Simplified Deferred implementation (now allows chaining like promises, #573)
- Support SDL Validation and other schema validation improvements (e.g. #492)
- Added promise adapter for [Amp](https://amphp.org/) (#551)
- Query plan utility improvements (#513, #632)
Other noteworthy changes:
- Allow retrieving query complexity once query has been completed (#316)
- Allow input types to be passed in from variables using \stdClass instead of associative arrays (#535)
- Support UTF-16 surrogate pairs within string literals (#554, #556)
- Having an empty string in `deprecationReason` will now print the `@deprecated` directive (only a `null` `deprecationReason` won't print the `@deprecated` directive).
- Deprecated Experimental executor (#397)
Also some bugs fixed, heavily invested in [PHPStan](https://github.com/phpstan/phpstan) for static analysis.
Special thanks to @simPod, @spawnia and @shmax for their major contributions!
#### v0.13.9
- Fix double Error wrapping when parsing variables (#689)
#### v0.13.8
- Don't call global field resolver on introspection fields (#481)
### v0.13.7
#### v0.13.7
- Added retrieving query complexity once query has been completed (#316)
- Allow input types to be passed in from variables using \stdClass instead of associative arrays (#535)
+6 -7
View File
@@ -1,6 +1,6 @@
# graphql-php
[![Build Status](https://travis-ci.org/webonyx/graphql-php.svg?branch=master)](https://travis-ci.org/webonyx/graphql-php)
[![Code Coverage](https://scrutinizer-ci.com/g/webonyx/graphql-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/webonyx/graphql-php)
![CI](https://github.com/webonyx/graphql-php/workflows/CI/badge.svg)
[![Coverage Status](https://coveralls.io/repos/github/webonyx/graphql-php/badge.svg?branch=master)](https://coveralls.io/github/webonyx/graphql-php?branch=master)
[![Latest Stable Version](https://poser.pugx.org/webonyx/graphql-php/version)](https://packagist.org/packages/webonyx/graphql-php)
[![License](https://poser.pugx.org/webonyx/graphql-php/license)](https://packagist.org/packages/webonyx/graphql-php)
@@ -17,12 +17,11 @@ composer require webonyx/graphql-php
Full documentation is available on the [Documentation site](https://webonyx.github.io/graphql-php/) as well
as in the [docs](docs/) folder of the distribution.
If you don't know what GraphQL is, visit this [official website](http://graphql.org)
by the Facebook engineering team.
If you don't know what GraphQL is, visit the [official website](http://graphql.org) first.
## Examples
There are several ready examples in the [examples](examples/) folder of the distribution with specific
README file per example.
There are several ready examples in the [examples](examples) folder of the distribution,
with a specific README file per example.
## Contributors
@@ -49,4 +48,4 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
## License
See [LICENCE](LICENSE).
See [LICENSE](LICENSE).
+204
View File
@@ -1,3 +1,207 @@
## v0.13.x > v14.x.x
### BREAKING: Strict coercion of scalar types (#278)
**Impact: Major**
This change may break API clients if they were sending loose variable values.
<details>
<summary>See Examples</summary>
Consider the following query:
```graphql
query($intQueryVariable: Int) {
test(intInput: $intQueryVariable)
}
```
What happens if we pass non-integer values as `$intQueryVariable`:
```
[true, false, 1, 0, 0.0, 'true', 'false', '1', '0', '0.0', [], [0,1]]
```
#### Integer coercion, changed behavior:
```
bool(true):
0.13.x: coerced to int(1)
14.x.x: Error: Variable "$queryVariable" got invalid value true; Expected type Int; Int cannot represent non-integer value: true
bool(false):
0.13.x: coerced to int(0)
14.x.x: Error: Variable "$queryVariable" got invalid value false; Expected type Int; Int cannot represent non-integer value: false
string(1) "1"
0.13.x: was coerced to int(1)
14.x.x: Error: Variable "$queryVariable" got invalid value "1"; Expected type Int; Int cannot represent non-integer value: 1
string(1) "0"
0.13.x: was coerced to int(0)
14.x.x: Error: Variable "$queryVariable" got invalid value "0"; Expected type Int; Int cannot represent non-integer value: 0
string(3) "0.0"
0.13.x: was coerced to int(0)
14.x.x: Error: Variable "$queryVariable" got invalid value "0.0"; Expected type Int; Int cannot represent non-integer value: 0.0
```
Did not change:
```
int(1): coerced to int(1)
int(0) was coerced to int(0)
float(0) was coerced to int(0)
string(4) "true":
Error: Variable "$queryVariable" got invalid value "true"; Expected type Int; Int cannot represent non 32-bit signed integer value: true
string(5) "false":
Error: Variable "$queryVariable" got invalid value "false"; Expected type Int; Int cannot represent non 32-bit signed integer value: false
array(0) {}
Error: Variable "$queryVariable" got invalid value []; Expected type Int; Int cannot represent non 32-bit signed integer value: []
array(2) { [0]=> int(0) [1]=> int(1) }
Error: Variable "$queryVariable" got invalid value [0,1]; Expected type Int; Int cannot represent non 32-bit signed integer value: [0,1]
```
#### Float coercion, changed behavior:
```graphql
query($queryVariable: Float) {
test(floatInput: $queryVariable)
}
```
```
bool(true)
0.13.x: was coerced to float(1)
14.x.x: Error: Variable "$queryVariable" got invalid value true; Expected type Float; Float cannot represent non numeric value: true
bool(false)
0.13.x: was coerced to float(0)
14.x.x: Error: Variable "$queryVariable" got invalid value false; Expected type Float; Float cannot represent non numeric value: false
string(1) "1"
0.13.x: was coerced to float(1)
14.x.x: Error: Variable "$queryVariable" got invalid value "1"; Expected type Float; Float cannot represent non numeric value: 1
string(1) "0"
0.13.x: was coerced to float(0)
14.x.x: Error: Variable "$queryVariable" got invalid value "0"; Expected type Float; Float cannot represent non numeric value: 0
string(3) "0.0"
0.13.x: was coerced to float(0)
14.x.x: Error: Variable "$queryVariable" got invalid value "0.0"; Expected type Float; Float cannot represent non numeric value: 0.0
```
#### String coercion, changed behavior:
```graphql
query($queryVariable: String) {
test(stringInput: $queryVariable)
}
```
```
bool(true)
0.13.x: was coerced to string(1) "1"
14.x.x: Error: Variable "$queryVariable" got invalid value true; Expected type String; String cannot represent a non string value: true
bool(false)
0.13.x: was coerced to string(0) ""
14.x.x: Error: Variable "$queryVariable" got invalid value false; Expected type String; String cannot represent a non string value: false
int(1)
0.13.x: was coerced to string(1) "1"
14.x.x: Error: Variable "$queryVariable" got invalid value 1; Expected type String; String cannot represent a non string value: 1
int(0)
0.13.x: was coerced to string(1) "0"
14.x.x: Error: Variable "$queryVariable" got invalid value 0; Expected type String; String cannot represent a non string value: 0
float(0)
0.13.x: was coerced to string(1) "0"
14.x.x: Error: Variable "$queryVariable" got invalid value 0; Expected type String; String cannot represent a non string value: 0
```
#### Boolean coercion did not change.
</details>
### Breaking: renamed classes and changed signatures
**Impact: Medium**
- Dropped previously deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`.
- Renamed `GraphQL\Error\Debug` to `GraphQL\Error\DebugFlag`.
- Debug flags in `GraphQL\Executor\ExecutionResult`, `GraphQL\Error\FormattedError` and `GraphQL\Server\ServerConfig`
do not accept `boolean` value anymore but `int` only (pass values of `GraphQL\Error\DebugFlag` constants)
- `$positions` in `GraphQL\Error\Error` are not nullable anymore. Same can be expressesed by passing empty array.
### BREAKING: Removed deprecated directive introspection fields (onOperation, onFragment, onField)
**Impact: Minor**
Could affect developer tools relying on old introspection format.
Replaced with [Directive Locations](https://spec.graphql.org/June2018/#sec-Type-System.Directives).
### BREAKING: Changes in validation rules:
**Impact: Minor**
- Removal of `VariablesDefaultValueAllowed` validation rule. All variables may now specify a default value.
- Renamed `ProvidedNonNullArguments` to `ProvidedRequiredArguments` (no longer require values to be provided to non-null arguments which provide a default value).
Could affect projects using custom sets of validation rules.
### BREAKING: `GraphQL\Deferred` now extends `GraphQL\Executor\Promise\Adapter\SyncPromise`
**Impact: Minor**
Can only affect a few projects that were somehow customizing deferreds or the default sync promise adapter.
### BREAKING: renamed several types of dangerous/breaking changes (returned by `BreakingChangesFinder`):
**Impact: Minor**
Can affect projects relying on `BreakingChangesFinder` utility in their CI.
Following types of changes were renamed:
```
- `NON_NULL_ARG_ADDED` to `REQUIRED_ARG_ADDED`
- `NON_NULL_INPUT_FIELD_ADDED` to `REQUIRED_INPUT_FIELD_ADDED`
- `NON_NULL_DIRECTIVE_ARG_ADDED` to `REQUIRED_DIRECTIVE_ARG_ADDED`
- `NULLABLE_INPUT_FIELD_ADDED` to `OPTIONAL_INPUT_FIELD_ADDED`
- `NULLABLE_ARG_ADDED` to `OPTIONAL_ARG_ADDED`
```
### Breaking: Dropped `GraphQL\Error\Error::$message`
**Impact: Minor**
Use `GraphQL\Error\Error->getMessage()` instead.
### Breaking: change TypeKind constants
**Impact: Minor**
The constants in `\GraphQL\Type\TypeKind` were partly renamed and their values
have been changed to match their name instead of a numeric index.
### Breaking: some error messages were changed
**Impact: Minor**
Can affect projects relying on error messages parsing.
One example: added quotes around `parentType.fieldName` in error message:
```diff
- Cannot return null for non-nullable field parentType.fieldName.
+ Cannot return null for non-nullable field "parentType.fieldName".
```
But expect other simiar changes like this.
## Upgrade v0.12.x > v0.13.x
### Breaking (major): minimum supported version of PHP
+15 -10
View File
@@ -14,15 +14,19 @@
"ext-mbstring": "*"
},
"require-dev": {
"amphp/amp": "^2.3",
"doctrine/coding-standard": "^6.0",
"phpbench/phpbench": "^0.14.0",
"phpstan/phpstan": "^0.11.4",
"phpstan/phpstan-phpunit": "^0.11.0",
"phpstan/phpstan-strict-rules": "^0.11.0",
"phpunit/phpcov": "^5.0",
"phpunit/phpunit": "^7.2",
"nyholm/psr7": "^1.2",
"phpbench/phpbench": "^0.16.10",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "0.12.82",
"phpstan/phpstan-phpunit": "0.12.18",
"phpstan/phpstan-strict-rules": "0.12.9",
"phpunit/phpunit": "^7.2|^8.5",
"psr/http-message": "^1.0",
"react/promise": "2.*"
"react/promise": "2.*",
"simpod/php-coveralls-mirror": "^3.0",
"squizlabs/php_codesniffer": "3.5.4"
},
"config": {
"preferred-install": "dist",
@@ -49,8 +53,9 @@
"bench": "phpbench run .",
"test": "phpunit",
"lint" : "phpcs",
"fix-style" : "phpcbf",
"static-analysis": "phpstan analyse --ansi --memory-limit 256M",
"check-all": "composer lint && composer static-analysis && composer test"
"fix" : "phpcbf",
"stan": "phpstan --ansi",
"baseline": "phpstan --ansi --generate-baseline",
"check": "composer lint && composer stan && composer test"
}
}
+1 -11
View File
@@ -1,13 +1,3 @@
# Config Validation
Defining types using arrays may be error-prone, but **graphql-php** provides config validation
tool to report when config has unexpected structure.
This validation tool is **disabled by default** because it is time-consuming operation which only
makes sense during development.
To enable validation - call: `GraphQL\Type\Definition\Config::enableValidation();` in your bootstrap
but make sure to restrict it to debug/development mode only.
# Type Registry
**graphql-php** expects that each type in Schema is presented by single instance. Therefore
if you define your types as separate PHP classes you need to ensure that each type is referenced only once.
@@ -16,4 +6,4 @@ Technically you can create several instances of your type (for example for tests
will throw on attempt to add different instances with the same name.
There are several ways to achieve this depending on your preferences. We provide reference
implementation below that introduces TypeRegistry class:
implementation below that introduces TypeRegistry class:
@@ -3,8 +3,10 @@
* [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
* [Siler](https://github.com/leocavalcante/siler) - Straightforward way to map GraphQL SDL to resolver callables, also built-in support for Swoole
# GraphQL PHP Tools
@@ -23,3 +25,4 @@
* [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp)
GraphiQL as Google Chrome extension
* [Altair GraphQL Client](https://altair.sirmuel.design/) - A beautiful feature-rich GraphQL Client for all platforms
+16 -15
View File
@@ -103,23 +103,25 @@ for a field you simply override this default resolver.
**graphql-php** provides following default field resolver:
```php
<?php
function defaultFieldResolver($source, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
function defaultFieldResolver($objectValue, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
if (is_array($source) || $source instanceof \ArrayAccess) {
if (isset($source[$fieldName])) {
$property = $source[$fieldName];
}
} else if (is_object($source)) {
if (isset($source->{$fieldName})) {
$property = $source->{$fieldName};
if (is_array($objectValue) || $objectValue instanceof \ArrayAccess) {
if (isset($objectValue[$fieldName])) {
$property = $objectValue[$fieldName];
}
} elseif (is_object($objectValue)) {
if (isset($objectValue->{$fieldName})) {
$property = $objectValue->{$fieldName};
}
}
return $property instanceof Closure
? $property($objectValue, $args, $context, $info)
: $property;
}
return $property instanceof Closure ? $property($source, $args, $context, $info) : $property;
}
```
As you see it returns value by key (for arrays) or property (for objects).
@@ -161,7 +163,6 @@ $userType = new ObjectType([
Keep in mind that **field resolver** has precedence over **default field resolver per type** which in turn
has precedence over **default field resolver**.
# Solving N+1 Problem
Since: 0.9.0
+15 -9
View File
@@ -17,7 +17,9 @@ By default, each error entry is converted to an associative array with following
<?php
[
'message' => 'Error message',
'category' => 'graphql',
'extensions' => [
'category' => 'graphql'
],
'locations' => [
['line' => 1, 'column' => 2]
],
@@ -67,7 +69,9 @@ When such exception is thrown it will be reported with a full error message:
<?php
[
'message' => 'My reported error',
'category' => 'businessLogic',
'extensions' => [
'category' => 'businessLogic'
],
'locations' => [
['line' => 10, 'column' => 2]
],
@@ -86,12 +90,12 @@ GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error");
# Debugging tools
During development or debugging use `$result->toArray(true)` to add **debugMessage** key to
During development or debugging use `$result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)` to add **debugMessage** key to
each formatted error entry. If you also want to add exception trace - pass flags instead:
```
use GraphQL\Error\Debug;
$debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE;
```php
use GraphQL\Error\DebugFlag;
$debug = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
$result = GraphQL::executeQuery(/*args*/)->toArray($debug);
```
@@ -101,7 +105,9 @@ This will make each error entry to look like this:
[
'debugMessage' => 'Actual exception message',
'message' => 'Internal server error',
'category' => 'internal',
'extensions' => [
'category' => 'internal'
],
'locations' => [
['line' => 10, 'column' => 2]
],
@@ -120,8 +126,8 @@ If you prefer the first resolver exception to be re-thrown, use following flags:
```php
<?php
use GraphQL\GraphQL;
use GraphQL\Error\Debug;
$debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::RETHROW_INTERNAL_EXCEPTIONS;
use GraphQL\Error\DebugFlag;
$debug = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::RETHROW_INTERNAL_EXCEPTIONS;
// Following will throw if there was an exception in resolver during execution:
$result = GraphQL::executeQuery(/*args*/)->toArray($debug);
+5 -5
View File
@@ -66,11 +66,11 @@ Server also supports [PSR-7 request/response interfaces](http://www.php-fig.org/
<?php
use GraphQL\Server\StandardServer;
use GraphQL\Executor\ExecutionResult;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
/** @var ServerRequestInterface $psrRequest */
/** @var RequestInterface $psrRequest */
/** @var ResponseInterface $psrResponse */
/** @var StreamInterface $psrBodyStream */
$server = new StandardServer([/* server options, see below */]);
@@ -86,9 +86,9 @@ $psrResponse = new SomePsr7ResponseImplementation(json_encode($result));
PSR-7 is useful when you want to integrate the server into existing framework:
- [PSR-7 for Laravel](https://laravel.com/docs/5.1/requests#psr7-requests)
- [PSR-7 for Laravel](https://laravel.com/docs/requests#psr7-requests)
- [Symfony PSR-7 Bridge](https://symfony.com/doc/current/components/psr7.html)
- [Slim](https://www.slimframework.com/docs/concepts/value-objects.html)
- [Slim](https://www.slimframework.com/docs/v4/concepts/value-objects.html)
- [Zend Expressive](http://zendframework.github.io/zend-expressive/)
## Server configuration options
@@ -120,7 +120,7 @@ use GraphQL\Server\StandardServer;
$config = ServerConfig::create()
->setSchema($schema)
->setErrorFormatter($myFormatter)
->setDebug($debug)
->setDebugFlag($debug)
;
$server = new StandardServer($config);
+2 -2
View File
@@ -54,8 +54,8 @@ $queryType = new ObjectType([
'args' => [
'message' => Type::nonNull(Type::string()),
],
'resolve' => function ($root, $args) {
return $root['prefix'] . $args['message'];
'resolve' => function ($rootValue, $args) {
return $rootValue['prefix'] . $args['message'];
}
],
],
+146 -150
View File
@@ -20,9 +20,11 @@ See [related documentation](executing-queries.md).
* rootValue:
* The value provided as the first argument to resolver functions on the top
* level type (e.g. the query object type).
* context:
* The value provided as the third argument to all resolvers.
* Use this to pass current session, user data, etc
* contextValue:
* The context value is provided as an argument to resolver functions after
* field arguments. It is used to pass shared information useful at any point
* during executing this query, for example the currently logged in user and
* connections to databases or other services.
* variableValues:
* A mapping of variable name to runtime value to use for all variables
* defined in the requestString.
@@ -41,7 +43,7 @@ See [related documentation](executing-queries.md).
*
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param mixed $contextValue
* @param mixed[]|null $variableValues
* @param ValidationRule[] $validationRules
*
@@ -51,12 +53,12 @@ static function executeQuery(
GraphQL\Type\Schema $schema,
$source,
$rootValue = null,
$context = null,
$contextValue = null,
$variableValues = null,
string $operationName = null,
callable $fieldResolver = null,
array $validationRules = null
)
): GraphQL\Executor\ExecutionResult
```
```php
@@ -82,7 +84,7 @@ static function promiseToExecute(
string $operationName = null,
callable $fieldResolver = null,
array $validationRules = null
)
): GraphQL\Executor\Promise\Promise
```
```php
@@ -93,7 +95,7 @@ static function promiseToExecute(
*
* @api
*/
static function getStandardDirectives()
static function getStandardDirectives(): array
```
```php
@@ -104,7 +106,7 @@ static function getStandardDirectives()
*
* @api
*/
static function getStandardTypes()
static function getStandardTypes(): array
```
```php
@@ -112,7 +114,7 @@ static function getStandardTypes()
* Replaces standard types with types from this list (matching by name)
* Standard types not listed here remain untouched.
*
* @param Type[] $types
* @param array<string, ScalarType> $types
*
* @api
*/
@@ -127,7 +129,7 @@ static function overrideStandardTypes(array $types)
*
* @api
*/
static function getStandardValidationRules()
static function getStandardValidationRules(): array
```
```php
@@ -136,7 +138,7 @@ static function getStandardValidationRules()
*
* @api
*/
static function setDefaultFieldResolver(callable $fn)
static function setDefaultFieldResolver(callable $fn): void
```
# GraphQL\Type\Definition\Type
Registry of standard GraphQL types
@@ -145,157 +147,114 @@ and a base class for all other types.
**Class Methods:**
```php
/**
* @return IDType
*
* @api
*/
static function id()
static function id(): GraphQL\Type\Definition\ScalarType
```
```php
/**
* @return StringType
*
* @api
*/
static function string()
static function string(): GraphQL\Type\Definition\ScalarType
```
```php
/**
* @return BooleanType
*
* @api
*/
static function boolean()
static function boolean(): GraphQL\Type\Definition\ScalarType
```
```php
/**
* @return IntType
*
* @api
*/
static function int()
static function int(): GraphQL\Type\Definition\ScalarType
```
```php
/**
* @return FloatType
*
* @api
*/
static function float()
static function float(): GraphQL\Type\Definition\ScalarType
```
```php
/**
* @param Type|ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType|NonNull $wrappedType
*
* @return ListOfType
*
* @api
*/
static function listOf($wrappedType)
static function listOf(GraphQL\Type\Definition\Type $wrappedType): GraphQL\Type\Definition\ListOfType
```
```php
/**
* @param NullableType $wrappedType
*
* @return NonNull
* @param callable|NullableType $wrappedType
*
* @api
*/
static function nonNull($wrappedType)
static function nonNull($wrappedType): GraphQL\Type\Definition\NonNull
```
```php
/**
* @param Type $type
*
* @return bool
*
* @api
*/
static function isInputType($type)
static function isInputType($type): bool
```
```php
/**
* @param Type $type
*
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType
*
* @api
*/
static function getNamedType($type)
static function getNamedType($type): GraphQL\Type\Definition\Type
```
```php
/**
* @param Type $type
*
* @return bool
*
* @api
*/
static function isOutputType($type)
static function isOutputType($type): bool
```
```php
/**
* @param Type $type
*
* @return bool
*
* @api
*/
static function isLeafType($type)
static function isLeafType($type): bool
```
```php
/**
* @param Type $type
*
* @return bool
*
* @api
*/
static function isCompositeType($type)
static function isCompositeType($type): bool
```
```php
/**
* @param Type $type
*
* @return bool
*
* @api
*/
static function isAbstractType($type)
static function isAbstractType($type): bool
```
```php
/**
* @param Type $type
*
* @return bool
*
* @api
*/
static function isType($type)
```
```php
/**
* @param Type $type
*
* @return NullableType
*
* @api
*/
static function getNullableType($type)
static function getNullableType(GraphQL\Type\Definition\Type $type): GraphQL\Type\Definition\Type
```
# GraphQL\Type\Definition\ResolveInfo
Structure containing information useful for field resolution process.
@@ -304,6 +263,14 @@ Passed as 4th argument to every field resolver. See [docs on field resolving (da
**Class Props:**
```php
/**
* The definition of the field being resolved.
*
* @api
* @var FieldDefinition
*/
public $fieldDefinition;
/**
* The name of the field being resolved.
*
@@ -312,6 +279,14 @@ Passed as 4th argument to every field resolver. See [docs on field resolving (da
*/
public $fieldName;
/**
* Expected return type of the field being resolved.
*
* @api
* @var Type
*/
public $returnType;
/**
* AST of all nodes referencing this field in the query.
*
@@ -320,14 +295,6 @@ public $fieldName;
*/
public $fieldNodes;
/**
* Expected return type of the field being resolved.
*
* @api
* @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull
*/
public $returnType;
/**
* Parent type of the field being resolved.
*
@@ -340,7 +307,7 @@ public $parentType;
* Path to this field from the very root value.
*
* @api
* @var string[][]
* @var string[]
*/
public $path;
@@ -420,7 +387,7 @@ public $variableValues;
*
* @param int $depth How many levels to include in output
*
* @return bool[]
* @return array<string, mixed>
*
* @api
*/
@@ -438,6 +405,7 @@ const FIELD = "FIELD";
const FRAGMENT_DEFINITION = "FRAGMENT_DEFINITION";
const FRAGMENT_SPREAD = "FRAGMENT_SPREAD";
const INLINE_FRAGMENT = "INLINE_FRAGMENT";
const VARIABLE_DEFINITION = "VARIABLE_DEFINITION";
const SCHEMA = "SCHEMA";
const SCALAR = "SCALAR";
const OBJECT = "OBJECT";
@@ -481,7 +449,7 @@ static function create(array $options = [])
```php
/**
* @return ObjectType
* @return ObjectType|null
*
* @api
*/
@@ -490,7 +458,7 @@ function getQuery()
```php
/**
* @param ObjectType $query
* @param ObjectType|null $query
*
* @return SchemaConfig
*
@@ -501,7 +469,7 @@ function setQuery($query)
```php
/**
* @return ObjectType
* @return ObjectType|null
*
* @api
*/
@@ -510,7 +478,7 @@ function getMutation()
```php
/**
* @param ObjectType $mutation
* @param ObjectType|null $mutation
*
* @return SchemaConfig
*
@@ -521,7 +489,7 @@ function setMutation($mutation)
```php
/**
* @return ObjectType
* @return ObjectType|null
*
* @api
*/
@@ -530,7 +498,7 @@ function getSubscription()
```php
/**
* @param ObjectType $subscription
* @param ObjectType|null $subscription
*
* @return SchemaConfig
*
@@ -541,7 +509,7 @@ function setSubscription($subscription)
```php
/**
* @return Type[]
* @return Type[]|callable
*
* @api
*/
@@ -561,7 +529,7 @@ function setTypes($types)
```php
/**
* @return Directive[]
* @return Directive[]|null
*
* @api
*/
@@ -581,7 +549,7 @@ function setDirectives(array $directives)
```php
/**
* @return callable
* @return callable(string $name):Type|null
*
* @api
*/
@@ -659,7 +627,7 @@ function getDirectives()
*
* @api
*/
function getQueryType()
function getQueryType(): GraphQL\Type\Definition\Type
```
```php
@@ -670,7 +638,7 @@ function getQueryType()
*
* @api
*/
function getMutationType()
function getMutationType(): GraphQL\Type\Definition\Type
```
```php
@@ -681,7 +649,7 @@ function getMutationType()
*
* @api
*/
function getSubscriptionType()
function getSubscriptionType(): GraphQL\Type\Definition\Type
```
```php
@@ -695,15 +663,11 @@ function getConfig()
```php
/**
* Returns type by it's name
*
* @param string $name
*
* @return Type|null
* Returns type by its name
*
* @api
*/
function getType($name)
function getType(string $name): GraphQL\Type\Definition\Type
```
```php
@@ -713,11 +677,13 @@ function getType($name)
*
* This operation requires full schema scan. Do not use in production environment.
*
* @return ObjectType[]
* @param InterfaceType|UnionType $abstractType
*
* @return array<Type&ObjectType>
*
* @api
*/
function getPossibleTypes(GraphQL\Type\Definition\AbstractType $abstractType)
function getPossibleTypes(GraphQL\Type\Definition\Type $abstractType): array
```
```php
@@ -725,27 +691,21 @@ function getPossibleTypes(GraphQL\Type\Definition\AbstractType $abstractType)
* Returns true if object type is concrete type of given abstract type
* (implementation for interfaces and members of union type for unions)
*
* @return bool
*
* @api
*/
function isPossibleType(
GraphQL\Type\Definition\AbstractType $abstractType,
GraphQL\Type\Definition\ObjectType $possibleType
)
): bool
```
```php
/**
* Returns instance of directive by name
*
* @param string $name
*
* @return Directive
*
* @api
*/
function getDirective($name)
function getDirective(string $name): GraphQL\Type\Definition\Directive
```
```php
@@ -776,6 +736,42 @@ function validate()
# GraphQL\Language\Parser
Parses string containing GraphQL query or [type definition](type-system/type-language.md) to Abstract Syntax Tree.
Those magic functions allow partial parsing:
@method static DocumentNode document(Source|string $source, bool[] $options = [])
@method static ExecutableDefinitionNode executableDefinition(Source|string $source, bool[] $options = [])
@method static string operationType(Source|string $source, bool[] $options = [])
@method static VariableDefinitionNode variableDefinition(Source|string $source, bool[] $options = [])
@method static SelectionSetNode selectionSet(Source|string $source, bool[] $options = [])
@method static FieldNode field(Source|string $source, bool[] $options = [])
@method static NodeList<ArgumentNode> constArguments(Source|string $source, bool[] $options = [])
@method static ArgumentNode constArgument(Source|string $source, bool[] $options = [])
@method static FragmentDefinitionNode fragmentDefinition(Source|string $source, bool[] $options = [])
@method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|NullValueNode|ObjectValueNode|StringValueNode|VariableNode valueLiteral(Source|string $source, bool[] $options = [])
@method static StringValueNode stringLiteral(Source|string $source, bool[] $options = [])
@method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode variableValue(Source|string $source, bool[] $options = [])
@method static ListValueNode constArray(Source|string $source, bool[] $options = [])
@method static ObjectValueNode constObject(Source|string $source, bool[] $options = [])
@method static ObjectFieldNode constObjectField(Source|string $source, bool[] $options = [])
@method static NodeList<DirectiveNode> constDirectives(Source|string $source, bool[] $options = [])
@method static DirectiveNode constDirective(Source|string $source, bool[] $options = [])
@method static NamedTypeNode namedType(Source|string $source, bool[] $options = [])
@method static StringValueNode|null description(Source|string $source, bool[] $options = [])
@method static OperationTypeDefinitionNode operationTypeDefinition(Source|string $source, bool[] $options = [])
@method static ObjectTypeDefinitionNode objectTypeDefinition(Source|string $source, bool[] $options = [])
@method static NodeList<FieldDefinitionNode> fieldsDefinition(Source|string $source, bool[] $options = [])
@method static NodeList<InputValueDefinitionNode> argumentsDefinition(Source|string $source, bool[] $options = [])
@method static InterfaceTypeDefinitionNode interfaceTypeDefinition(Source|string $source, bool[] $options = [])
@method static NamedTypeNode[] unionMemberTypes(Source|string $source, bool[] $options = [])
@method static NodeList<EnumValueDefinitionNode> enumValuesDefinition(Source|string $source, bool[] $options = [])
@method static InputObjectTypeDefinitionNode inputObjectTypeDefinition(Source|string $source, bool[] $options = [])
@method static TypeExtensionNode typeExtension(Source|string $source, bool[] $options = [])
@method static ScalarTypeExtensionNode scalarTypeExtension(Source|string $source, bool[] $options = [])
@method static InterfaceTypeExtensionNode interfaceTypeExtension(Source|string $source, bool[] $options = [])
@method static EnumTypeExtensionNode enumTypeExtension(Source|string $source, bool[] $options = [])
@method static DirectiveDefinitionNode directiveDefinition(Source|string $source, bool[] $options = [])
@method static DirectiveLocation directiveLocation(Source|string $source, bool[] $options = [])
**Class Methods:**
```php
/**
@@ -866,7 +862,7 @@ static function parseValue($source, array $options = [])
* @param Source|string $source
* @param bool[] $options
*
* @return ListTypeNode|NameNode|NonNullTypeNode
* @return ListTypeNode|NamedTypeNode|NonNullTypeNode
*
* @api
*/
@@ -1229,16 +1225,13 @@ function setErrorsHandler(callable $handler)
* If debug argument is passed, output of error formatter is enriched which debugging information
* ("debugMessage", "trace" keys depending on flags).
*
* $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from
* GraphQL\Error\Debug
*
* @param bool|int $debug
* $debug argument must sum of flags from @see \GraphQL\Error\DebugFlag
*
* @return mixed[]
*
* @api
*/
function toArray($debug = false)
function toArray(int $debug = "GraphQL\Error\DebugFlag::NONE"): array
```
# GraphQL\Executor\Promise\PromiseAdapter
Provides a means for integration of async PHP platforms ([related docs](data-fetching.md#async-php))
@@ -1487,7 +1480,7 @@ const ALL = 63;
*
* @api
*/
static function setWarningHandler(callable $warningHandler = null)
static function setWarningHandler(callable $warningHandler = null): void
```
```php
@@ -1503,7 +1496,7 @@ static function setWarningHandler(callable $warningHandler = null)
*
* @api
*/
static function suppress($suppress = true)
static function suppress($suppress = true): void
```
```php
@@ -1519,7 +1512,7 @@ static function suppress($suppress = true)
*
* @api
*/
static function enable($enable = true)
static function enable($enable = true): void
```
# GraphQL\Error\ClientAware
This interface is used for [default error formatting](error-handling.md).
@@ -1553,11 +1546,12 @@ function isClientSafe()
*/
function getCategory()
```
# GraphQL\Error\Debug
# GraphQL\Error\DebugFlag
Collection of flags for [error debugging](error-handling.md#debugging-tools).
**Class Constants:**
```php
const NONE = 0;
const INCLUDE_DEBUG_MESSAGE = 1;
const INCLUDE_TRACE = 2;
const RETHROW_INTERNAL_EXCEPTIONS = 4;
@@ -1590,11 +1584,9 @@ static function setInternalErrorMessage($msg)
* This method only exposes exception message when exception implements ClientAware interface
* (or when debug flags are passed).
*
* For a list of available debug flags see GraphQL\Error\Debug constants.
* For a list of available debug flags @see \GraphQL\Error\DebugFlag constants.
*
* @param Throwable $e
* @param bool|int $debug
* @param string $internalErrorMessage
* @param string $internalErrorMessage
*
* @return mixed[]
*
@@ -1602,7 +1594,11 @@ static function setInternalErrorMessage($msg)
*
* @api
*/
static function createFromException($e, $debug = false, $internalErrorMessage = null)
static function createFromException(
Throwable $exception,
int $debug = "GraphQL\Error\DebugFlag::NONE",
$internalErrorMessage = null
): array
```
```php
@@ -1718,7 +1714,7 @@ function executeRequest($parsedBody = null)
* @api
*/
function processPsrRequest(
Psr\Http\Message\ServerRequestInterface $request,
Psr\Http\Message\RequestInterface $request,
Psr\Http\Message\ResponseInterface $response,
Psr\Http\Message\StreamInterface $writableBodyStream
)
@@ -1733,7 +1729,7 @@ function processPsrRequest(
*
* @api
*/
function executePsrRequest(Psr\Http\Message\ServerRequestInterface $request)
function executePsrRequest(Psr\Http\Message\RequestInterface $request)
```
```php
@@ -1832,7 +1828,7 @@ function setErrorsHandler(callable $handler)
/**
* Set validation rules for this server.
*
* @param ValidationRule[]|callable $validationRules
* @param ValidationRule[]|callable|null $validationRules
*
* @return self
*
@@ -1865,15 +1861,11 @@ function setPersistentQueryLoader(callable $persistentQueryLoader)
```php
/**
* Set response debug flags. See GraphQL\Error\Debug class for a list of all available flags
*
* @param bool|int $set
*
* @return self
* Set response debug flags. @see \GraphQL\Error\DebugFlag class for a list of all available flags
*
* @api
*/
function setDebug($set = true)
function setDebugFlag(int $debugFlag = "GraphQL\Error\DebugFlag::INCLUDE_DEBUG_MESSAGE"): self
```
```php
@@ -1882,7 +1874,7 @@ function setDebug($set = true)
*
* @api
*/
function setQueryBatching(bool $enableBatching)
function setQueryBatching(bool $enableBatching): self
```
```php
@@ -1947,7 +1939,7 @@ function parseRequestParams($method, array $bodyParams, array $queryParams)
* Checks validity of OperationParams extracted from HTTP request and returns an array of errors
* if params are invalid (or empty array when params are valid)
*
* @return Error[]
* @return array<int, RequestError>
*
* @api
*/
@@ -2002,7 +1994,7 @@ function sendResponse($result, $exitWhenDone = false)
*
* @api
*/
function parsePsrRequest(Psr\Http\Message\ServerRequestInterface $request)
function parsePsrRequest(Psr\Http\Message\RequestInterface $request)
```
```php
@@ -2073,7 +2065,7 @@ public $extensions;
*
* @api
*/
static function create(array $params, bool $readonly = false)
static function create(array $params, bool $readonly = false): GraphQL\Server\OperationParams
```
```php
@@ -2133,6 +2125,7 @@ static function build($source, callable $typeConfigDecorator = null, array $opti
*
* - commentDescriptions:
* Provide true to use preceding comments as the description.
* This option is provided to ease adoption and will be removed in v16.
*
* @param bool[] $options
*
@@ -2178,7 +2171,7 @@ Various utilities dealing with AST
*
* @api
*/
static function fromArray(array $node)
static function fromArray(array $node): GraphQL\Language\AST\Node
```
```php
@@ -2189,7 +2182,7 @@ static function fromArray(array $node)
*
* @api
*/
static function toArray(GraphQL\Language\AST\Node $node)
static function toArray(GraphQL\Language\AST\Node $node): array
```
```php
@@ -2213,7 +2206,7 @@ static function toArray(GraphQL\Language\AST\Node $node)
*
* @param Type|mixed|null $value
*
* @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode
* @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode|null
*
* @api
*/
@@ -2240,8 +2233,8 @@ static function astFromValue($value, GraphQL\Type\Definition\InputType $type)
* | Enum Value | Mixed |
* | Null Value | null |
*
* @param ValueNode|null $valueNode
* @param mixed[]|null $variables
* @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $valueNode
* @param mixed[]|null $variables
*
* @return mixed[]|stdClass|null
*
@@ -2249,7 +2242,11 @@ static function astFromValue($value, GraphQL\Type\Definition\InputType $type)
*
* @api
*/
static function valueFromAST($valueNode, GraphQL\Type\Definition\InputType $type, array $variables = null)
static function valueFromAST(
GraphQL\Language\AST\ValueNode $valueNode,
GraphQL\Type\Definition\Type $type,
array $variables = null
)
```
```php
@@ -2302,7 +2299,7 @@ static function typeFromAST(GraphQL\Type\Schema $schema, $inputTypeNode)
*
* @param string $operationName
*
* @return bool
* @return bool|string
*
* @api
*/
@@ -2314,23 +2311,22 @@ Given an instance of Schema, prints it in GraphQL type language.
**Class Methods:**
```php
/**
* Accepts options as a second argument:
*
* @param array<string, bool> $options
* Available options:
* - commentDescriptions:
* Provide true to use preceding comments as the description.
*
* @param bool[] $options
* This option is provided to ease adoption and will be removed in v16.
*
* @api
*/
static function doPrint(GraphQL\Type\Schema $schema, array $options = [])
static function doPrint(GraphQL\Type\Schema $schema, array $options = []): string
```
```php
/**
* @param bool[] $options
* @param array<string, bool> $options
*
* @api
*/
static function printIntrospectionSchema(GraphQL\Type\Schema $schema, array $options = [])
static function printIntrospectionSchema(GraphQL\Type\Schema $schema, array $options = []): string
```
+1 -1
View File
@@ -158,7 +158,7 @@ $heroType = new ObjectType([
'args' => [
'episode' => Type::nonNull($enumType)
],
'resolve' => function($_value, $args) {
'resolve' => function($hero, $args) {
return $args['episode'] === 5 ? true : false;
}
]
+1 -1
View File
@@ -131,7 +131,7 @@ $queryType = new ObjectType([
'type' => Type::listOf($storyType),
'args' => [
'filters' => [
'type' => Type::nonNull($filters),
'type' => $filters,
'defaultValue' => [
'popular' => true
]
@@ -134,3 +134,41 @@ concrete Object Type.
If a **resolveType** option is omitted, graphql-php will loop through all interface implementors and
use their **isTypeOf** callback to pick the first suitable one. This is obviously less efficient
than single **resolveType** call. So it is recommended to define **resolveType** whenever possible.
# Prevent invisible types
When object types that implement an interface are not directly referenced by a field, they cannot
be discovered during schema introspection. For example:
```graphql
type Query {
animal: Animal
}
interface Animal {...}
type Cat implements Animal {...}
type Dog implements Animal {...}
```
In this example, `Cat` and `Dog` would be considered *invisible* types. Querying the `animal` field
would fail, since no possible implementing types for `Animal` can be found.
There are two possible solutions:
1. Add fields that reference the invisible types directly, e.g.:
```graphql
type Query {
dog: Dog
cat: Cat
}
```
2. Pass the invisible types during schema construction, e.g.:
```php
new GraphQLSchema([
'query' => ...,
'types' => [$cat, $dog]
]);
```
@@ -80,7 +80,7 @@ Option | Type | Notes
name | `string` | **Required.** Name of the field. When not set - inferred from **fields** array key (read about [shorthand field definition](#shorthand-field-definitions) below)
type | `Type` | **Required.** An instance of internal or custom type. Note: type must be represented by a single instance within one schema (see also [Type Registry](index.md#type-registry))
args | `array` | An array of possible type arguments. Each entry is expected to be an array with keys: **name**, **type**, **description**, **defaultValue**. See [Field Arguments](#field-arguments) section below.
resolve | `callable` | **function($value, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$value** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
resolve | `callable` | **function($objectValue, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$objectValue** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
complexity | `callable` | **function($childrenComplexity, $args)**<br> Used to restrict query complexity. The feature is disabled by default, read about [Security](../security.md#query-complexity-analysis) to use it.
description | `string` | Plain-text description of this field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
deprecationReason | `string` | Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced)
@@ -100,7 +100,7 @@ class EmailType extends ScalarType
* @return string
* @throws Error
*/
public function parseLiteral($valueNode, array $variables = null)
public function parseLiteral(Node $valueNode, ?array $variables = null)
{
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
// error location in query:
+1 -1
View File
@@ -62,7 +62,7 @@ $mutationType = new ObjectType([
'episode' => $episodeEnum,
'review' => $reviewInputObject
],
'resolve' => function($val, $args) {
'resolve' => function($rootValue, $args) {
// TODOC
}
]
@@ -1,18 +0,0 @@
# Hello world
Clean and simple single-file example of main GraphQL concepts originally proposed and
implemented by [Leo Cavalcante](https://github.com/leocavalcante)
### Run locally
```
php -S localhost:8080 ./graphql.php
```
### Try query
```
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
```
### Try mutation
```
curl http://localhost:8080 -d '{"query": "mutation { sum(x: 2, y: 2) }" }'
```
@@ -1,69 +0,0 @@
<?php
// Test this using following command
// php -S localhost:8080 ./graphql.php &
// curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
// curl http://localhost:8080 -d '{"query": "mutation { sum(x: 2, y: 2) }" }'
require_once __DIR__ . '/../../vendor/autoload.php';
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\GraphQL;
try {
$queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'echo' => [
'type' => Type::string(),
'args' => [
'message' => ['type' => Type::string()],
],
'resolve' => function ($root, $args) {
return $root['prefix'] . $args['message'];
}
],
],
]);
$mutationType = new ObjectType([
'name' => 'Calc',
'fields' => [
'sum' => [
'type' => Type::int(),
'args' => [
'x' => ['type' => Type::int()],
'y' => ['type' => Type::int()],
],
'resolve' => function ($root, $args) {
return $args['x'] + $args['y'];
},
],
],
]);
// See docs on schema options:
// http://webonyx.github.io/graphql-php/type-system/schema/#configuration-options
$schema = new Schema([
'query' => $queryType,
'mutation' => $mutationType,
]);
$rawInput = file_get_contents('php://input');
$input = json_decode($rawInput, true);
$query = $input['query'];
$variableValues = isset($input['variables']) ? $input['variables'] : null;
$rootValue = ['prefix' => 'You said: '];
$result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variableValues);
$output = $result->toArray();
} catch (\Exception $e) {
$output = [
'error' => [
'message' => $e->getMessage()
]
];
}
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($output);
@@ -1,28 +0,0 @@
<?php
namespace GraphQL\Examples\Blog;
use GraphQL\Examples\Blog\Data\User;
/**
* Class AppContext
* Instance available in all GraphQL resolvers as 3rd argument
*
* @package GraphQL\Examples\Blog
*/
class AppContext
{
/**
* @var string
*/
public $rootUrl;
/**
* @var User
*/
public $viewer;
/**
* @var \mixed
*/
public $request;
}
@@ -1,25 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Data;
use GraphQL\Utils\Utils;
class Comment
{
public $id;
public $authorId;
public $storyId;
public $parentId;
public $body;
public $isAnonymous;
public function __construct(array $data)
{
Utils::assign($this, $data);
}
}
@@ -1,206 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Data;
/**
* Class DataSource
*
* This is just a simple in-memory data holder for the sake of example.
* Data layer for real app may use Doctrine or query the database directly (e.g. in CQRS style)
*
* @package GraphQL\Examples\Blog
*/
class DataSource
{
private static $users = [];
private static $stories = [];
private static $storyLikes = [];
private static $comments = [];
private static $storyComments = [];
private static $commentReplies = [];
private static $storyMentions = [];
public static function init()
{
self::$users = [
'1' => new User([
'id' => '1',
'email' => 'john@example.com',
'firstName' => 'John',
'lastName' => 'Doe'
]),
'2' => new User([
'id' => '2',
'email' => 'jane@example.com',
'firstName' => 'Jane',
'lastName' => 'Doe'
]),
'3' => new User([
'id' => '3',
'email' => 'john@example.com',
'firstName' => 'John',
'lastName' => 'Doe'
]),
];
self::$stories = [
'1' => new Story(['id' => '1', 'authorId' => '1', 'body' => '<h1>GraphQL is awesome!</h1>']),
'2' => new Story(['id' => '2', 'authorId' => '1', 'body' => '<a>Test this</a>']),
'3' => new Story(['id' => '3', 'authorId' => '3', 'body' => "This\n<br>story\n<br>spans\n<br>newlines"]),
];
self::$storyLikes = [
'1' => ['1', '2', '3'],
'2' => [],
'3' => ['1']
];
self::$comments = [
// thread #1:
'100' => new Comment(['id' => '100', 'authorId' => '3', 'storyId' => '1', 'body' => 'Likes']),
'110' => new Comment(['id' =>'110', 'authorId' =>'2', 'storyId' => '1', 'body' => 'Reply <b>#1</b>', 'parentId' => '100']),
'111' => new Comment(['id' => '111', 'authorId' => '1', 'storyId' => '1', 'body' => 'Reply #1-1', 'parentId' => '110']),
'112' => new Comment(['id' => '112', 'authorId' => '3', 'storyId' => '1', 'body' => 'Reply #1-2', 'parentId' => '110']),
'113' => new Comment(['id' => '113', 'authorId' => '2', 'storyId' => '1', 'body' => 'Reply #1-3', 'parentId' => '110']),
'114' => new Comment(['id' => '114', 'authorId' => '1', 'storyId' => '1', 'body' => 'Reply #1-4', 'parentId' => '110']),
'115' => new Comment(['id' => '115', 'authorId' => '3', 'storyId' => '1', 'body' => 'Reply #1-5', 'parentId' => '110']),
'116' => new Comment(['id' => '116', 'authorId' => '1', 'storyId' => '1', 'body' => 'Reply #1-6', 'parentId' => '110']),
'117' => new Comment(['id' => '117', 'authorId' => '2', 'storyId' => '1', 'body' => 'Reply #1-7', 'parentId' => '110']),
'120' => new Comment(['id' => '120', 'authorId' => '3', 'storyId' => '1', 'body' => 'Reply #2', 'parentId' => '100']),
'130' => new Comment(['id' => '130', 'authorId' => '3', 'storyId' => '1', 'body' => 'Reply #3', 'parentId' => '100']),
'200' => new Comment(['id' => '200', 'authorId' => '2', 'storyId' => '1', 'body' => 'Me2']),
'300' => new Comment(['id' => '300', 'authorId' => '3', 'storyId' => '1', 'body' => 'U2']),
# thread #2:
'400' => new Comment(['id' => '400', 'authorId' => '2', 'storyId' => '2', 'body' => 'Me too']),
'500' => new Comment(['id' => '500', 'authorId' => '2', 'storyId' => '2', 'body' => 'Nice!']),
];
self::$storyComments = [
'1' => ['100', '200', '300'],
'2' => ['400', '500']
];
self::$commentReplies = [
'100' => ['110', '120', '130'],
'110' => ['111', '112', '113', '114', '115', '116', '117'],
];
self::$storyMentions = [
'1' => [
self::$users['2']
],
'2' => [
self::$stories['1'],
self::$users['3']
]
];
}
public static function findUser($id)
{
return isset(self::$users[$id]) ? self::$users[$id] : null;
}
public static function findStory($id)
{
return isset(self::$stories[$id]) ? self::$stories[$id] : null;
}
public static function findComment($id)
{
return isset(self::$comments[$id]) ? self::$comments[$id] : null;
}
public static function findLastStoryFor($authorId)
{
$storiesFound = array_filter(self::$stories, function(Story $story) use ($authorId) {
return $story->authorId == $authorId;
});
return !empty($storiesFound) ? $storiesFound[count($storiesFound) - 1] : null;
}
public static function findLikes($storyId, $limit)
{
$likes = isset(self::$storyLikes[$storyId]) ? self::$storyLikes[$storyId] : [];
$result = array_map(
function($userId) {
return self::$users[$userId];
},
$likes
);
return array_slice($result, 0, $limit);
}
public static function isLikedBy($storyId, $userId)
{
$subscribers = isset(self::$storyLikes[$storyId]) ? self::$storyLikes[$storyId] : [];
return in_array($userId, $subscribers);
}
public static function getUserPhoto($userId, $size)
{
return new Image([
'id' => $userId,
'type' => Image::TYPE_USERPIC,
'size' => $size,
'width' => rand(100, 200),
'height' => rand(100, 200)
]);
}
public static function findLatestStory()
{
return array_pop(self::$stories);
}
public static function findStories($limit, $afterId = null)
{
$start = $afterId ? (int) array_search($afterId, array_keys(self::$stories)) + 1 : 0;
return array_slice(array_values(self::$stories), $start, $limit);
}
public static function findComments($storyId, $limit = 5, $afterId = null)
{
$storyComments = isset(self::$storyComments[$storyId]) ? self::$storyComments[$storyId] : [];
$start = isset($after) ? (int) array_search($afterId, $storyComments) + 1 : 0;
$storyComments = array_slice($storyComments, $start, $limit);
return array_map(
function($commentId) {
return self::$comments[$commentId];
},
$storyComments
);
}
public static function findReplies($commentId, $limit = 5, $afterId = null)
{
$commentReplies = isset(self::$commentReplies[$commentId]) ? self::$commentReplies[$commentId] : [];
$start = isset($after) ? (int) array_search($afterId, $commentReplies) + 1: 0;
$commentReplies = array_slice($commentReplies, $start, $limit);
return array_map(
function($replyId) {
return self::$comments[$replyId];
},
$commentReplies
);
}
public static function countComments($storyId)
{
return isset(self::$storyComments[$storyId]) ? count(self::$storyComments[$storyId]) : 0;
}
public static function countReplies($commentId)
{
return isset(self::$commentReplies[$commentId]) ? count(self::$commentReplies[$commentId]) : 0;
}
public static function findStoryMentions($storyId)
{
return isset(self::$storyMentions[$storyId]) ? self::$storyMentions[$storyId] :[];
}
}
@@ -1,29 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Data;
use GraphQL\Utils\Utils;
class Image
{
const TYPE_USERPIC = 'userpic';
const SIZE_ICON = 'icon';
const SIZE_SMALL = 'small';
const SIZE_MEDIUM = 'medium';
const SIZE_ORIGINAL = 'original';
public $id;
public $type;
public $size;
public $width;
public $height;
public function __construct(array $data)
{
Utils::assign($this, $data);
}
}
@@ -1,22 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Data;
use GraphQL\Utils\Utils;
class Story
{
public $id;
public $authorId;
public $title;
public $body;
public $isAnonymous = false;
public function __construct(array $data)
{
Utils::assign($this, $data);
}
}
@@ -1,22 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Data;
use GraphQL\Utils\Utils;
class User
{
public $id;
public $email;
public $firstName;
public $lastName;
public $hasPhoto;
public function __construct(array $data)
{
Utils::assign($this, $data);
}
}
@@ -1,76 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext;
use GraphQL\Examples\Blog\Data\Comment;
use GraphQL\Examples\Blog\Data\DataSource;
use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
class CommentType extends ObjectType
{
public function __construct()
{
$config = [
'name' => 'Comment',
'fields' => function() {
return [
'id' => Types::id(),
'author' => Types::user(),
'parent' => Types::comment(),
'isAnonymous' => Types::boolean(),
'replies' => [
'type' => Types::listOf(Types::comment()),
'args' => [
'after' => Types::int(),
'limit' => [
'type' => Types::int(),
'defaultValue' => 5
]
]
],
'totalReplyCount' => Types::int(),
Types::htmlField('body')
];
},
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) {
return $this->{$method}($value, $args, $context, $info);
} else {
return $value->{$info->fieldName};
}
}
];
parent::__construct($config);
}
public function resolveAuthor(Comment $comment)
{
if ($comment->isAnonymous) {
return null;
}
return DataSource::findUser($comment->authorId);
}
public function resolveParent(Comment $comment)
{
if ($comment->parentId) {
return DataSource::findComment($comment->parentId);
}
return null;
}
public function resolveReplies(Comment $comment, $args)
{
$args += ['after' => null];
return DataSource::findReplies($comment->id, $args['limit'], $args['after']);
}
public function resolveTotalReplyCount(Comment $comment)
{
return DataSource::countReplies($comment->id);
}
}
@@ -1,19 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type\Enum;
use GraphQL\Type\Definition\EnumType;
class ContentFormatEnum extends EnumType
{
const FORMAT_TEXT = 'TEXT';
const FORMAT_HTML = 'HTML';
public function __construct()
{
$config = [
'name' => 'ContentFormatEnum',
'values' => [self::FORMAT_TEXT, self::FORMAT_HTML]
];
parent::__construct($config);
}
}
@@ -1,23 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type\Enum;
use GraphQL\Examples\Blog\Data\Image;
use GraphQL\Type\Definition\EnumType;
class ImageSizeEnumType extends EnumType
{
public function __construct()
{
$config = [
// Note: 'name' option is not needed in this form - it will be inferred from className
'values' => [
'ICON' => Image::SIZE_ICON,
'SMALL' => Image::SIZE_SMALL,
'MEDIUM' => Image::SIZE_MEDIUM,
'ORIGINAL' => Image::SIZE_ORIGINAL
]
];
parent::__construct($config);
}
}
@@ -1,52 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type\Field;
use GraphQL\Examples\Blog\Type\Enum\ContentFormatEnum;
use GraphQL\Examples\Blog\Types;
class HtmlField
{
public static function build($name, $objectKey = null)
{
$objectKey = $objectKey ?: $name;
// Demonstrates how to organize re-usable fields
// Usual example: when the same field with same args shows up in different types
// (for example when it is a part of some interface)
return [
'name' => $name,
'type' => Types::string(),
'args' => [
'format' => [
'type' => Types::contentFormatEnum(),
'defaultValue' => ContentFormatEnum::FORMAT_HTML
],
'maxLength' => Types::int()
],
'resolve' => function($object, $args) use ($objectKey) {
$html = $object->{$objectKey};
$text = strip_tags($html);
if (!empty($args['maxLength'])) {
$safeText = mb_substr($text, 0, $args['maxLength']);
} else {
$safeText = $text;
}
switch ($args['format']) {
case ContentFormatEnum::FORMAT_HTML:
if ($safeText !== $text) {
// Text was truncated, so just show what's safe:
return nl2br($safeText);
} else {
return $html;
}
case ContentFormatEnum::FORMAT_TEXT:
default:
return $safeText;
}
}
];
}
}
@@ -1,62 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext;
use GraphQL\Examples\Blog\Data\Image;
use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\ObjectType;
class ImageType extends ObjectType
{
public function __construct()
{
$config = [
'name' => 'ImageType',
'fields' => [
'id' => Types::id(),
'type' => new EnumType([
'name' => 'ImageTypeEnum',
'values' => [
'USERPIC' => Image::TYPE_USERPIC
]
]),
'size' => Types::imageSizeEnum(),
'width' => Types::int(),
'height' => Types::int(),
'url' => [
'type' => Types::url(),
'resolve' => [$this, 'resolveUrl']
],
// Just for the sake of example
'fieldWithError' => [
'type' => Types::string(),
'resolve' => function() {
throw new \Exception("Field with exception");
}
],
'nonNullFieldWithError' => [
'type' => Types::nonNull(Types::string()),
'resolve' => function() {
throw new \Exception("Non-null field with exception");
}
]
]
];
parent::__construct($config);
}
public function resolveUrl(Image $value, $args, AppContext $context)
{
switch ($value->type) {
case Image::TYPE_USERPIC:
$path = "/images/user/{$value->id}-{$value->size}.jpg";
break;
default:
throw new \UnexpectedValueException("Unexpected image type: " . $value->type);
}
return $context->rootUrl . $path;
}
}
@@ -1,34 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\Data\Story;
use GraphQL\Examples\Blog\Data\User;
use GraphQL\Examples\Blog\Data\Image;
use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\InterfaceType;
class NodeType extends InterfaceType
{
public function __construct()
{
$config = [
'name' => 'Node',
'fields' => [
'id' => Types::id()
],
'resolveType' => [$this, 'resolveNodeType']
];
parent::__construct($config);
}
public function resolveNodeType($object)
{
if ($object instanceof User) {
return Types::user();
} else if ($object instanceof Image) {
return Types::image();
} else if ($object instanceof Story) {
return Types::story();
}
}
}
@@ -1,97 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext;
use GraphQL\Examples\Blog\Data\DataSource;
use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
class QueryType extends ObjectType
{
public function __construct()
{
$config = [
'name' => 'Query',
'fields' => [
'user' => [
'type' => Types::user(),
'description' => 'Returns user by id (in range of 1-5)',
'args' => [
'id' => Types::nonNull(Types::id())
]
],
'viewer' => [
'type' => Types::user(),
'description' => 'Represents currently logged-in user (for the sake of example - simply returns user with id == 1)'
],
'stories' => [
'type' => Types::listOf(Types::story()),
'description' => 'Returns subset of stories posted for this blog',
'args' => [
'after' => [
'type' => Types::id(),
'description' => 'Fetch stories listed after the story with this ID'
],
'limit' => [
'type' => Types::int(),
'description' => 'Number of stories to be returned',
'defaultValue' => 10
]
]
],
'lastStoryPosted' => [
'type' => Types::story(),
'description' => 'Returns last story posted for this blog'
],
'deprecatedField' => [
'type' => Types::string(),
'deprecationReason' => 'This field is deprecated!'
],
'fieldWithException' => [
'type' => Types::string(),
'resolve' => function() {
throw new \Exception("Exception message thrown in field resolver");
}
],
'hello' => Type::string()
],
'resolveField' => function($val, $args, $context, ResolveInfo $info) {
return $this->{$info->fieldName}($val, $args, $context, $info);
}
];
parent::__construct($config);
}
public function user($rootValue, $args)
{
return DataSource::findUser($args['id']);
}
public function viewer($rootValue, $args, AppContext $context)
{
return $context->viewer;
}
public function stories($rootValue, $args)
{
$args += ['after' => null];
return DataSource::findStories($args['limit'], $args['after']);
}
public function lastStoryPosted()
{
return DataSource::findLatestStory();
}
public function hello()
{
return 'Your graphql-php endpoint is ready! Use GraphiQL to browse API';
}
public function deprecatedField()
{
return 'You can request deprecated field, but it is not displayed in auto-generated documentation by default.';
}
}
@@ -1,70 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type\Scalar;
use GraphQL\Error\Error;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Utils\Utils;
class EmailType
{
public static function create()
{
return new CustomScalarType([
'name' => 'Email',
'serialize' => [__CLASS__, 'serialize'],
'parseValue' => [__CLASS__, 'parseValue'],
'parseLiteral' => [__CLASS__, 'parseLiteral'],
]);
}
/**
* Serializes an internal value to include in a response.
*
* @param string $value
* @return string
*/
public static function serialize($value)
{
// Assuming internal representation of email is always correct:
return $value;
// If it might be incorrect and you want to make sure that only correct values are included in response -
// use following line instead:
// return $this->parseValue($value);
}
/**
* Parses an externally provided value (query variable) to use as an input
*
* @param mixed $value
* @return mixed
*/
public static function parseValue($value)
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new \UnexpectedValueException("Cannot represent value as email: " . Utils::printSafe($value));
}
return $value;
}
/**
* Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input
*
* @param \GraphQL\Language\AST\Node $valueNode
* @return string
* @throws Error
*/
public static function parseLiteral($valueNode)
{
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
// error location in query:
if (!$valueNode instanceof StringValueNode) {
throw new Error('Query error: Can only parse strings got: ' . $valueNode->kind, [$valueNode]);
}
if (!filter_var($valueNode->value, FILTER_VALIDATE_EMAIL)) {
throw new Error("Not a valid email", [$valueNode]);
}
return $valueNode->value;
}
}
@@ -1,63 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type\Scalar;
use GraphQL\Error\Error;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
class UrlType extends ScalarType
{
/**
* Serializes an internal value to include in a response.
*
* @param mixed $value
* @return mixed
*/
public function serialize($value)
{
// Assuming internal representation of url is always correct:
return $value;
// If it might be incorrect and you want to make sure that only correct values are included in response -
// use following line instead:
// return $this->parseValue($value);
}
/**
* Parses an externally provided value (query variable) to use as an input
*
* @param mixed $value
* @return mixed
* @throws Error
*/
public function parseValue($value)
{
if (!is_string($value) || !filter_var($value, FILTER_VALIDATE_URL)) { // quite naive, but after all this is example
throw new Error("Cannot represent value as URL: " . Utils::printSafe($value));
}
return $value;
}
/**
* Parses an externally provided literal value to use as an input (e.g. in Query AST)
*
* @param Node $valueNode
* @param array|null $variables
* @return null|string
* @throws Error
*/
public function parseLiteral($valueNode, array $variables = null)
{
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
// error location in query:
if (!($valueNode instanceof StringValueNode)) {
throw new Error('Query error: Can only parse strings got: ' . $valueNode->kind, [$valueNode]);
}
if (!is_string($valueNode->value) || !filter_var($valueNode->value, FILTER_VALIDATE_URL)) {
throw new Error('Query error: Not a valid URL', [$valueNode]);
}
return $valueNode->value;
}
}
@@ -1,31 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\Data\Story;
use GraphQL\Examples\Blog\Data\User;
use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\UnionType;
class SearchResultType extends UnionType
{
public function __construct()
{
$config = [
'name' => 'SearchResultType',
'types' => function() {
return [
Types::story(),
Types::user()
];
},
'resolveType' => function($value) {
if ($value instanceof Story) {
return Types::story();
} else if ($value instanceof User) {
return Types::user();
}
}
];
parent::__construct($config);
}
}
@@ -1,127 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext;
use GraphQL\Examples\Blog\Data\DataSource;
use GraphQL\Examples\Blog\Data\Story;
use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
/**
* Class StoryType
* @package GraphQL\Examples\Social\Type
*/
class StoryType extends ObjectType
{
const EDIT = 'EDIT';
const DELETE = 'DELETE';
const LIKE = 'LIKE';
const UNLIKE = 'UNLIKE';
const REPLY = 'REPLY';
public function __construct()
{
$config = [
'name' => 'Story',
'fields' => function() {
return [
'id' => Types::id(),
'author' => Types::user(),
'mentions' => Types::listOf(Types::mention()),
'totalCommentCount' => Types::int(),
'comments' => [
'type' => Types::listOf(Types::comment()),
'args' => [
'after' => [
'type' => Types::id(),
'description' => 'Load all comments listed after given comment ID'
],
'limit' => [
'type' => Types::int(),
'defaultValue' => 5
]
]
],
'likes' => [
'type' => Types::listOf(Types::user()),
'args' => [
'limit' => [
'type' => Types::int(),
'description' => 'Limit the number of recent likes returned',
'defaultValue' => 5
]
]
],
'likedBy' => [
'type' => Types::listOf(Types::user()),
],
'affordances' => Types::listOf(new EnumType([
'name' => 'StoryAffordancesEnum',
'values' => [
self::EDIT,
self::DELETE,
self::LIKE,
self::UNLIKE,
self::REPLY
]
])),
'hasViewerLiked' => Types::boolean(),
Types::htmlField('body'),
];
},
'interfaces' => [
Types::node()
],
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) {
return $this->{$method}($value, $args, $context, $info);
} else {
return $value->{$info->fieldName};
}
}
];
parent::__construct($config);
}
public function resolveAuthor(Story $story)
{
return DataSource::findUser($story->authorId);
}
public function resolveAffordances(Story $story, $args, AppContext $context)
{
$isViewer = $context->viewer === DataSource::findUser($story->authorId);
$isLiked = DataSource::isLikedBy($story->id, $context->viewer->id);
if ($isViewer) {
$affordances[] = self::EDIT;
$affordances[] = self::DELETE;
}
if ($isLiked) {
$affordances[] = self::UNLIKE;
} else {
$affordances[] = self::LIKE;
}
return $affordances;
}
public function resolveHasViewerLiked(Story $story, $args, AppContext $context)
{
return DataSource::isLikedBy($story->id, $context->viewer->id);
}
public function resolveTotalCommentCount(Story $story)
{
return DataSource::countComments($story->id);
}
public function resolveComments(Story $story, $args)
{
$args += ['after' => null];
return DataSource::findComments($story->id, $args['limit'], $args['after']);
}
}
@@ -1,68 +0,0 @@
<?php
namespace GraphQL\Examples\Blog\Type;
use GraphQL\Examples\Blog\AppContext;
use GraphQL\Examples\Blog\Data\DataSource;
use GraphQL\Examples\Blog\Data\User;
use GraphQL\Examples\Blog\Types;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
class UserType extends ObjectType
{
public function __construct()
{
$config = [
'name' => 'User',
'description' => 'Our blog authors',
'fields' => function() {
return [
'id' => Types::id(),
'email' => Types::email(),
'photo' => [
'type' => Types::image(),
'description' => 'User photo URL',
'args' => [
'size' => Types::nonNull(Types::imageSizeEnum()),
]
],
'firstName' => [
'type' => Types::string(),
],
'lastName' => [
'type' => Types::string(),
],
'lastStoryPosted' => Types::story(),
'fieldWithError' => [
'type' => Types::string(),
'resolve' => function() {
throw new \Exception("This is error field");
}
]
];
},
'interfaces' => [
Types::node()
],
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) {
return $this->{$method}($value, $args, $context, $info);
} else {
return $value->{$info->fieldName};
}
}
];
parent::__construct($config);
}
public function resolvePhoto(User $user, $args)
{
return DataSource::getUserPhoto($user->id, $args['size']);
}
public function resolveLastStoryPosted(User $user)
{
return DataSource::findLastStoryFor($user->id);
}
}
@@ -1,209 +0,0 @@
<?php
namespace GraphQL\Examples\Blog;
use GraphQL\Examples\Blog\Type\CommentType;
use GraphQL\Examples\Blog\Type\Enum\ContentFormatEnum;
use GraphQL\Examples\Blog\Type\Enum\ImageSizeEnumType;
use GraphQL\Examples\Blog\Type\Field\HtmlField;
use GraphQL\Examples\Blog\Type\SearchResultType;
use GraphQL\Examples\Blog\Type\NodeType;
use GraphQL\Examples\Blog\Type\QueryType;
use GraphQL\Examples\Blog\Type\Scalar\EmailType;
use GraphQL\Examples\Blog\Type\StoryType;
use GraphQL\Examples\Blog\Type\Scalar\UrlType;
use GraphQL\Examples\Blog\Type\UserType;
use GraphQL\Examples\Blog\Type\ImageType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\Type;
/**
* Class Types
*
* Acts as a registry and factory for your types.
*
* As simplistic as possible for the sake of clarity of this example.
* Your own may be more dynamic (or even code-generated).
*
* @package GraphQL\Examples\Blog
*/
class Types
{
// Object types:
private static $user;
private static $story;
private static $comment;
private static $image;
private static $query;
/**
* @return UserType
*/
public static function user()
{
return self::$user ?: (self::$user = new UserType());
}
/**
* @return StoryType
*/
public static function story()
{
return self::$story ?: (self::$story = new StoryType());
}
/**
* @return CommentType
*/
public static function comment()
{
return self::$comment ?: (self::$comment = new CommentType());
}
/**
* @return ImageType
*/
public static function image()
{
return self::$image ?: (self::$image = new ImageType());
}
/**
* @return QueryType
*/
public static function query()
{
return self::$query ?: (self::$query = new QueryType());
}
// Interface types
private static $node;
/**
* @return NodeType
*/
public static function node()
{
return self::$node ?: (self::$node = new NodeType());
}
// Unions types:
private static $mention;
/**
* @return SearchResultType
*/
public static function mention()
{
return self::$mention ?: (self::$mention = new SearchResultType());
}
// Enum types
private static $imageSizeEnum;
private static $contentFormatEnum;
/**
* @return ImageSizeEnumType
*/
public static function imageSizeEnum()
{
return self::$imageSizeEnum ?: (self::$imageSizeEnum = new ImageSizeEnumType());
}
/**
* @return ContentFormatEnum
*/
public static function contentFormatEnum()
{
return self::$contentFormatEnum ?: (self::$contentFormatEnum = new ContentFormatEnum());
}
// Custom Scalar types:
private static $urlType;
private static $emailType;
public static function email()
{
return self::$emailType ?: (self::$emailType = EmailType::create());
}
/**
* @return UrlType
*/
public static function url()
{
return self::$urlType ?: (self::$urlType = new UrlType());
}
/**
* @param $name
* @param null $objectKey
* @return array
*/
public static function htmlField($name, $objectKey = null)
{
return HtmlField::build($name, $objectKey);
}
// Let's add internal types as well for consistent experience
public static function boolean()
{
return Type::boolean();
}
/**
* @return \GraphQL\Type\Definition\FloatType
*/
public static function float()
{
return Type::float();
}
/**
* @return \GraphQL\Type\Definition\IDType
*/
public static function id()
{
return Type::id();
}
/**
* @return \GraphQL\Type\Definition\IntType
*/
public static function int()
{
return Type::int();
}
/**
* @return \GraphQL\Type\Definition\StringType
*/
public static function string()
{
return Type::string();
}
/**
* @param Type $type
* @return ListOfType
*/
public static function listOf($type)
{
return new ListOfType($type);
}
/**
* @param Type $type
* @return NonNull
*/
public static function nonNull($type)
{
return new NonNull($type);
}
}
-120
View File
@@ -1,120 +0,0 @@
## Blog Example
Simple yet full-featured example of GraphQL API. Models blogging platform with Stories, Users
and hierarchical comments.
### Run locally
```
php -S localhost:8080 ./graphql.php
```
### Test if GraphQL is running
If you open `http://localhost:8080` in browser you should see `json` response with
following message:
```
{
data: {
hello: "Your GraphQL endpoint is ready! Install GraphiQL to browse API"
}
}
```
Note that some browsers may try to download JSON file instead of showing you the response.
In this case try to install browser plugin that adds JSON support (like JSONView or similar)
### Debugging Mode
By default GraphQL endpoint exposed at `http://localhost:8080` runs in production mode without
additional debugging tools enabled.
In order to enable debugging mode with additional validation, error handling and reporting -
use `http://localhost:8080?debug=1` as endpoint
### Browsing API
The most convenient way to browse GraphQL API is by using [GraphiQL](https://github.com/graphql/graphiql)
But setting it up from scratch may be inconvenient. An easy alternative is to use one of
the existing Google Chrome extensions:
- [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
- [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp)
Set `http://localhost:8080?debug=1` as your GraphQL endpoint/server in one of these extensions
and try clicking "Docs" button (usually in the top-right corner) to browse auto-generated
documentation.
### Running GraphQL queries
Copy following query to GraphiQL and execute (by clicking play button on top bar)
```
{
viewer {
id
email
}
user(id: "2") {
id
email
}
stories(after: "1") {
id
body
comments {
...CommentView
}
}
lastStoryPosted {
id
hasViewerLiked
author {
id
photo(size: ICON) {
id
url
type
size
width
height
# Uncomment following line to see validation error:
# nonExistingField
# Uncomment to see error reporting for fields with exceptions thrown in resolvers
# fieldWithError
# nonNullFieldWithError
}
lastStoryPosted {
id
}
}
body(format: HTML, maxLength: 10)
}
}
fragment CommentView on Comment {
id
body
totalReplyCount
replies {
id
body
}
}
```
### Run your own query
Use GraphiQL autocomplete (via CTRL+space) to easily create your own query.
Note: GraphQL query requires at least one field per object type (to prevent accidental overfetching).
For example following query is invalid in GraphQL:
```
{
viewer
}
```
Try copying this query and see what happens
### Run mutation query
TODOC
### Dig into source code
Now when you tried GraphQL API as a consumer, see how it is implemented by browsing
source code.
-71
View File
@@ -1,71 +0,0 @@
<?php
// Test this using following command
// php -S localhost:8080 ./graphql.php
require_once __DIR__ . '/../../vendor/autoload.php';
use \GraphQL\Examples\Blog\Types;
use \GraphQL\Examples\Blog\AppContext;
use \GraphQL\Examples\Blog\Data\DataSource;
use \GraphQL\Type\Schema;
use \GraphQL\GraphQL;
use \GraphQL\Error\FormattedError;
use \GraphQL\Error\Debug;
// Disable default PHP error reporting - we have better one for debug mode (see bellow)
ini_set('display_errors', 0);
$debug = false;
if (!empty($_GET['debug'])) {
set_error_handler(function($severity, $message, $file, $line) use (&$phpErrors) {
throw new ErrorException($message, 0, $severity, $file, $line);
});
$debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE;
}
try {
// Initialize our fake data source
DataSource::init();
// Prepare context that will be available in all field resolvers (as 3rd argument):
$appContext = new AppContext();
$appContext->viewer = DataSource::findUser('1'); // simulated "currently logged-in user"
$appContext->rootUrl = 'http://localhost:8080';
$appContext->request = $_REQUEST;
// Parse incoming query and variables
if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) {
$raw = file_get_contents('php://input') ?: '';
$data = json_decode($raw, true) ?: [];
} else {
$data = $_REQUEST;
}
$data += ['query' => null, 'variables' => null];
if (null === $data['query']) {
$data['query'] = '{hello}';
}
// GraphQL schema to be passed to query executor:
$schema = new Schema([
'query' => Types::query()
]);
$result = GraphQL::executeQuery(
$schema,
$data['query'],
null,
$appContext,
(array) $data['variables']
);
$output = $result->toArray($debug);
$httpStatus = 200;
} catch (\Exception $error) {
$httpStatus = 500;
$output['errors'] = [
FormattedError::createFromException($error, $debug)
];
}
header('Content-Type: application/json', true, $httpStatus);
echo json_encode($output);
@@ -1,19 +0,0 @@
# Parsing GraphQL IDL shorthand
Same as the Hello world example but shows how to build GraphQL schema from shorthand
and wire up some resolvers
### Run locally
```
php -S localhost:8080 ./graphql.php
```
### Try query
```
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
```
### Try mutation
```
curl http://localhost:8080 -d '{"query": "mutation { sum(x: 2, y: 2) }" }'
```
@@ -1,31 +0,0 @@
<?php
// Test this using following command
// php -S localhost:8080 ./graphql.php &
// curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
// curl http://localhost:8080 -d '{"query": "mutation { sum(x: 2, y: 2) }" }'
require_once __DIR__ . '/../../vendor/autoload.php';
use GraphQL\GraphQL;
use GraphQL\Utils\BuildSchema;
try {
$schema = BuildSchema::build(file_get_contents(__DIR__ . '/schema.graphqls'));
$rootValue = include __DIR__ . '/rootvalue.php';
$rawInput = file_get_contents('php://input');
$input = json_decode($rawInput, true);
$query = $input['query'];
$variableValues = isset($input['variables']) ? $input['variables'] : null;
$result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variableValues);
} catch (\Exception $e) {
$result = [
'error' => [
'message' => $e->getMessage()
]
];
}
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($result);
@@ -1,35 +0,0 @@
<?php
interface Resolver {
public function resolve($root, $args, $context);
}
class Addition implements Resolver
{
public function resolve($root, $args, $context)
{
return $args['x'] + $args['y'];
}
}
class Echoer implements Resolver
{
public function resolve($root, $args, $context)
{
return $root['prefix'].$args['message'];
}
}
return [
'sum' => function($root, $args, $context) {
$sum = new Addition();
return $sum->resolve($root, $args, $context);
},
'echo' => function($root, $args, $context) {
$echo = new Echoer();
return $echo->resolve($root, $args, $context);
},
'prefix' => 'You said: ',
];
@@ -1,13 +0,0 @@
schema {
query: Query
mutation: Calc
}
type Calc {
sum(x: Int, y: Int): Int
}
type Query {
echo(message: String): String
}
-19
View File
@@ -1,19 +0,0 @@
# Hello world
Same example as 01-hello-world, but uses
[Standard Http Server](http://webonyx.github.io/graphql-php/executing-queries/#using-server)
instead of manual parsing of incoming data.
### Run locally
```
php -S localhost:8080 ./graphql.php
```
### Try query
```
curl -d '{"query": "query { echo(message: \"Hello World\") }" }' -H "Content-Type: application/json" http://localhost:8080
```
### Try mutation
```
curl -d '{"query": "mutation { sum(x: 2, y: 2) }" }' -H "Content-Type: application/json" http://localhost:8080
```
@@ -1,61 +0,0 @@
<?php
// Test this using following command
// php -S localhost:8080 ./graphql.php &
// curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
// curl http://localhost:8080 -d '{"query": "mutation { sum(x: 2, y: 2) }" }'
require_once __DIR__ . '/../../vendor/autoload.php';
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Server\StandardServer;
try {
$queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'echo' => [
'type' => Type::string(),
'args' => [
'message' => ['type' => Type::string()],
],
'resolve' => function ($root, $args) {
return $root['prefix'] . $args['message'];
}
],
],
]);
$mutationType = new ObjectType([
'name' => 'Calc',
'fields' => [
'sum' => [
'type' => Type::int(),
'args' => [
'x' => ['type' => Type::int()],
'y' => ['type' => Type::int()],
],
'resolve' => function ($root, $args) {
return $args['x'] + $args['y'];
},
],
],
]);
// See docs on schema options:
// http://webonyx.github.io/graphql-php/type-system/schema/#configuration-options
$schema = new Schema([
'query' => $queryType,
'mutation' => $mutationType,
]);
// See docs on server options:
// http://webonyx.github.io/graphql-php/executing-queries/#server-configuration-options
$server = new StandardServer([
'schema' => $schema
]);
$server->handleRequest();
} catch (\Exception $e) {
StandardServer::send500Error($e);
}
+652
View File
@@ -0,0 +1,652 @@
parameters:
ignoreErrors:
-
message: "#^Variable property access on object\\.$#"
count: 2
path: src/Executor/Executor.php
-
message: "#^Variable property access on object\\.$#"
count: 2
path: src/Experimental/Executor/CoroutineExecutor.php
-
message: "#^Variable property access on mixed\\.$#"
count: 1
path: src/Experimental/Executor/CoroutineExecutor.php
-
message: "#^Variable property access on GraphQL\\\\Language\\\\AST\\\\Node\\.$#"
count: 1
path: src/Language/AST/Node.php
-
message: "#^Method GraphQL\\\\Language\\\\AST\\\\NodeList\\:\\:splice\\(\\) should return GraphQL\\\\Language\\\\AST\\\\NodeList\\<T of GraphQL\\\\Language\\\\AST\\\\Node\\> but returns GraphQL\\\\Language\\\\AST\\\\NodeList\\<GraphQL\\\\Language\\\\AST\\\\Node\\>\\.$#"
count: 1
path: src/Language/AST/NodeList.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\<int, array\\|GraphQL\\\\Language\\\\AST\\\\Node\\|GraphQL\\\\Language\\\\AST\\\\NodeList\\> given\\.$#"
count: 1
path: src/Language/Visitor.php
-
message: "#^Variable property access on GraphQL\\\\Language\\\\AST\\\\Node\\|null\\.$#"
count: 1
path: src/Language/Visitor.php
-
message: "#^Variable property access on GraphQL\\\\Language\\\\AST\\\\Node\\.$#"
count: 1
path: src/Language/Visitor.php
-
message: "#^Only booleans are allowed in a negated boolean, \\(callable\\)\\|null given\\.$#"
count: 1
path: src/Language/Visitor.php
-
message: "#^Only booleans are allowed in a ternary operator condition, \\(callable\\)\\|null given\\.$#"
count: 2
path: src/Server/Helper.php
-
message: "#^Only booleans are allowed in an if condition, int given\\.$#"
count: 1
path: src/Server/Helper.php
-
message: "#^Only booleans are allowed in a negated boolean, string given\\.$#"
count: 2
path: src/Server/Helper.php
-
message: "#^Only booleans are allowed in &&, string given on the left side\\.$#"
count: 1
path: src/Server/Helper.php
-
message: "#^Only booleans are allowed in &&, string given on the right side\\.$#"
count: 1
path: src/Server/Helper.php
-
message: "#^Only booleans are allowed in a ternary operator condition, string given\\.$#"
count: 1
path: src/Server/Helper.php
-
message: "#^Only booleans are allowed in an if condition, \\(callable\\)\\|null given\\.$#"
count: 1
path: src/Server/Helper.php
-
message: "#^Only booleans are allowed in \\|\\|, \\(callable\\)\\|null given on the left side\\.$#"
count: 1
path: src/Server/Helper.php
-
message: "#^Only booleans are allowed in a negated boolean, ArrayObject\\<string, GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\> given\\.$#"
count: 1
path: src/Type/Definition/EnumType.php
-
message: "#^Variable property access on \\$this\\(GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\)\\.$#"
count: 3
path: src/Type/Definition/FieldDefinition.php
-
message: "#^Variable property access on \\$this\\(GraphQL\\\\Type\\\\Definition\\\\InputObjectField\\)\\.$#"
count: 4
path: src/Type/Definition/InputObjectField.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#"
count: 1
path: src/Type/Definition/QueryPlan.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#"
count: 1
path: src/Type/Definition/QueryPlan.php
-
message: "#^Only booleans are allowed in an if condition, string given\\.$#"
count: 1
path: src/Type/Definition/Type.php
-
message: "#^Only booleans are allowed in a negated boolean, string\\|null given\\.$#"
count: 2
path: src/Type/Introspection.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\<GraphQL\\\\Type\\\\Definition\\\\Type\\>\\|\\(callable\\) given\\.$#"
count: 1
path: src/Type/Schema.php
-
message: "#^Only booleans are allowed in an if condition, \\(callable\\)\\|null given\\.$#"
count: 1
path: src/Type/Schema.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#"
count: 1
path: src/Type/Schema.php
-
message: "#^Only booleans are allowed in an if condition, array\\<GraphQL\\\\Error\\\\Error\\|GraphQL\\\\Error\\\\InvariantViolation\\> given\\.$#"
count: 1
path: src/Type/Schema.php
-
message: "#^Only booleans are allowed in a negated boolean, \\(callable\\)\\|null given\\.$#"
count: 1
path: src/Type/Schema.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given on the left side\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in &&, array\\<GraphQL\\\\Language\\\\AST\\\\Node\\>\\|GraphQL\\\\Language\\\\AST\\\\Node\\|GraphQL\\\\Language\\\\AST\\\\TypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\TypeNode\\|null given on the left side\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\OperationTypeDefinitionNode\\|null given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Definition\\\\Type given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Error\\\\Error\\|null given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\DirectiveDefinitionNode\\|null given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in &&, array\\<GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\\> given on the left side\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\DirectiveDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\EnumTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\InputObjectTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\InterfaceTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\SchemaDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\UnionTypeDefinitionNode\\|null given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a ternary operator condition, array\\<GraphQL\\\\Language\\\\AST\\\\EnumTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\InputObjectTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\InterfaceTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\SchemaTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\UnionTypeExtensionNode\\> given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\InterfaceTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\InterfaceTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeExtensionNode given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\\|null given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\\|null given on the left side\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\NodeList\\<GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\\> given on the right side\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\\|null given\\.$#"
count: 2
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\|null given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\FieldArgument\\|null given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\FieldArgument\\|null given on the left side\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\<GraphQL\\\\Type\\\\Definition\\\\ObjectType\\> given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\<GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\> given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in &&, array\\<GraphQL\\\\Language\\\\AST\\\\EnumValueDefinitionNode\\> given on the left side\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\<GraphQL\\\\Type\\\\Definition\\\\InputObjectField\\> given\\.$#"
count: 1
path: src/Type/SchemaValidationContext.php
-
message: "#^Variable property access on mixed\\.$#"
count: 2
path: src/Utils/AST.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\BooleanValueNode\\|GraphQL\\\\Language\\\\AST\\\\EnumValueNode\\|GraphQL\\\\Language\\\\AST\\\\FloatValueNode\\|GraphQL\\\\Language\\\\AST\\\\IntValueNode\\|GraphQL\\\\Language\\\\AST\\\\ListValueNode\\|GraphQL\\\\Language\\\\AST\\\\NullValueNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectValueNode\\|GraphQL\\\\Language\\\\AST\\\\StringValueNode\\|null given\\.$#"
count: 2
path: src/Utils/AST.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\|null given\\.$#"
count: 1
path: src/Utils/AST.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\|null given\\.$#"
count: 1
path: src/Utils/AST.php
-
message: "#^Only booleans are allowed in &&, array\\|null given on the left side\\.$#"
count: 1
path: src/Utils/AST.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#"
count: 2
path: src/Utils/AST.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\NodeList\\<GraphQL\\\\Language\\\\AST\\\\DefinitionNode&GraphQL\\\\Language\\\\AST\\\\Node\\> given\\.$#"
count: 1
path: src/Utils/AST.php
-
message: "#^Only booleans are allowed in a negated boolean, string\\|null given\\.$#"
count: 1
path: src/Utils/AST.php
-
message: "#^Only booleans are allowed in an if condition, callable given\\.$#"
count: 1
path: src/Utils/ASTDefinitionBuilder.php
-
message: "#^Only booleans are allowed in &&, array\\<bool\\>\\|null given on the left side\\.$#"
count: 1
path: src/Utils/PairSet.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#"
count: 1
path: src/Utils/SchemaExtender.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\SchemaDefinitionNode\\|null given\\.$#"
count: 1
path: src/Utils/SchemaExtender.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\<GraphQL\\\\Type\\\\Definition\\\\Type\\>\\|null given\\.$#"
count: 1
path: src/Utils/TypeInfo.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#"
count: 1
path: src/Utils/TypeInfo.php
-
message: "#^Only booleans are allowed in an if condition, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#"
count: 1
path: src/Utils/TypeInfo.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\|null given\\.$#"
count: 1
path: src/Utils/TypeInfo.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NamedTypeNode given\\.$#"
count: 1
path: src/Utils/TypeInfo.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\Directive\\|GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\|null given\\.$#"
count: 1
path: src/Utils/TypeInfo.php
-
message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\FieldArgument\\|null given on the left side\\.$#"
count: 1
path: src/Utils/TypeInfo.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Definition\\\\InputObjectField\\|null given\\.$#"
count: 1
path: src/Utils/TypeInfo.php
-
message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\InputObjectField\\|null given on the left side\\.$#"
count: 1
path: src/Utils/TypeInfo.php
-
message: "#^Variable property access on object\\.$#"
count: 1
path: src/Utils/Utils.php
-
message: "#^Only booleans are allowed in a negated boolean, string given\\.$#"
count: 1
path: src/Utils/Utils.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Error\\\\Error\\|null given\\.$#"
count: 1
path: src/Utils/Utils.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\|null given\\.$#"
count: 1
path: src/Utils/Value.php
-
message: "#^Only booleans are allowed in a ternary operator condition, array\\<string\\> given\\.$#"
count: 2
path: src/Utils/Value.php
-
message: "#^Only booleans are allowed in a ternary operator condition, array\\<GraphQL\\\\Error\\\\Error\\> given\\.$#"
count: 2
path: src/Utils/Value.php
-
message: "#^Only booleans are allowed in a ternary operator condition, string given\\.$#"
count: 2
path: src/Utils/Value.php
-
message: "#^Only booleans are allowed in a ternary operator condition, string\\|null given\\.$#"
count: 1
path: src/Utils/Value.php
-
message: "#^Only booleans are allowed in a negated boolean, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#"
count: 1
path: src/Validator/Rules/FieldsOnCorrectType.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition given\\.$#"
count: 1
path: src/Validator/Rules/FieldsOnCorrectType.php
-
message: "#^Only booleans are allowed in an if condition, array\\<string\\> given\\.$#"
count: 1
path: src/Validator/Rules/FieldsOnCorrectType.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\NamedTypeNode given\\.$#"
count: 1
path: src/Validator/Rules/FragmentsOnCompositeTypes.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#"
count: 2
path: src/Validator/Rules/FragmentsOnCompositeTypes.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Schema\\|null given\\.$#"
count: 1
path: src/Validator/Rules/KnownDirectives.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\|null given\\.$#"
count: 1
path: src/Validator/Rules/KnownDirectives.php
-
message: "#^Only booleans are allowed in a negated boolean, string given\\.$#"
count: 1
path: src/Validator/Rules/KnownDirectives.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#"
count: 1
path: src/Validator/Rules/KnownFragmentNames.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#"
count: 1
path: src/Validator/Rules/NoFragmentCycles.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NameNode\\|null given\\.$#"
count: 1
path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NamedTypeNode given\\.$#"
count: 1
path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\|null given\\.$#"
count: 2
path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\ArgumentNode\\|null given\\.$#"
count: 1
path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\Node given\\.$#"
count: 2
path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#"
count: 3
path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php
-
message: "#^Only booleans are allowed in a negated boolean, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#"
count: 1
path: src/Validator/Rules/PossibleFragmentSpreads.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#"
count: 1
path: src/Validator/Rules/PossibleFragmentSpreads.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition given\\.$#"
count: 1
path: src/Validator/Rules/ProvidedRequiredArguments.php
-
message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Language\\\\AST\\\\ArgumentNode\\|null given on the left side\\.$#"
count: 1
path: src/Validator/Rules/ProvidedRequiredArguments.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Schema\\|null given\\.$#"
count: 1
path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\|null given\\.$#"
count: 1
path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#"
count: 1
path: src/Validator/Rules/QuerySecurityRule.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NameNode\\|null given\\.$#"
count: 1
path: src/Validator/Rules/QuerySecurityRule.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\OutputType\\|null given\\.$#"
count: 1
path: src/Validator/Rules/ScalarLeafs.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#"
count: 1
path: src/Validator/Rules/ScalarLeafs.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#"
count: 1
path: src/Validator/Rules/ScalarLeafs.php
-
message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType given on the left side\\.$#"
count: 1
path: src/Validator/Rules/ValuesOfCorrectType.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\|null given\\.$#"
count: 1
path: src/Validator/Rules/ValuesOfCorrectType.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType given\\.$#"
count: 1
path: src/Validator/Rules/ValuesOfCorrectType.php
-
message: "#^Only booleans are allowed in a ternary operator condition, array\\<string\\> given\\.$#"
count: 1
path: src/Validator/Rules/ValuesOfCorrectType.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#"
count: 1
path: src/Validator/Rules/VariablesAreInputTypes.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#"
count: 1
path: src/Validator/Rules/VariablesInAllowedPosition.php
-
message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\ValueNode\\|null given on the left side\\.$#"
count: 1
path: src/Validator/Rules/VariablesInAllowedPosition.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#"
count: 1
path: src/Validator/ValidationContext.php
-
message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#"
count: 1
path: src/Validator/ValidationContext.php
-
message: "#^Only booleans are allowed in a negated boolean, array\\<GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\> given\\.$#"
count: 1
path: src/Validator/ValidationContext.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Schema given\\.$#"
count: 1
path: tests/Executor/DirectivesTest.php
-
message: "#^Anonymous function should have native return typehint \"class@anonymous/tests/Executor/ExecutorTest\\.php\\:1329\"\\.$#"
count: 1
path: tests/Executor/ExecutorTest.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\InterfaceType given\\.$#"
count: 1
path: tests/Executor/LazyInterfaceTest.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType given\\.$#"
count: 1
path: tests/Executor/LazyInterfaceTest.php
-
message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Schema given\\.$#"
count: 1
path: tests/Executor/ValuesTest.php
-
message: "#^Only booleans are allowed in a ternary operator condition, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#"
count: 4
path: tests/Language/VisitorTest.php
-
message: "#^Only booleans are allowed in a ternary operator condition, \\(GraphQL\\\\Type\\\\Definition\\\\OutputType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#"
count: 4
path: tests/Language/VisitorTest.php
-
message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType\\|null given\\.$#"
count: 4
path: tests/Language/VisitorTest.php
-
message: "#^Access to an undefined property GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\:\\:\\$nonExistentProp\\.$#"
count: 2
path: tests/Type/DefinitionTest.php
-
message: "#^Access to an undefined property GraphQL\\\\Type\\\\Definition\\\\InputObjectField\\:\\:\\$nonExistentProp\\.$#"
count: 1
path: tests/Type/DefinitionTest.php
-
message: "#^Variable property access on \\$this\\(GraphQL\\\\Tests\\\\Type\\\\TypeLoaderTest\\)\\.$#"
count: 1
path: tests/Type/TypeLoaderTest.php
-
message: "#^Only booleans are allowed in a negated boolean, stdClass given\\.$#"
count: 1
path: tests/Utils/AstFromValueTest.php
+38 -8
View File
@@ -1,17 +1,47 @@
parameters:
level: 1
level: 3
inferPrivatePropertyTypeFromConstructor: true
paths:
- %currentWorkingDirectory%/src
- %currentWorkingDirectory%/tests
excludes_analyse:
# Ported from dms/phpunit-arraysubset-asserts
- tests/PHPUnit/ArraySubsetAsserts.php
- tests/PHPUnit/Constraint/ArraySubset.php
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
# Since this is a library that is supposed to be flexible, we don't
# want to lock down every possible extension point.
- "~Unsafe usage of new static\\(\\)~"
# This class uses magic methods to reduce a whole lot of boilerplate required to
# allow partial parsing of language fragments.
- "~Variable method call on GraphQL\\\\Language\\\\Parser\\.~"
# Those come from graphql-php\tests\Language\VisitorTest.php
- "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\.+::\\$didEnter~"
- "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\.+::\\$didLeave~"
- "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\Node::\\$value~"
# TODO convert to less magical code
- "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~"
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-strict-rules/rules.neon
- phpstan-baseline.neon
services:
-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsInputTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsOutputTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
-
class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsCompositeTypeStaticMethodTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
+11 -50
View File
@@ -4,62 +4,23 @@ declare(strict_types=1);
namespace GraphQL;
use Exception;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use SplQueue;
use Throwable;
class Deferred
class Deferred extends SyncPromise
{
/** @var SplQueue|null */
private static $queue;
/** @var callable */
private $callback;
/** @var SyncPromise */
public $promise;
public function __construct(callable $callback)
/**
* @param callable() : mixed $executor
*/
public static function create(callable $executor) : self
{
$this->callback = $callback;
$this->promise = new SyncPromise();
self::getQueue()->enqueue($this);
return new self($executor);
}
public static function getQueue() : SplQueue
/**
* @param callable() : mixed $executor
*/
public function __construct(callable $executor)
{
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);
}
public function run() : void
{
try {
$cb = $this->callback;
$this->promise->resolve($cb());
} catch (Exception $e) {
$this->promise->reject($e);
} catch (Throwable $e) {
$this->promise->reject($e);
}
parent::__construct($executor);
}
}
-16
View File
@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
/**
* Collection of flags for [error debugging](error-handling.md#debugging-tools).
*/
class Debug
{
const INCLUDE_DEBUG_MESSAGE = 1;
const INCLUDE_TRACE = 2;
const RETHROW_INTERNAL_EXCEPTIONS = 4;
const RETHROW_UNSAFE_EXCEPTIONS = 8;
}
+17
View File
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
/**
* Collection of flags for [error debugging](error-handling.md#debugging-tools).
*/
final class DebugFlag
{
public const NONE = 0;
public const INCLUDE_DEBUG_MESSAGE = 1;
public const INCLUDE_TRACE = 2;
public const RETHROW_INTERNAL_EXCEPTIONS = 4;
public const RETHROW_UNSAFE_EXCEPTIONS = 8;
}
+44 -42
View File
@@ -15,6 +15,7 @@ use Traversable;
use function array_filter;
use function array_map;
use function array_values;
use function count;
use function is_array;
use function iterator_to_array;
@@ -38,13 +39,10 @@ class Error extends Exception implements JsonSerializable, ClientAware
const CATEGORY_INTERNAL = 'internal';
/**
* A message describing the Error for debugging purposes.
* Lazily initialized.
*
* @var string
* @var SourceLocation[]
*/
public $message;
/** @var SourceLocation[] */
private $locations;
/**
@@ -72,7 +70,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
*/
private $source;
/** @var int[]|null */
/** @var int[] */
private $positions;
/** @var bool */
@@ -87,16 +85,16 @@ class Error extends Exception implements JsonSerializable, ClientAware
/**
* @param string $message
* @param Node|Node[]|Traversable|null $nodes
* @param mixed[]|null $positions
* @param mixed[] $positions
* @param mixed[]|null $path
* @param Throwable $previous
* @param mixed[] $extensions
*/
public function __construct(
$message,
$message = '',
$nodes = null,
?Source $source = null,
$positions = null,
array $positions = [],
$path = null,
$previous = null,
array $extensions = []
@@ -106,7 +104,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
// Compute list of blame nodes.
if ($nodes instanceof Traversable) {
$nodes = iterator_to_array($nodes);
} elseif ($nodes && ! is_array($nodes)) {
} elseif ($nodes !== null && ! is_array($nodes)) {
$nodes = [$nodes];
}
@@ -114,16 +112,17 @@ class Error extends Exception implements JsonSerializable, ClientAware
$this->source = $source;
$this->positions = $positions;
$this->path = $path;
$this->extensions = $extensions ?: (
$previous && $previous instanceof self
$this->extensions = count($extensions) > 0 ? $extensions : (
$previous instanceof self
? $previous->extensions
: []
);
if ($previous instanceof ClientAware) {
$this->isClientSafe = $previous->isClientSafe();
$this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL;
} elseif ($previous) {
$cat = $previous->getCategory();
$this->category = $cat === '' || $cat === null ? self::CATEGORY_INTERNAL: $cat;
} elseif ($previous !== null) {
$this->isClientSafe = false;
$this->category = self::CATEGORY_INTERNAL;
} else {
@@ -146,25 +145,27 @@ class Error extends Exception implements JsonSerializable, ClientAware
public static function createLocatedError($error, $nodes = null, $path = null)
{
if ($error instanceof self) {
if ($error->path && $error->nodes) {
if ($error->path !== null && $error->nodes !== null && count($error->nodes) !== 0) {
return $error;
}
$nodes = $nodes ?: $error->nodes;
$path = $path ?: $error->path;
$nodes = $nodes ?? $error->nodes;
$path = $path ?? $error->path;
}
$source = $positions = $originalError = null;
$extensions = [];
$source = null;
$originalError = null;
$positions = [];
$extensions = [];
if ($error instanceof self) {
$message = $error->getMessage();
$originalError = $error;
$nodes = $error->nodes ?: $nodes;
$nodes = $error->nodes ?? $nodes;
$source = $error->source;
$positions = $error->positions;
$extensions = $error->extensions;
} elseif ($error instanceof Exception || $error instanceof Throwable) {
} elseif ($error instanceof Throwable) {
$message = $error->getMessage();
$originalError = $error;
} else {
@@ -172,7 +173,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
}
return new static(
$message ?: 'An unknown error occurred.',
$message === '' || $message === null ? 'An unknown error occurred.' : $message,
$nodes,
$source,
$positions,
@@ -206,13 +207,10 @@ class Error extends Exception implements JsonSerializable, ClientAware
return $this->category;
}
/**
* @return Source|null
*/
public function getSource()
public function getSource() : ?Source
{
if ($this->source === null) {
if (! empty($this->nodes[0]) && ! empty($this->nodes[0]->loc)) {
if (isset($this->nodes[0]) && $this->nodes[0]->loc !== null) {
$this->source = $this->nodes[0]->loc->source;
}
}
@@ -223,11 +221,11 @@ class Error extends Exception implements JsonSerializable, ClientAware
/**
* @return int[]
*/
public function getPositions()
public function getPositions() : array
{
if ($this->positions === null && ! empty($this->nodes)) {
if (count($this->positions) === 0 && count($this->nodes ?? []) > 0) {
$positions = array_map(
static function ($node) {
static function ($node) : ?int {
return isset($node->loc) ? $node->loc->start : null;
},
$this->nodes
@@ -235,7 +233,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
$positions = array_filter(
$positions,
static function ($p) {
static function ($p) : bool {
return $p !== null;
}
);
@@ -261,27 +259,29 @@ class Error extends Exception implements JsonSerializable, ClientAware
*
* @api
*/
public function getLocations()
public function getLocations() : array
{
if ($this->locations === null) {
if (! isset($this->locations)) {
$positions = $this->getPositions();
$source = $this->getSource();
$nodes = $this->nodes;
if ($positions && $source) {
if ($source !== null && count($positions) !== 0) {
$this->locations = array_map(
static function ($pos) use ($source) {
static function ($pos) use ($source) : SourceLocation {
return $source->getLocation($pos);
},
$positions
);
} elseif ($nodes) {
} elseif ($nodes !== null && count($nodes) !== 0) {
$locations = array_filter(
array_map(
static function ($node) {
if ($node->loc && $node->loc->source) {
static function ($node) : ?SourceLocation {
if (isset($node->loc->source)) {
return $node->loc->source->getLocation($node->loc->start);
}
return null;
},
$nodes
)
@@ -330,6 +330,8 @@ class Error extends Exception implements JsonSerializable, ClientAware
* @deprecated Use FormattedError::createFromException() instead
*
* @return mixed[]
*
* @codeCoverageIgnore
*/
public function toSerializableArray()
{
@@ -339,18 +341,18 @@ class Error extends Exception implements JsonSerializable, ClientAware
$locations = Utils::map(
$this->getLocations(),
static function (SourceLocation $loc) {
static function (SourceLocation $loc) : array {
return $loc->toSerializableArray();
}
);
if (! empty($locations)) {
if (count($locations) > 0) {
$arr['locations'] = $locations;
}
if (! empty($this->path)) {
if (count($this->path ?? []) > 0) {
$arr['path'] = $this->path;
}
if (! empty($this->extensions)) {
if (count($this->extensions ?? []) > 0) {
$arr['extensions'] = $this->extensions;
}
+45 -67
View File
@@ -6,7 +6,6 @@ namespace GraphQL\Error;
use Countable;
use ErrorException;
use Exception;
use GraphQL\Language\AST\Node;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
@@ -67,10 +66,10 @@ class FormattedError
public static function printError(Error $error)
{
$printedLocations = [];
if ($error->nodes) {
if (count($error->nodes ?? []) !== 0) {
/** @var Node $node */
foreach ($error->nodes as $node) {
if (! $node->loc) {
if ($node->loc === null) {
continue;
}
@@ -83,14 +82,14 @@ class FormattedError
$node->loc->source->getLocation($node->loc->start)
);
}
} elseif ($error->getSource() && $error->getLocations()) {
} elseif ($error->getSource() !== null && count($error->getLocations()) !== 0) {
$source = $error->getSource();
foreach ($error->getLocations() as $location) {
foreach (($error->getLocations() ?? []) as $location) {
$printedLocations[] = self::highlightSourceAtLocation($source, $location);
}
}
return ! $printedLocations
return count($printedLocations) === 0
? $error->getMessage()
: implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
}
@@ -162,11 +161,9 @@ class FormattedError
* This method only exposes exception message when exception implements ClientAware interface
* (or when debug flags are passed).
*
* For a list of available debug flags see GraphQL\Error\Debug constants.
* For a list of available debug flags @see \GraphQL\Error\DebugFlag constants.
*
* @param Throwable $e
* @param bool|int $debug
* @param string $internalErrorMessage
* @param string $internalErrorMessage
*
* @return mixed[]
*
@@ -174,21 +171,15 @@ class FormattedError
*
* @api
*/
public static function createFromException($e, $debug = false, $internalErrorMessage = null)
public static function createFromException(Throwable $exception, int $debug = DebugFlag::NONE, $internalErrorMessage = null) : array
{
Utils::invariant(
$e instanceof Exception || $e instanceof Throwable,
'Expected exception, got %s',
Utils::getVariableType($e)
);
$internalErrorMessage = $internalErrorMessage ?? self::$internalErrorMessage;
$internalErrorMessage = $internalErrorMessage ?: self::$internalErrorMessage;
if ($e instanceof ClientAware) {
if ($exception instanceof ClientAware) {
$formattedError = [
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
'message' => $exception->isClientSafe() ? $exception->getMessage() : $internalErrorMessage,
'extensions' => [
'category' => $e->getCategory(),
'category' => $exception->getCategory(),
],
];
} else {
@@ -200,26 +191,27 @@ class FormattedError
];
}
if ($e instanceof Error) {
if ($exception instanceof Error) {
$locations = Utils::map(
$e->getLocations(),
static function (SourceLocation $loc) {
$exception->getLocations(),
static function (SourceLocation $loc) : array {
return $loc->toSerializableArray();
}
);
if (! empty($locations)) {
if (count($locations) > 0) {
$formattedError['locations'] = $locations;
}
if (! empty($e->path)) {
$formattedError['path'] = $e->path;
if (count($exception->path ?? []) > 0) {
$formattedError['path'] = $exception->path;
}
if (! empty($e->getExtensions())) {
$formattedError['extensions'] = $e->getExtensions() + $formattedError['extensions'];
if (count($exception->getExtensions() ?? []) > 0) {
$formattedError['extensions'] = $exception->getExtensions() + $formattedError['extensions'];
}
}
if ($debug) {
$formattedError = self::addDebugEntries($formattedError, $e, $debug);
if ($debug !== DebugFlag::NONE) {
$formattedError = self::addDebugEntries($formattedError, $exception, $debug);
}
return $formattedError;
@@ -227,54 +219,42 @@ class FormattedError
/**
* Decorates spec-compliant $formattedError with debug entries according to $debug flags
* (see GraphQL\Error\Debug for available flags)
* (@see \GraphQL\Error\DebugFlag for available flags)
*
* @param mixed[] $formattedError
* @param Throwable $e
* @param bool|int $debug
* @param mixed[] $formattedError
*
* @return mixed[]
*
* @throws Throwable
*/
public static function addDebugEntries(array $formattedError, $e, $debug)
public static function addDebugEntries(array $formattedError, Throwable $e, int $debugFlag) : array
{
if (! $debug) {
if ($debugFlag === DebugFlag::NONE) {
return $formattedError;
}
Utils::invariant(
$e instanceof Exception || $e instanceof Throwable,
'Expected exception, got %s',
Utils::getVariableType($e)
);
$debug = (int) $debug;
if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
if (( $debugFlag & DebugFlag::RETHROW_INTERNAL_EXCEPTIONS) !== 0) {
if (! $e instanceof Error) {
throw $e;
}
if ($e->getPrevious()) {
if ($e->getPrevious() !== null) {
throw $e->getPrevious();
}
}
$isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) && $isUnsafe) {
if ($e->getPrevious()) {
throw $e->getPrevious();
}
if (($debugFlag & DebugFlag::RETHROW_UNSAFE_EXCEPTIONS) !== 0 && $isUnsafe && $e->getPrevious() !== null) {
throw $e->getPrevious();
}
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isUnsafe) {
if (($debugFlag & DebugFlag::INCLUDE_DEBUG_MESSAGE) !== 0 && $isUnsafe) {
// Displaying debugMessage as a first entry:
$formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
}
if ($debug & Debug::INCLUDE_TRACE) {
if (($debugFlag & DebugFlag::INCLUDE_TRACE) !== 0) {
if ($e instanceof ErrorException || $e instanceof \Error) {
$formattedError += [
'file' => $e->getFile(),
@@ -282,10 +262,10 @@ class FormattedError
];
}
$isTrivial = $e instanceof Error && ! $e->getPrevious();
$isTrivial = $e instanceof Error && $e->getPrevious() === null;
if (! $isTrivial) {
$debugging = $e->getPrevious() ?: $e;
$debugging = $e->getPrevious() ?? $e;
$formattedError['trace'] = static::toSafeTrace($debugging);
}
}
@@ -296,18 +276,14 @@ 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
*
* @return callable|callable
*/
public static function prepareFormatter(?callable $formatter = null, $debug)
public static function prepareFormatter(?callable $formatter, int $debug) : callable
{
$formatter = $formatter ?: static function ($e) {
$formatter = $formatter ?? static function ($e) : array {
return FormattedError::createFromException($e);
};
if ($debug) {
$formatter = static function ($e) use ($formatter, $debug) {
if ($debug !== DebugFlag::NONE) {
$formatter = static function ($e) use ($formatter, $debug) : array {
return FormattedError::addDebugEntries($formatter($e), $e, $debug);
};
}
@@ -338,12 +314,12 @@ class FormattedError
}
return array_map(
static function ($err) {
static function ($err) : array {
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
if (isset($err['function'])) {
$func = $err['function'];
$args = ! empty($err['args']) ? array_map([self::class, 'printVar'], $err['args']) : [];
$args = array_map([self::class, 'printVar'], $err['args'] ?? []);
$funcStr = $func . '(' . implode(', ', $args) . ')';
if (isset($err['class'])) {
@@ -412,9 +388,9 @@ class FormattedError
{
$formatted = ['message' => $error];
if (! empty($locations)) {
if (count($locations) > 0) {
$formatted['locations'] = array_map(
static function ($loc) {
static function ($loc) : array {
return $loc->toArray();
},
$locations
@@ -428,6 +404,8 @@ class FormattedError
* @deprecated as of v0.10.0, use general purpose method createFromException() instead
*
* @return mixed[]
*
* @codeCoverageIgnore
*/
public static function createFromPHPError(ErrorException $e)
{
@@ -13,4 +13,8 @@ use LogicException;
*/
class InvariantViolation extends LogicException
{
public static function shouldNotHappen() : self
{
return new self('This should not have happened');
}
}
+29 -19
View File
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace GraphQL\Error;
use GraphQL\Exception\InvalidArgument;
use function is_int;
use function trigger_error;
use const E_USER_WARNING;
@@ -15,12 +17,12 @@ use const E_USER_WARNING;
*/
final class Warning
{
const WARNING_ASSIGN = 2;
const WARNING_CONFIG = 4;
const WARNING_FULL_SCHEMA_SCAN = 8;
const WARNING_CONFIG_DEPRECATION = 16;
const WARNING_NOT_A_TYPE = 32;
const ALL = 63;
public const WARNING_ASSIGN = 2;
public const WARNING_CONFIG = 4;
public const WARNING_FULL_SCHEMA_SCAN = 8;
public const WARNING_CONFIG_DEPRECATION = 16;
public const WARNING_NOT_A_TYPE = 32;
public const ALL = 63;
/** @var int */
private static $enableWarnings = self::ALL;
@@ -37,7 +39,7 @@ final class Warning
*
* @api
*/
public static function setWarningHandler(?callable $warningHandler = null)
public static function setWarningHandler(?callable $warningHandler = null) : void
{
self::$warningHandler = $warningHandler;
}
@@ -54,14 +56,16 @@ final class Warning
*
* @api
*/
public static function suppress($suppress = true)
public static function suppress($suppress = true) : void
{
if ($suppress === true) {
self::$enableWarnings = 0;
} elseif ($suppress === false) {
self::$enableWarnings = self::ALL;
} else {
} elseif (is_int($suppress)) {
self::$enableWarnings &= ~$suppress;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress);
}
}
@@ -77,35 +81,41 @@ final class Warning
*
* @api
*/
public static function enable($enable = true)
public static function enable($enable = true) : void
{
if ($enable === true) {
self::$enableWarnings = self::ALL;
} elseif ($enable === false) {
self::$enableWarnings = 0;
} else {
} elseif (is_int($enable)) {
self::$enableWarnings |= $enable;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable);
}
}
public static function warnOnce($errorMessage, $warningId, $messageLevel = null)
public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
{
if (self::$warningHandler) {
$messageLevel = $messageLevel ?? E_USER_WARNING;
if (self::$warningHandler !== null) {
$fn = self::$warningHandler;
$fn($errorMessage, $warningId);
$fn($errorMessage, $warningId, $messageLevel);
} elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) {
self::$warned[$warningId] = true;
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
trigger_error($errorMessage, $messageLevel);
}
}
public static function warn($errorMessage, $warningId, $messageLevel = null)
public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
{
if (self::$warningHandler) {
$messageLevel = $messageLevel ?? E_USER_WARNING;
if (self::$warningHandler !== null) {
$fn = self::$warningHandler;
$fn($errorMessage, $warningId);
$fn($errorMessage, $warningId, $messageLevel);
} elseif ((self::$enableWarnings & $warningId) > 0) {
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
trigger_error($errorMessage, $messageLevel);
}
}
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Exception;
use InvalidArgumentException;
use function gettype;
use function sprintf;
final class InvalidArgument extends InvalidArgumentException
{
/**
* @param mixed $argument
*/
public static function fromExpectedTypeAndArgument(string $expectedType, $argument) : self
{
return new self(sprintf('Expected type "%s", got "%s"', $expectedType, gettype($argument)));
}
}
@@ -14,7 +14,7 @@ use GraphQL\Type\Schema;
* Data that must be available at all points during query execution.
*
* Namely, schema of the type system that is currently executing,
* and the fragments defined in the query document
* and the fragments defined in the query document.
*
* @internal
*/
@@ -45,28 +45,28 @@ class ExecutionContext
public $errors;
/** @var PromiseAdapter */
public $promises;
public $promiseAdapter;
public function __construct(
$schema,
$fragments,
$root,
$rootValue,
$contextValue,
$operation,
$variables,
$variableValues,
$errors,
$fieldResolver,
$promiseAdapter
) {
$this->schema = $schema;
$this->fragments = $fragments;
$this->rootValue = $root;
$this->rootValue = $rootValue;
$this->contextValue = $contextValue;
$this->operation = $operation;
$this->variableValues = $variables;
$this->errors = $errors ?: [];
$this->variableValues = $variableValues;
$this->errors = $errors ?? [];
$this->fieldResolver = $fieldResolver;
$this->promises = $promiseAdapter;
$this->promiseAdapter = $promiseAdapter;
}
public function addError(Error $error)
+13 -9
View File
@@ -4,10 +4,12 @@ declare(strict_types=1);
namespace GraphQL\Executor;
use GraphQL\Error\DebugFlag;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use JsonSerializable;
use function array_map;
use function count;
/**
* Returned after [query execution](executing-queries.md).
@@ -125,35 +127,37 @@ class ExecutionResult implements JsonSerializable
* If debug argument is passed, output of error formatter is enriched which debugging information
* ("debugMessage", "trace" keys depending on flags).
*
* $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from
* GraphQL\Error\Debug
*
* @param bool|int $debug
* $debug argument must sum of flags from @see \GraphQL\Error\DebugFlag
*
* @return mixed[]
*
* @api
*/
public function toArray($debug = false)
public function toArray(int $debug = DebugFlag::NONE) : array
{
$result = [];
if (! empty($this->errors)) {
$errorsHandler = $this->errorsHandler ?: static function (array $errors, callable $formatter) {
if (count($this->errors ?? []) > 0) {
$errorsHandler = $this->errorsHandler ?? static function (array $errors, callable $formatter) : array {
return array_map($formatter, $errors);
};
$result['errors'] = $errorsHandler(
$handledErrors = $errorsHandler(
$this->errors,
FormattedError::prepareFormatter($this->errorFormatter, $debug)
);
// While we know that there were errors initially, they might have been discarded
if ($handledErrors !== []) {
$result['errors'] = $handledErrors;
}
}
if ($this->data !== null) {
$result['data'] = $this->data;
}
if (! empty($this->extensions)) {
if (count($this->extensions ?? []) > 0) {
$result['extensions'] = $this->extensions;
}
+32 -29
View File
@@ -20,7 +20,7 @@ use function is_object;
*/
class Executor
{
/** @var callable|string[] */
/** @var callable */
private static $defaultFieldResolver = [self::class, 'defaultFieldResolver'];
/** @var PromiseAdapter */
@@ -35,7 +35,7 @@ class Executor
}
/**
* Custom default resolve function.
* Set a custom default resolve function.
*/
public static function setDefaultFieldResolver(callable $fieldResolver)
{
@@ -44,9 +44,12 @@ class Executor
public static function getPromiseAdapter() : PromiseAdapter
{
return self::$defaultPromiseAdapter ?: (self::$defaultPromiseAdapter = new SyncPromiseAdapter());
return self::$defaultPromiseAdapter ?? (self::$defaultPromiseAdapter = new SyncPromiseAdapter());
}
/**
* Set a custom default promise adapter.
*/
public static function setPromiseAdapter(?PromiseAdapter $defaultPromiseAdapter = null)
{
self::$defaultPromiseAdapter = $defaultPromiseAdapter;
@@ -58,9 +61,7 @@ class Executor
}
/**
* Custom executor implementation factory.
*
* Will be called with as
* Set a custom executor implementation factory.
*/
public static function setImplementationFactory(callable $implementationFactory)
{
@@ -70,13 +71,13 @@ class Executor
/**
* Executes DocumentNode against given $schema.
*
* Always returns ExecutionResult and never throws. All errors which occur during operation
* execution are collected in `$result->errors`.
* Always returns ExecutionResult and never throws.
* All errors which occur during operation execution are collected in `$result->errors`.
*
* @param mixed|null $rootValue
* @param mixed|null $contextValue
* @param mixed[]|ArrayAccess|null $variableValues
* @param string|null $operationName
* @param mixed|null $rootValue
* @param mixed|null $contextValue
* @param array<mixed>|ArrayAccess|null $variableValues
* @param string|null $operationName
*
* @return ExecutionResult|Promise
*
@@ -119,10 +120,10 @@ class Executor
*
* Useful for async PHP platforms.
*
* @param mixed|null $rootValue
* @param mixed|null $contextValue
* @param mixed[]|null $variableValues
* @param string|null $operationName
* @param mixed|null $rootValue
* @param mixed|null $contextValue
* @param array<mixed>|null $variableValues
* @param string|null $operationName
*
* @return Promise
*
@@ -149,7 +150,7 @@ class Executor
$contextValue,
$variableValues,
$operationName,
$fieldResolver ?: self::$defaultFieldResolver
$fieldResolver ?? self::$defaultFieldResolver
);
return $executor->doExecute();
@@ -157,31 +158,33 @@ class Executor
/**
* If a resolve function is not given, then a default resolve behavior is used
* which takes the property of the source object of the same name as the field
* which takes the property of the root value of the same name as the field
* and returns it as the result, or if it's a function, returns the result
* of calling that function while passing along args and context.
*
* @param mixed $source
* @param mixed[] $args
* @param mixed|null $context
* @param mixed $objectValue
* @param array<string, mixed> $args
* @param mixed|null $contextValue
*
* @return mixed|null
*/
public static function defaultFieldResolver($source, $args, $context, ResolveInfo $info)
public static function defaultFieldResolver($objectValue, $args, $contextValue, ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
if (is_array($source) || $source instanceof ArrayAccess) {
if (isset($source[$fieldName])) {
$property = $source[$fieldName];
if (is_array($objectValue) || $objectValue instanceof ArrayAccess) {
if (isset($objectValue[$fieldName])) {
$property = $objectValue[$fieldName];
}
} elseif (is_object($source)) {
if (isset($source->{$fieldName})) {
$property = $source->{$fieldName};
} elseif (is_object($objectValue)) {
if (isset($objectValue->{$fieldName})) {
$property = $objectValue->{$fieldName};
}
}
return $property instanceof Closure ? $property($source, $args, $context, $info) : $property;
return $property instanceof Closure
? $property($objectValue, $args, $contextValue, $info)
: $property;
}
}
@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use Amp\Deferred;
use Amp\Failure;
use Amp\Promise as AmpPromise;
use Amp\Success;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use Throwable;
use function Amp\Promise\all;
use function array_replace;
class AmpPromiseAdapter implements PromiseAdapter
{
/**
* @inheritdoc
*/
public function isThenable($value) : bool
{
return $value instanceof AmpPromise;
}
/**
* @inheritdoc
*/
public function convertThenable($thenable) : Promise
{
return new Promise($thenable, $this);
}
/**
* @inheritdoc
*/
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null) : Promise
{
$deferred = new Deferred();
$onResolve = static function (?Throwable $reason, $value) use ($onFulfilled, $onRejected, $deferred) : void {
if ($reason === null && $onFulfilled !== null) {
self::resolveWithCallable($deferred, $onFulfilled, $value);
} elseif ($reason === null) {
$deferred->resolve($value);
} elseif ($onRejected !== null) {
self::resolveWithCallable($deferred, $onRejected, $reason);
} else {
$deferred->fail($reason);
}
};
/** @var AmpPromise $adoptedPromise */
$adoptedPromise = $promise->adoptedPromise;
$adoptedPromise->onResolve($onResolve);
return new Promise($deferred->promise(), $this);
}
/**
* @inheritdoc
*/
public function create(callable $resolver) : Promise
{
$deferred = new Deferred();
$resolver(
static function ($value) use ($deferred) : void {
$deferred->resolve($value);
},
static function (Throwable $exception) use ($deferred) : void {
$deferred->fail($exception);
}
);
return new Promise($deferred->promise(), $this);
}
/**
* @inheritdoc
*/
public function createFulfilled($value = null) : Promise
{
$promise = new Success($value);
return new Promise($promise, $this);
}
/**
* @inheritdoc
*/
public function createRejected($reason) : Promise
{
$promise = new Failure($reason);
return new Promise($promise, $this);
}
/**
* @inheritdoc
*/
public function all(array $promisesOrValues) : Promise
{
/** @var AmpPromise[] $promises */
$promises = [];
foreach ($promisesOrValues as $key => $item) {
if ($item instanceof Promise) {
$promises[$key] = $item->adoptedPromise;
} elseif ($item instanceof AmpPromise) {
$promises[$key] = $item;
}
}
$deferred = new Deferred();
$onResolve = static function (?Throwable $reason, ?array $values) use ($promisesOrValues, $deferred) : void {
if ($reason === null) {
$deferred->resolve(array_replace($promisesOrValues, $values));
return;
}
$deferred->fail($reason);
};
all($promises)->onResolve($onResolve);
return new Promise($deferred->promise(), $this);
}
private static function resolveWithCallable(Deferred $deferred, callable $callback, $argument) : void
{
try {
$result = $callback($argument);
} catch (Throwable $exception) {
$deferred->fail($exception);
return;
}
if ($result instanceof Promise) {
$result = $result->adoptedPromise;
}
$deferred->resolve($result);
}
}
@@ -85,7 +85,7 @@ class ReactPromiseAdapter implements PromiseAdapter
}
);
$promise = all($promisesOrValues)->then(static function ($values) use ($promisesOrValues) {
$promise = all($promisesOrValues)->then(static function ($values) use ($promisesOrValues) : array {
$orderedResults = [];
foreach ($promisesOrValues as $key => $value) {
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use Exception;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Utils\Utils;
use SplQueue;
use Throwable;
@@ -15,6 +14,14 @@ use function method_exists;
/**
* Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
* (using queue to defer promises execution)
*
* Note:
* Library users are not supposed to use SyncPromise class in their resolvers.
* Instead they should use GraphQL\Deferred which enforces $executor callback in the constructor.
*
* Root SyncPromise without explicit $executor will never resolve (actually throw while trying).
* The whole point of Deferred is to ensure it never happens and that any resolver creates
* at least one $executor to start the promise chain.
*/
class SyncPromise
{
@@ -28,7 +35,7 @@ class SyncPromise
/** @var string */
public $state = self::PENDING;
/** @var ExecutionResult|Throwable */
/** @var mixed */
public $result;
/**
@@ -38,16 +45,33 @@ class SyncPromise
*/
private $waiting = [];
public static function runQueue()
public static function runQueue() : void
{
$q = self::$queue;
while ($q && ! $q->isEmpty()) {
while ($q !== null && ! $q->isEmpty()) {
$task = $q->dequeue();
$task();
}
}
public function resolve($value)
/**
* @param callable() : mixed $executor
*/
public function __construct(?callable $executor = null)
{
if ($executor === null) {
return;
}
self::getQueue()->enqueue(function () use ($executor) : void {
try {
$this->resolve($executor());
} catch (Throwable $e) {
$this->reject($e);
}
});
}
public function resolve($value) : self
{
switch ($this->state) {
case self::PENDING:
@@ -56,10 +80,10 @@ class SyncPromise
}
if (is_object($value) && method_exists($value, 'then')) {
$value->then(
function ($resolvedValue) {
function ($resolvedValue) : void {
$this->resolve($resolvedValue);
},
function ($reason) {
function ($reason) : void {
$this->reject($reason);
}
);
@@ -83,9 +107,9 @@ class SyncPromise
return $this;
}
public function reject($reason)
public function reject($reason) : self
{
if (! $reason instanceof Exception && ! $reason instanceof Throwable) {
if (! $reason instanceof Throwable) {
throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
}
@@ -107,7 +131,7 @@ class SyncPromise
return $this;
}
private function enqueueWaitingPromises()
private function enqueueWaitingPromises() : void
{
Utils::invariant(
$this->state !== self::PENDING,
@@ -115,15 +139,13 @@ class SyncPromise
);
foreach ($this->waiting as $descriptor) {
self::getQueue()->enqueue(function () use ($descriptor) {
/** @var $promise self */
self::getQueue()->enqueue(function () use ($descriptor) : void {
/** @var self $promise */
[$promise, $onFulfilled, $onRejected] = $descriptor;
if ($this->state === self::FULFILLED) {
try {
$promise->resolve($onFulfilled === null ? $this->result : $onFulfilled($this->result));
} catch (Exception $e) {
$promise->reject($e);
} catch (Throwable $e) {
$promise->reject($e);
}
@@ -134,8 +156,6 @@ class SyncPromise
} else {
$promise->resolve($onRejected($this->result));
}
} catch (Exception $e) {
$promise->reject($e);
} catch (Throwable $e) {
$promise->reject($e);
}
@@ -145,17 +165,21 @@ class SyncPromise
$this->waiting = [];
}
public static function getQueue()
public static function getQueue() : SplQueue
{
return self::$queue ?: self::$queue = new SplQueue();
return self::$queue ?? self::$queue = new SplQueue();
}
public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
/**
* @param callable(mixed) : mixed $onFulfilled
* @param callable(Throwable) : mixed $onRejected
*/
public function then(?callable $onFulfilled = null, ?callable $onRejected = null) : self
{
if ($this->state === self::REJECTED && ! $onRejected) {
if ($this->state === self::REJECTED && $onRejected === null) {
return $this;
}
if ($this->state === self::FULFILLED && ! $onFulfilled) {
if ($this->state === self::FULFILLED && $onFulfilled === null) {
return $this;
}
$tmp = new self();
@@ -167,4 +191,12 @@ class SyncPromise
return $tmp;
}
/**
* @param callable(Throwable) : mixed $onRejected
*/
public function catch(callable $onRejected) : self
{
return $this->then(null, $onRejected);
}
}
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use Exception;
use GraphQL\Deferred;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Promise\Promise;
@@ -25,7 +23,7 @@ class SyncPromiseAdapter implements PromiseAdapter
*/
public function isThenable($value)
{
return $value instanceof Deferred;
return $value instanceof SyncPromise;
}
/**
@@ -33,11 +31,12 @@ class SyncPromiseAdapter implements PromiseAdapter
*/
public function convertThenable($thenable)
{
if (! $thenable instanceof Deferred) {
if (! $thenable instanceof SyncPromise) {
// End-users should always use Deferred (and don't use SyncPromise directly)
throw new InvariantViolation('Expected instance of GraphQL\Deferred, got ' . Utils::printSafe($thenable));
}
return new Promise($thenable->promise, $this);
return new Promise($thenable, $this);
}
/**
@@ -69,8 +68,6 @@ class SyncPromiseAdapter implements PromiseAdapter
'reject',
]
);
} catch (Exception $e) {
$promise->reject($e);
} catch (Throwable $e) {
$promise->reject($e);
}
@@ -113,7 +110,7 @@ class SyncPromiseAdapter implements PromiseAdapter
if ($promiseOrValue instanceof Promise) {
$result[$index] = null;
$promiseOrValue->then(
static function ($value) use ($index, &$count, $total, &$result, $all) {
static function ($value) use ($index, &$count, $total, &$result, $all) : void {
$result[$index] = $value;
$count++;
if ($count < $total) {
@@ -144,13 +141,11 @@ class SyncPromiseAdapter implements PromiseAdapter
public function wait(Promise $promise)
{
$this->beforeWait($promise);
$dfdQueue = Deferred::getQueue();
$promiseQueue = SyncPromise::getQueue();
$taskQueue = SyncPromise::getQueue();
while ($promise->adoptedPromise->state === SyncPromise::PENDING &&
! ($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
! $taskQueue->isEmpty()
) {
Deferred::runQueue();
SyncPromise::runQueue();
$this->onWait($promise);
}
File diff suppressed because it is too large Load Diff
+140 -84
View File
@@ -6,22 +6,34 @@ namespace GraphQL\Executor;
use GraphQL\Error\Error;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\ObjectValueNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\Printer;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\AST;
@@ -32,6 +44,7 @@ use stdClass;
use Throwable;
use function array_key_exists;
use function array_map;
use function count;
use function sprintf;
class Values
@@ -55,48 +68,9 @@ class Values
/** @var InputType|Type $varType */
$varType = TypeInfo::typeFromAST($schema, $varDefNode->type);
if (Type::isInputType($varType)) {
if (array_key_exists($varName, $inputs)) {
$value = $inputs[$varName];
$coerced = Value::coerceValue($value, $varType, $varDefNode);
/** @var Error[] $coercionErrors */
$coercionErrors = $coerced['errors'];
if (empty($coercionErrors)) {
$coercedValues[$varName] = $coerced['value'];
} else {
$messagePrelude = sprintf(
'Variable "$%s" got invalid value %s; ',
$varName,
Utils::printSafeJson($value)
);
foreach ($coercionErrors as $error) {
$errors[] = new Error(
$messagePrelude . $error->getMessage(),
$error->getNodes(),
$error->getSource(),
$error->getPositions(),
$error->getPath(),
$error->getPrevious(),
$error->getExtensions()
);
}
}
} else {
if ($varType instanceof NonNull) {
$errors[] = new Error(
sprintf(
'Variable "$%s" of required type "%s" was not provided.',
$varName,
$varType
),
[$varDefNode]
);
} elseif ($varDefNode->defaultValue) {
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
}
}
} else {
if (! Type::isInputType($varType)) {
// Must use input types for variables. This should be caught during
// validation, however is checked again here for safety.
$errors[] = new Error(
sprintf(
'Variable "$%s" expected value of type "%s" which cannot be used as an input type.',
@@ -105,10 +79,65 @@ class Values
),
[$varDefNode->type]
);
} else {
$hasValue = array_key_exists($varName, $inputs);
$value = $hasValue ? $inputs[$varName] : Utils::undefined();
if (! $hasValue && ($varDefNode->defaultValue !== null)) {
// If no value was provided to a variable with a default value,
// use the default value.
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
} elseif ((! $hasValue || $value === null) && ($varType instanceof NonNull)) {
// If no value or a nullish value was provided to a variable with a
// non-null type (required), produce an error.
$errors[] = new Error(
sprintf(
$hasValue
? 'Variable "$%s" of non-null type "%s" must not be null.'
: 'Variable "$%s" of required type "%s" was not provided.',
$varName,
Utils::printSafe($varType)
),
[$varDefNode]
);
} elseif ($hasValue) {
if ($value === null) {
// If the explicit value `null` was provided, an entry in the coerced
// values must exist as the value `null`.
$coercedValues[$varName] = null;
} else {
// Otherwise, a non-null value was provided, coerce it to the expected
// type or report an error if coercion fails.
$coerced = Value::coerceValue($value, $varType, $varDefNode);
/** @var Error[] $coercionErrors */
$coercionErrors = $coerced['errors'];
if (count($coercionErrors ?? []) > 0) {
$messagePrelude = sprintf(
'Variable "$%s" got invalid value %s; ',
$varName,
Utils::printSafeJson($value)
);
foreach ($coercionErrors as $error) {
$errors[] = new Error(
$messagePrelude . $error->getMessage(),
$error->getNodes(),
$error->getSource(),
$error->getPositions(),
$error->getPath(),
$error->getPrevious(),
$error->getExtensions()
);
}
} else {
$coercedValues[$varName] = $coerced['value'];
}
}
}
}
}
if (! empty($errors)) {
if (count($errors) > 0) {
return [$errors, null];
}
@@ -132,7 +161,7 @@ class Values
if (isset($node->directives) && $node->directives instanceof NodeList) {
$directiveNode = Utils::find(
$node->directives,
static function (DirectiveNode $directive) use ($directiveDef) {
static function (DirectiveNode $directive) use ($directiveDef) : bool {
return $directive->name->value === $directiveDef->name;
}
);
@@ -159,15 +188,11 @@ class Values
*/
public static function getArgumentValues($def, $node, $variableValues = null)
{
if (empty($def->args)) {
return [];
}
$argumentNodes = $node->arguments;
if (empty($argumentNodes)) {
if (count($def->args) === 0) {
return [];
}
$argumentNodes = $node->arguments;
$argumentValueMap = [];
foreach ($argumentNodes as $argumentNode) {
$argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
@@ -196,27 +221,32 @@ class Values
$argType = $argumentDefinition->getType();
$argumentValueNode = $argumentValueMap[$name] ?? null;
if (! $argumentValueNode) {
if ($argumentDefinition->defaultValueExists()) {
$coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ($argType instanceof NonNull) {
if ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value;
$hasValue = array_key_exists($variableName, $variableValues ?? []);
$isNull = $hasValue ? $variableValues[$variableName] === null : false;
} else {
$hasValue = $argumentValueNode !== null;
$isNull = $argumentValueNode instanceof NullValueNode;
}
if (! $hasValue && $argumentDefinition->defaultValueExists()) {
// If no argument was provided where the definition has a default value,
// use the default value.
$coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ((! $hasValue || $isNull) && ($argType instanceof NonNull)) {
// If no argument or a null value was provided to an argument with a
// non-null type (required), produce a field error.
if ($isNull) {
throw new Error(
'Argument "' . $name . '" of required type ' .
'"' . Utils::printSafe($argType) . '" was not provided.',
'Argument "' . $name . '" of non-null type ' .
'"' . Utils::printSafe($argType) . '" must not be null.',
$referenceNode
);
}
} elseif ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value;
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.
$coercedValues[$name] = $variableValues[$variableName];
} elseif ($argumentDefinition->defaultValueExists()) {
$coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ($argType instanceof NonNull) {
if ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value;
throw new Error(
'Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' .
'provided the variable "$' . $variableName . '" which was not provided ' .
@@ -224,19 +254,38 @@ class Values
[$argumentValueNode]
);
}
} else {
$valueNode = $argumentValueNode;
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
if (Utils::isInvalid($coercedValue)) {
// Note: ValuesOfCorrectType validation should catch this before
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new Error(
'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.',
[$argumentValueNode]
);
throw new Error(
'Argument "' . $name . '" of required type ' .
'"' . Utils::printSafe($argType) . '" was not provided.',
$referenceNode
);
} elseif ($hasValue) {
if ($argumentValueNode instanceof NullValueNode) {
// If the explicit value `null` was provided, an entry in the coerced
// values must exist as the value `null`.
$coercedValues[$name] = null;
} elseif ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value;
Utils::invariant($variableValues !== null, 'Must exist for hasValue to be true.');
// Note: This does no further checking that this variable is correct.
// This assumes that this query has been validated and the variable
// usage here is of the correct type.
$coercedValues[$name] = $variableValues[$variableName] ?? null;
} else {
$valueNode = $argumentValueNode;
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
if (Utils::isInvalid($coercedValue)) {
// Note: ValuesOfCorrectType validation should catch this before
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new Error(
'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.',
[$argumentValueNode]
);
}
$coercedValues[$name] = $coercedValue;
}
$coercedValues[$name] = $coercedValue;
}
}
@@ -246,12 +295,15 @@ class Values
/**
* @deprecated as of 8.0 (Moved to \GraphQL\Utils\AST::valueFromAST)
*
* @param ValueNode $valueNode
* @param mixed[]|null $variables
* @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $valueNode
* @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type
* @param mixed[]|null $variables
*
* @return mixed[]|stdClass|null
*
* @codeCoverageIgnore
*/
public static function valueFromAST($valueNode, InputType $type, ?array $variables = null)
public static function valueFromAST(ValueNode $valueNode, InputType $type, ?array $variables = null)
{
return AST::valueFromAST($valueNode, $type, $variables);
}
@@ -259,9 +311,12 @@ class Values
/**
* @deprecated as of 0.12 (Use coerceValue() directly for richer information)
*
* @param mixed[] $value
* @param mixed[] $value
* @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type
*
* @return string[]
*
* @codeCoverageIgnore
*/
public static function isValidPHPValue($value, InputType $type)
{
@@ -269,10 +324,11 @@ class Values
return $errors
? array_map(
static function (Throwable $error) {
static function (Throwable $error) : string {
return $error->getMessage();
},
$errors
) : [];
)
: [];
}
}
@@ -6,23 +6,31 @@ namespace GraphQL\Experimental\Executor;
use Generator;
use GraphQL\Error\Error;
use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\DefinitionNode;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FloatValueNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\ObjectValueNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use function count;
use function sprintf;
/**
@@ -48,7 +56,7 @@ class Collector
/** @var FieldNode[][] */
private $fields;
/** @var string[] */
/** @var array<string, bool> */
private $visitedFragments;
public function __construct(Schema $schema, Runtime $runtime)
@@ -64,8 +72,7 @@ class Collector
foreach ($documentNode->definitions as $definitionNode) {
/** @var DefinitionNode|Node $definitionNode */
if ($definitionNode->kind === NodeKind::OPERATION_DEFINITION) {
/** @var OperationDefinitionNode $definitionNode */
if ($definitionNode instanceof OperationDefinitionNode) {
if ($operationName === null && $this->operation !== null) {
$hasMultipleAssumedOperations = true;
}
@@ -74,8 +81,7 @@ class Collector
) {
$this->operation = $definitionNode;
}
} elseif ($definitionNode->kind === NodeKind::FRAGMENT_DEFINITION) {
/** @var FragmentDefinitionNode $definitionNode */
} elseif ($definitionNode instanceof FragmentDefinitionNode) {
$this->fragments[$definitionNode->name->value] = $definitionNode;
}
}
@@ -122,7 +128,7 @@ class Collector
$fieldName = $fieldNode->name->value;
$argumentValueMap = null;
if (! empty($fieldNode->arguments)) {
if (count($fieldNode->arguments) > 0) {
foreach ($fieldNode->arguments as $argumentNode) {
$argumentValueMap = $argumentValueMap ?? [];
$argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
@@ -149,11 +155,10 @@ class Collector
foreach ($selectionSet->selections as $selection) {
/** @var FieldNode|FragmentSpreadNode|InlineFragmentNode $selection */
if (! empty($selection->directives)) {
if (count($selection->directives) > 0) {
foreach ($selection->directives as $directiveNode) {
if ($directiveNode->name->value === Directive::SKIP_NAME) {
/** @var ValueNode|null $condition */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $condition */
$condition = null;
foreach ($directiveNode->arguments as $argumentNode) {
if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
@@ -173,7 +178,7 @@ class Collector
}
}
} elseif ($directiveNode->name->value === Directive::INCLUDE_NAME) {
/** @var ValueNode|null $condition */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $condition */
$condition = null;
foreach ($directiveNode->arguments as $argumentNode) {
if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
@@ -196,19 +201,15 @@ class Collector
}
}
if ($selection->kind === NodeKind::FIELD) {
/** @var FieldNode $selection */
$resultName = $selection->alias ? $selection->alias->value : $selection->name->value;
if ($selection instanceof FieldNode) {
$resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value;
if (! isset($this->fields[$resultName])) {
$this->fields[$resultName] = [];
}
$this->fields[$resultName][] = $selection;
} elseif ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
/** @var FragmentSpreadNode $selection */
} elseif ($selection instanceof FragmentSpreadNode) {
$fragmentName = $selection->name->value;
if (isset($this->visitedFragments[$fragmentName])) {
@@ -243,15 +244,13 @@ class Collector
continue;
}
} elseif ($conditionType instanceof AbstractType) {
if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
if (! $this->schema->isSubType($conditionType, $runtimeType)) {
continue;
}
}
$this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet);
} elseif ($selection->kind === NodeKind::INLINE_FRAGMENT) {
/** @var InlineFragmentNode $selection */
} elseif ($selection instanceof InlineFragmentNode) {
if ($selection->typeCondition !== null) {
$conditionTypeName = $selection->typeCondition->name->value;
@@ -270,7 +269,7 @@ class Collector
continue;
}
} elseif ($conditionType instanceof AbstractType) {
if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
if (! $this->schema->isSubType($conditionType, $runtimeType)) {
continue;
}
}
@@ -27,7 +27,7 @@ class CoroutineContext
/** @var string[] */
public $path;
/** @var ResolveInfo|null */
/** @var ResolveInfo */
public $resolveInfo;
/** @var string[]|null */
@@ -18,6 +18,8 @@ use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\LeafType;
@@ -25,6 +27,7 @@ use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Introspection;
@@ -34,6 +37,7 @@ use GraphQL\Utils\Utils;
use SplQueue;
use stdClass;
use Throwable;
use function count;
use function is_array;
use function is_string;
use function sprintf;
@@ -70,10 +74,10 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
/** @var string|null */
private $operationName;
/** @var Collector */
/** @var Collector|null */
private $collector;
/** @var Error[] */
/** @var array<Error> */
private $errors;
/** @var SplQueue */
@@ -82,10 +86,10 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
/** @var SplQueue */
private $schedule;
/** @var stdClass */
/** @var stdClass|null */
private $rootResult;
/** @var int */
/** @var int|null */
private $pending;
/** @var callable */
@@ -105,6 +109,9 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
self::$undefined = Utils::undefined();
}
$this->errors = [];
$this->queue = new SplQueue();
$this->schedule = new SplQueue();
$this->schema = $schema;
$this->fieldResolver = $fieldResolver;
$this->promiseAdapter = $promiseAdapter;
@@ -140,11 +147,12 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
private static function resultToArray($value, $emptyObjectAsStdClass = true)
{
if ($value instanceof stdClass) {
$array = [];
foreach ($value as $propertyName => $propertyValue) {
$array = (array) $value;
foreach ($array as $propertyName => $propertyValue) {
$array[$propertyName] = self::resultToArray($propertyValue);
}
if ($emptyObjectAsStdClass && empty($array)) {
if ($emptyObjectAsStdClass && count($array) === 0) {
return new stdClass();
}
@@ -174,17 +182,17 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$this->collector = new Collector($this->schema, $this);
$this->collector->initialize($this->documentNode, $this->operationName);
if (! empty($this->errors)) {
if (count($this->errors) > 0) {
return $this->promiseAdapter->createFulfilled($this->finishExecute(null, $this->errors));
}
[$errors, $coercedVariableValues] = Values::getVariableValues(
$this->schema,
$this->collector->operation->variableDefinitions ?: [],
$this->rawVariableValues ?: []
$this->collector->operation->variableDefinitions ?? [],
$this->rawVariableValues ?? []
);
if (! empty($errors)) {
if (count($errors ?? []) > 0) {
return $this->promiseAdapter->createFulfilled($this->finishExecute(null, $errors));
}
@@ -219,7 +227,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$this->run();
if ($this->pending > 0) {
return $this->promiseAdapter->create(function (callable $resolve) {
return $this->promiseAdapter->create(function (callable $resolve) : void {
$this->doResolve = $resolve;
});
}
@@ -234,9 +242,9 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
private function finishExecute($value, array $errors) : ExecutionResult
{
$this->rootResult = null;
$this->errors = null;
$this->queue = null;
$this->schedule = null;
$this->errors = [];
$this->queue = new SplQueue();
$this->schedule = new SplQueue();
$this->pending = null;
$this->collector = null;
$this->variableValues = null;
@@ -250,6 +258,8 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
/**
* @internal
*
* @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type
*/
public function evaluate(ValueNode $valueNode, InputType $type)
{
@@ -304,13 +314,13 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$this->promiseAdapter
->then(
$value,
function ($value) use ($strand) {
function ($value) use ($strand) : void {
$strand->success = true;
$strand->value = $value;
$this->queue->enqueue($strand);
$this->done();
},
function (Throwable $throwable) use ($strand) {
function (Throwable $throwable) use ($strand) : void {
$strand->success = false;
$strand->value = $throwable;
$this->queue->enqueue($strand);
@@ -396,9 +406,8 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$returnType = $fieldDefinition->getType();
$ctx->resolveInfo = new ResolveInfo(
$ctx->shared->fieldName,
$fieldDefinition,
$ctx->shared->fieldNodes,
$returnType,
$ctx->type,
$ctx->path,
$this->schema,
@@ -498,7 +507,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($type !== $this->schema->getType($type->name)) {
$hint = '';
if ($this->schema->getConfig()->typeLoader) {
if ($this->schema->getConfig()->typeLoader !== null) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type,
@@ -543,7 +552,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($nonNull && $returnValue === null) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Cannot return null for non-nullable field %s.%s.',
'Cannot return null for non-nullable field "%s.%s".',
$ctx->type->name,
$ctx->shared->fieldName
)),
@@ -620,8 +629,9 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
foreach ($value as $itemValue) {
++$index;
$itemPath = $path;
$itemPath[] = $index; // !!! use arrays COW semantics
$itemPath = $path;
$itemPath[] = $index; // !!! use arrays COW semantics
$ctx->resolveInfo->path = $itemPath;
try {
if (! $this->completeValueFast($ctx, $itemType, $itemValue, $itemPath, $itemReturnValue)) {
@@ -646,7 +656,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
} else {
if ($type !== $this->schema->getType($type->name)) {
$hint = '';
if ($this->schema->getConfig()->typeLoader) {
if ($this->schema->getConfig()->typeLoader !== null) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type,
@@ -735,7 +745,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$returnValue = null;
goto CHECKED_RETURN;
} elseif (! $this->schema->isPossibleType($type, $objectType)) {
} elseif (! $this->schema->isSubType($type, $objectType)) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Runtime Object type "%s" is not a possible type for "%s".',
@@ -820,9 +830,16 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
} else {
$childContexts = [];
foreach ($this->collector->collectFields($objectType, $ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)) as $childShared) {
/** @var CoroutineContextShared $childShared */
$fields = [];
if ($this->collector !== null) {
$fields = $this->collector->collectFields(
$objectType,
$ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)
);
}
/** @var CoroutineContextShared $childShared */
foreach ($fields as $childShared) {
$childPath = $path;
$childPath[] = $childShared->resultName; // !!! uses array COW semantics
$childCtx = new CoroutineContext(
@@ -863,7 +880,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($nonNull && $returnValue === null) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Cannot return null for non-nullable field %s.%s.',
'Cannot return null for non-nullable field "%s.%s".',
$ctx->type->name,
$ctx->shared->fieldName
)),
@@ -894,6 +911,11 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
return $ctx->shared->mergedSelectionSet = new SelectionSetNode(['selections' => $selections]);
}
/**
* @param InterfaceType|UnionType $abstractType
*
* @return Generator|ObjectType|Type|null
*/
private function resolveTypeSlow(CoroutineContext $ctx, $value, AbstractType $abstractType)
{
if ($value !== null &&
@@ -904,7 +926,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
return $this->schema->getType($value['__typename']);
}
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader) {
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader !== null) {
Warning::warnOnce(
sprintf(
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
@@ -926,7 +948,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$selectedType = null;
foreach ($possibleTypes as $type) {
$typeCheck = yield $type->isTypeOf($value, $this->contextValue, $ctx->resolveInfo);
if ($selectedType !== null || $typeCheck !== true) {
if ($selectedType !== null || ! $typeCheck) {
continue;
}
@@ -5,13 +5,21 @@ declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ScalarType;
/**
* @internal
*/
interface Runtime
{
/**
* @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type
*/
public function evaluate(ValueNode $valueNode, InputType $type);
public function addError($error);
+25 -11
View File
@@ -16,12 +16,14 @@ use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema as SchemaType;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\ValidationRule;
use function array_values;
use function count;
use function trigger_error;
use const E_USER_DEPRECATED;
@@ -47,9 +49,11 @@ class GraphQL
* rootValue:
* The value provided as the first argument to resolver functions on the top
* level type (e.g. the query object type).
* context:
* The value provided as the third argument to all resolvers.
* Use this to pass current session, user data, etc
* contextValue:
* The context value is provided as an argument to resolver functions after
* field arguments. It is used to pass shared information useful at any point
* during executing this query, for example the currently logged in user and
* connections to databases or other services.
* variableValues:
* A mapping of variable name to runtime value to use for all variables
* defined in the requestString.
@@ -68,7 +72,7 @@ class GraphQL
*
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param mixed $contextValue
* @param mixed[]|null $variableValues
* @param ValidationRule[] $validationRules
*
@@ -78,7 +82,7 @@ class GraphQL
SchemaType $schema,
$source,
$rootValue = null,
$context = null,
$contextValue = null,
$variableValues = null,
?string $operationName = null,
?callable $fieldResolver = null,
@@ -91,7 +95,7 @@ class GraphQL
$schema,
$source,
$rootValue,
$context,
$contextValue,
$variableValues,
$operationName,
$fieldResolver,
@@ -128,11 +132,11 @@ class GraphQL
if ($source instanceof DocumentNode) {
$documentNode = $source;
} else {
$documentNode = Parser::parse(new Source($source ?: '', 'GraphQL'));
$documentNode = Parser::parse(new Source($source ?? '', 'GraphQL'));
}
// FIXME
if (empty($validationRules)) {
if (count($validationRules ?? []) === 0) {
/** @var QueryComplexity $queryComplexity */
$queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
$queryComplexity->setRawVariableValues($variableValues);
@@ -148,7 +152,7 @@ class GraphQL
$validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);
if (! empty($validationErrors)) {
if (count($validationErrors) > 0) {
return $promiseAdapter->createFulfilled(
new ExecutionResult(null, $validationErrors)
);
@@ -180,6 +184,8 @@ class GraphQL
* @param mixed[]|null $variableValues
*
* @return Promise|mixed[]
*
* @codeCoverageIgnore
*/
public static function execute(
SchemaType $schema,
@@ -208,7 +214,7 @@ class GraphQL
if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result)->toArray();
} else {
$result = $result->then(static function (ExecutionResult $r) {
$result = $result->then(static function (ExecutionResult $r) : array {
return $r->toArray();
});
}
@@ -225,6 +231,8 @@ class GraphQL
* @param mixed[]|null $variableValues
*
* @return ExecutionResult|Promise
*
* @codeCoverageIgnore
*/
public static function executeAndReturnResult(
SchemaType $schema,
@@ -285,7 +293,7 @@ class GraphQL
* Replaces standard types with types from this list (matching by name)
* Standard types not listed here remain untouched.
*
* @param Type[] $types
* @param array<string, ScalarType> $types
*
* @api
*/
@@ -326,6 +334,10 @@ class GraphQL
*/
public static function useExperimentalExecutor()
{
trigger_error(
'Experimental Executor is deprecated and will be removed in the next major version',
E_USER_DEPRECATED
);
Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
}
@@ -343,6 +355,8 @@ class GraphQL
* @deprecated Renamed to getStandardDirectives
*
* @return Directive[]
*
* @codeCoverageIgnore
*/
public static function getInternalDirectives() : array
{
@@ -9,7 +9,7 @@ class ArgumentNode extends Node
/** @var string */
public $kind = NodeKind::ARGUMENT;
/** @var ValueNode */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode */
public $value;
/** @var NameNode */
@@ -7,7 +7,7 @@ namespace GraphQL\Language\AST;
/**
* export type DefinitionNode =
* | ExecutableDefinitionNode
* | TypeSystemDefinitionNode; // experimental non-spec addition.
* | TypeSystemDefinitionNode;
*/
interface DefinitionNode
{
@@ -12,12 +12,15 @@ class DirectiveDefinitionNode extends Node implements TypeSystemDefinitionNode
/** @var NameNode */
public $name;
/** @var ArgumentNode[] */
public $arguments;
/** @var NameNode[] */
public $locations;
/** @var StringValueNode|null */
public $description;
/** @var NodeList<InputValueDefinitionNode> */
public $arguments;
/** @var bool */
public $repeatable;
/** @var NodeList<NameNode> */
public $locations;
}
@@ -12,6 +12,6 @@ class DirectiveNode extends Node
/** @var NameNode */
public $name;
/** @var ArgumentNode[] */
/** @var NodeList<ArgumentNode> */
public $arguments;
}
@@ -9,6 +9,6 @@ class DocumentNode extends Node
/** @var string */
public $kind = NodeKind::DOCUMENT;
/** @var NodeList|DefinitionNode[] */
/** @var NodeList<DefinitionNode&Node> */
public $definitions;
}
@@ -12,10 +12,10 @@ class EnumTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var EnumValueDefinitionNode[]|NodeList|null */
/** @var NodeList<EnumValueDefinitionNode> */
public $values;
/** @var StringValueNode|null */
@@ -12,9 +12,9 @@ class EnumTypeExtensionNode extends Node implements TypeExtensionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var EnumValueDefinitionNode[]|null */
/** @var NodeList<EnumValueDefinitionNode> */
public $values;
}
@@ -12,7 +12,7 @@ class EnumValueDefinitionNode extends Node
/** @var NameNode */
public $name;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var StringValueNode|null */
@@ -12,13 +12,13 @@ class FieldDefinitionNode extends Node
/** @var NameNode */
public $name;
/** @var InputValueDefinitionNode[]|NodeList */
/** @var NodeList<InputValueDefinitionNode> */
public $arguments;
/** @var TypeNode */
/** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */
public $type;
/** @var DirectiveNode[]|NodeList */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var StringValueNode|null */
+2 -2
View File
@@ -15,10 +15,10 @@ class FieldNode extends Node implements SelectionNode
/** @var NameNode|null */
public $alias;
/** @var ArgumentNode[]|null */
/** @var NodeList<ArgumentNode> */
public $arguments;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var SelectionSetNode|null */
@@ -16,14 +16,14 @@ class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, H
* Note: fragment variable definitions are experimental and may be changed
* or removed in the future.
*
* @var VariableDefinitionNode[]|NodeList
* @var NodeList<VariableDefinitionNode>
*/
public $variableDefinitions;
/** @var NamedTypeNode */
public $typeCondition;
/** @var DirectiveNode[]|NodeList */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var SelectionSetNode */
@@ -12,6 +12,6 @@ class FragmentSpreadNode extends Node implements SelectionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
}
@@ -4,10 +4,12 @@ declare(strict_types=1);
namespace GraphQL\Language\AST;
/**
* export type DefinitionNode = OperationDefinitionNode
* | FragmentDefinitionNode
*
* @property SelectionSetNode $selectionSet
*/
interface HasSelectionSet
{
/**
* export type DefinitionNode = OperationDefinitionNode
* | FragmentDefinitionNode
*/
}
@@ -12,7 +12,7 @@ class InlineFragmentNode extends Node implements SelectionNode
/** @var NamedTypeNode */
public $typeCondition;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var SelectionSetNode */
@@ -12,10 +12,10 @@ class InputObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var InputValueDefinitionNode[]|null */
/** @var NodeList<InputValueDefinitionNode> */
public $fields;
/** @var StringValueNode|null */
@@ -12,9 +12,9 @@ class InputObjectTypeExtensionNode extends Node implements TypeExtensionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var InputValueDefinitionNode[]|null */
/** @var NodeList<InputValueDefinitionNode> */
public $fields;
}
@@ -12,13 +12,13 @@ class InputValueDefinitionNode extends Node
/** @var NameNode */
public $name;
/** @var TypeNode */
/** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */
public $type;
/** @var ValueNode */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null */
public $defaultValue;
/** @var DirectiveNode[] */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var StringValueNode|null */
@@ -12,10 +12,13 @@ class InterfaceTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var FieldDefinitionNode[]|null */
/** @var NodeList<NamedTypeNode> */
public $interfaces;
/** @var NodeList<FieldDefinitionNode> */
public $fields;
/** @var StringValueNode|null */
@@ -12,9 +12,12 @@ class InterfaceTypeExtensionNode extends Node implements TypeExtensionNode
/** @var NameNode */
public $name;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var FieldDefinitionNode[]|null */
/** @var NodeList<InterfaceTypeDefinitionNode> */
public $interfaces;
/** @var NodeList<FieldDefinitionNode> */
public $fields;
}
@@ -9,6 +9,6 @@ class ListTypeNode extends Node implements TypeNode
/** @var string */
public $kind = NodeKind::LIST_TYPE;
/** @var Node */
/** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */
public $type;
}
@@ -9,6 +9,6 @@ class ListValueNode extends Node implements ValueNode
/** @var string */
public $kind = NodeKind::LST;
/** @var ValueNode[]|NodeList */
/** @var NodeList<ValueNode&Node> */
public $values;
}
+3 -3
View File
@@ -30,14 +30,14 @@ class Location
/**
* The Token at which this Node begins.
*
* @var Token
* @var Token|null
*/
public $startToken;
/**
* The Token at which this Node ends.
*
* @var Token
* @var Token|null
*/
public $endToken;
@@ -69,7 +69,7 @@ class Location
$this->endToken = $endToken;
$this->source = $source;
if (! $startToken || ! $endToken) {
if ($startToken === null || $endToken === null) {
return;
}
+10 -11
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace GraphQL\Language\AST;
use GraphQL\Utils\Utils;
use function count;
use function get_object_vars;
use function is_array;
use function is_scalar;
@@ -36,15 +37,18 @@ use function json_encode;
*/
abstract class Node
{
/** @var Location */
/** @var Location|null */
public $loc;
/** @var string */
public $kind;
/**
* @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars
*/
public function __construct(array $vars)
{
if (empty($vars)) {
if (count($vars) === 0) {
return;
}
@@ -83,10 +87,7 @@ abstract class Node
return $cloned;
}
/**
* @return string
*/
public function __toString()
public function __toString() : string
{
$tmp = $this->toArray(true);
@@ -94,11 +95,9 @@ abstract class Node
}
/**
* @param bool $recursive
*
* @return mixed[]
*/
public function toArray($recursive = false)
public function toArray(bool $recursive = false) : array
{
if ($recursive) {
return $this->recursiveToArray($this);
@@ -106,7 +105,7 @@ abstract class Node
$tmp = (array) $this;
if ($this->loc) {
if ($this->loc !== null) {
$tmp['loc'] = [
'start' => $this->loc->start,
'end' => $this->loc->end,
@@ -125,7 +124,7 @@ abstract class Node
'kind' => $node->kind,
];
if ($node->loc) {
if ($node->loc !== null) {
$result['loc'] = [
'start' => $node->loc->start,
'end' => $node->loc->end,
+58 -35
View File
@@ -6,31 +6,43 @@ namespace GraphQL\Language\AST;
use ArrayAccess;
use Countable;
use Generator;
use GraphQL\Utils\AST;
use InvalidArgumentException;
use IteratorAggregate;
use Traversable;
use function array_merge;
use function array_splice;
use function count;
use function is_array;
/**
* @template T of Node
* @phpstan-implements ArrayAccess<int|string, T>
* @phpstan-implements IteratorAggregate<T>
*/
class NodeList implements ArrayAccess, IteratorAggregate, Countable
{
/** @var Node[]|mixed[] */
/**
* @var Node[]
* @phpstan-var array<T>
*/
private $nodes;
/**
* @param Node[]|mixed[] $nodes
* @param Node[] $nodes
*
* @return static
* @phpstan-param array<T> $nodes
* @phpstan-return self<T>
*/
public static function create(array $nodes)
public static function create(array $nodes) : self
{
return new static($nodes);
}
/**
* @param Node[]|mixed[] $nodes
* @param Node[] $nodes
*
* @phpstan-param array<T> $nodes
*/
public function __construct(array $nodes)
{
@@ -38,59 +50,75 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
}
/**
* @param mixed $offset
*
* @return bool
* @param int|string $offset
*/
public function offsetExists($offset)
public function offsetExists($offset) : bool
{
return isset($this->nodes[$offset]);
}
/**
* @param mixed $offset
* TODO enable strict typing by changing how the Visitor deals with NodeList.
* Ideally, this function should always return a Node instance.
* However, the Visitor currently allows mutation of the NodeList
* and puts arbitrary values in the NodeList, such as strings.
* We will have to switch to using an array or a less strict
* type instead so we can enable strict typing in this class.
*
* @return mixed
* @param int|string $offset
*
* @phpstan-return T
*/
public function offsetGet($offset)
public function offsetGet($offset)// : Node
{
$item = $this->nodes[$offset];
if (is_array($item) && isset($item['kind'])) {
$this->nodes[$offset] = $item = AST::fromArray($item);
/** @phpstan-var T $node */
$node = AST::fromArray($item);
$this->nodes[$offset] = $node;
}
return $item;
return $this->nodes[$offset];
}
/**
* @param mixed $offset
* @param mixed $value
* @param int|string|null $offset
* @param Node|mixed[] $value
*
* @phpstan-param T|mixed[] $value
*/
public function offsetSet($offset, $value)
public function offsetSet($offset, $value) : void
{
if (is_array($value) && isset($value['kind'])) {
if (is_array($value)) {
/** @phpstan-var T $value */
$value = AST::fromArray($value);
}
// Happens when a Node is pushed via []=
if ($offset === null) {
$this->nodes[] = $value;
return;
}
$this->nodes[$offset] = $value;
}
/**
* @param mixed $offset
* @param int|string $offset
*/
public function offsetUnset($offset)
public function offsetUnset($offset) : void
{
unset($this->nodes[$offset]);
}
/**
* @param int $offset
* @param int $length
* @param mixed $replacement
*
* @return NodeList
* @phpstan-return NodeList<T>
*/
public function splice($offset, $length, $replacement = null)
public function splice(int $offset, int $length, $replacement = null) : NodeList
{
return new NodeList(array_splice($this->nodes, $offset, $length, $replacement));
}
@@ -98,9 +126,10 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
/**
* @param NodeList|Node[] $list
*
* @return NodeList
* @phpstan-param NodeList<T>|array<T> $list
* @phpstan-return NodeList<T>
*/
public function merge($list)
public function merge($list) : NodeList
{
if ($list instanceof self) {
$list = $list->nodes;
@@ -109,20 +138,14 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
return new NodeList(array_merge($this->nodes, $list));
}
/**
* @return Generator
*/
public function getIterator()
public function getIterator() : Traversable
{
foreach ($this->nodes as $key => $_) {
yield $this->offsetGet($key);
}
}
/**
* @return int
*/
public function count()
public function count() : int
{
return count($this->nodes);
}
@@ -9,6 +9,6 @@ class NonNullTypeNode extends Node implements TypeNode
/** @var string */
public $kind = NodeKind::NON_NULL_TYPE;
/** @var NameNode | ListTypeNode */
/** @var NamedTypeNode|ListTypeNode */
public $type;
}
@@ -12,6 +12,6 @@ class ObjectFieldNode extends Node
/** @var NameNode */
public $name;
/** @var ValueNode */
/** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode */
public $value;
}
@@ -12,13 +12,13 @@ class ObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
/** @var NameNode */
public $name;
/** @var NamedTypeNode[] */
public $interfaces = [];
/** @var NodeList<NamedTypeNode> */
public $interfaces;
/** @var DirectiveNode[]|null */
/** @var NodeList<DirectiveNode> */
public $directives;
/** @var FieldDefinitionNode[]|null */
/** @var NodeList<FieldDefinitionNode> */
public $fields;
/** @var StringValueNode|null */

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