new Deps
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
coverage_clover: /tmp/coverage/*.xml
|
||||
json_path: /tmp/coverage/coverage.json
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
Vendored
+6
-7
@@ -1,6 +1,6 @@
|
||||
# graphql-php
|
||||
[](https://travis-ci.org/webonyx/graphql-php)
|
||||
[](https://scrutinizer-ci.com/g/webonyx/graphql-php)
|
||||

|
||||
[](https://coveralls.io/github/webonyx/graphql-php?branch=master)
|
||||
[](https://packagist.org/packages/webonyx/graphql-php)
|
||||
[](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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
```
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
-19
@@ -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);
|
||||
}
|
||||
}
|
||||
-23
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+147
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-12
@@ -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);
|
||||
}
|
||||
|
||||
+289
-347
File diff suppressed because it is too large
Load Diff
+140
-84
@@ -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 */
|
||||
|
||||
+52
-30
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
+2
-2
@@ -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 */
|
||||
|
||||
+2
-2
@@ -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 */
|
||||
|
||||
+5
-2
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user