mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-04-03 12:43:31 +03:00
Compare commits
392 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b72ba3c93a | ||
|
1ac5af1d8b | ||
|
a01b089058 | ||
|
4401f4dd18 | ||
|
005b1a38c5 | ||
|
974258b352 | ||
|
a502c33254 | ||
|
22a0da9b98 | ||
|
8c4e7b178d | ||
|
3b33167c87 | ||
|
d037ab7ec3 | ||
|
54064b37b3 | ||
|
f7443b6f0c | ||
|
9704baf422 | ||
|
ed1d835bd5 | ||
|
3b27abafca | ||
|
c069d20ca7 | ||
|
19a37609f4 | ||
|
99453076b5 | ||
|
24f236403a | ||
|
8da3043702 | ||
|
a222cc9137 | ||
|
e704f8cc5c | ||
|
a34bb68d65 | ||
|
218e02a88c | ||
|
9ca7bb6ea1 | ||
|
65e4488ce8 | ||
|
bc66034f40 | ||
|
65a3a8d13e | ||
|
8381f67bd8 | ||
|
91b72f145d | ||
|
6e91e2181c | ||
|
03c33c9dc2 | ||
|
93ccd7351d | ||
|
ed1746e800 | ||
|
84a52c6c76 | ||
|
261f8f5ebd | ||
|
d1d4455eaa | ||
|
d5fbf1b29f | ||
|
c336d01bd2 | ||
|
752010b341 | ||
|
368a9ee2f7 | ||
|
6086792824 | ||
|
d259a303d3 | ||
|
0a71f9fba9 | ||
|
61453a4f0b | ||
|
6a5325a448 | ||
|
e87460880c | ||
|
173a4297d9 | ||
|
6f6a39468c | ||
|
a22a083220 | ||
|
acc0442152 | ||
|
21592f8f28 | ||
|
e17f578842 | ||
|
6d9275e6bc | ||
|
747cb49ae3 | ||
|
cdcf5b4473 | ||
|
692d10c127 | ||
|
6c82b85e79 | ||
|
e5528d14ab | ||
|
c9faa3489b | ||
|
bd02ccd47e | ||
|
08d9493b2c | ||
|
0b4b1485e0 | ||
|
387f416984 | ||
|
cf90a8d338 | ||
|
2fd21dd231 | ||
|
2173bb9696 | ||
|
e55c7d72cb | ||
|
ef9a24b01f | ||
|
40ac5ed269 | ||
|
27539d5af0 | ||
|
58d86abaf7 | ||
|
c803c455b4 | ||
|
9fc4d11425 | ||
|
d0ab4dc8d8 | ||
|
231919fbb2 | ||
|
22e3b0e981 | ||
|
019ed04a51 | ||
|
e5b955ecc8 | ||
|
e94db8a045 | ||
|
49b7aced87 | ||
|
0bd13d1828 | ||
|
179944495e | ||
|
0308cf0c0c | ||
|
11d32d4568 | ||
|
db915d8812 | ||
|
5821caa249 | ||
|
8435c3111e | ||
|
edec095055 | ||
|
06529e1924 | ||
|
ddebd9a414 | ||
|
1acddf4e22 | ||
|
569945cd37 | ||
|
b088720d40 | ||
|
b3bb316224 | ||
|
23ece09407 | ||
|
1864facda8 | ||
|
d15a9405cd | ||
|
d4742a76e5 | ||
|
f107cc2076 | ||
|
0226b08429 | ||
|
255ecbd709 | ||
|
8c66fa8d1e | ||
|
7405ddc852 | ||
|
0cbc1c9c07 | ||
|
c628fa39a1 | ||
|
a0f214a9f9 | ||
|
153f6f862e | ||
|
610979555d | ||
|
ababa18157 | ||
|
f52dfcfaef | ||
|
8b8ea0d4a3 | ||
|
42b20e7651 | ||
|
08992de960 | ||
|
1d8f526d91 | ||
|
fda73f3212 | ||
|
22cee49747 | ||
|
59c128c54a | ||
|
a116127436 | ||
|
9ada606919 | ||
|
1e778d259e | ||
|
b005803bf6 | ||
|
21dc3fe664 | ||
|
16d42dead3 | ||
|
bf471838ae | ||
|
29eba82093 | ||
|
e6e9d9ea22 | ||
|
f24e00f4b4 | ||
|
4a39dadd0d | ||
|
616fc10837 | ||
|
2f73bbe96c | ||
|
b667d8b3a3 | ||
|
441d70c7e5 | ||
|
972532cf6c | ||
|
e467f80149 | ||
|
d97fac6ab0 | ||
|
dfefdf24cb | ||
|
20e98aefa4 | ||
|
ff16350aa1 | ||
|
d20a6a9d56 | ||
|
77448ba623 | ||
|
bc637414e5 | ||
|
7d59811c4f | ||
|
31bbc416a5 | ||
|
b1ab1820b6 | ||
|
5b3f44c7a3 | ||
|
1dc291b073 | ||
|
edb5268583 | ||
|
6544197ef8 | ||
|
5ac3eeab18 | ||
|
a80d38747f | ||
|
9c1a89710e | ||
|
fd7465521a | ||
|
828a9fb002 | ||
|
00490d289c | ||
|
2295b96a49 | ||
|
31d89acfae | ||
|
244ec66ecc | ||
|
f96bd2740d | ||
|
ead704022c | ||
|
9609d2ac84 | ||
|
d5fddfd504 | ||
|
62b0036437 | ||
|
9a0dbff26b | ||
|
e22b400373 | ||
|
f644c1a837 | ||
|
c33e41f2bf | ||
|
63b4e3f0a4 | ||
|
039577d9eb | ||
|
6610f4e2da | ||
|
19d9c523c5 | ||
|
99f93939db | ||
|
8338db0480 | ||
|
f95d1e81ea | ||
|
33e3c9c338 | ||
|
376e927505 | ||
|
ead1b864bc | ||
|
b2cea8b538 | ||
|
012082d1d9 | ||
|
9bb8e73277 | ||
|
627cc786a5 | ||
|
c45fa1a4b1 | ||
|
54263d50c0 | ||
|
e1b4d438db | ||
|
e344c8a441 | ||
|
90b51e1a5d | ||
|
396d3370dc | ||
|
d591eccd9f | ||
|
bdbb30c604 | ||
|
89fa0c3e67 | ||
|
9cb6b4e6f3 | ||
|
d8d032f0f6 | ||
|
c0ae3ccdaf | ||
|
7c19777dff | ||
|
779774b162 | ||
|
b4be42acdf | ||
|
25cfebbd37 | ||
|
0f3ebaa20b | ||
|
21e0c830a6 | ||
|
4811cd198f | ||
|
7249e2611a | ||
|
b5d3341995 | ||
|
98807286f7 | ||
|
f39f0f5517 | ||
|
23169603fc | ||
|
6f8aed800e | ||
|
1a1bf17c6a | ||
|
cd38568aaa | ||
|
9ee62b014e | ||
|
4a096b14ec | ||
|
427ac1b5d5 | ||
|
2bddfe2225 | ||
|
0d954b6ecc | ||
|
644f97634b | ||
|
6cce6742eb | ||
|
e32bbb726d | ||
|
5fb970b3f1 | ||
|
0ea7ffa601 | ||
|
4bc9dfc6f8 | ||
|
c7fcd4eb48 | ||
|
90d0156291 | ||
|
0070cb4039 | ||
|
e4c743cf8c | ||
|
e7de069bd5 | ||
|
7ff3e9399f | ||
|
1417a43697 | ||
|
dd4a5076f6 | ||
|
e664c4455e | ||
|
18a5639cb7 | ||
|
7ba98ce773 | ||
|
a95d2ad140 | ||
|
bfebcd7bee | ||
|
0063bd6c15 | ||
|
73e75b6314 | ||
|
07c070d795 | ||
|
af31ca7ad8 | ||
|
849c15dbf8 | ||
|
84f13650a2 | ||
|
aed406eade | ||
|
5f5c7d89be | ||
|
5a1caf0549 | ||
|
a3d050ff6b | ||
|
0262f59a3f | ||
|
b65aa4e657 | ||
|
b1cd086177 | ||
|
0c33cfa88f | ||
|
1dc2b939cb | ||
|
b0ee36feb4 | ||
|
1d15ae7f3e | ||
|
68bfde953d | ||
|
c4e06ba528 | ||
|
4d4282b60f | ||
|
450a1932e5 | ||
|
6bdead3fe3 | ||
|
85f4c774a6 | ||
|
e2e6d70ea8 | ||
|
0ddb7519bb | ||
|
1ec4927f69 | ||
|
c4fe304efe | ||
|
e7513e356a | ||
|
326e0b4719 | ||
|
86503e2e35 | ||
|
a400f27dce | ||
|
48c6f56640 | ||
|
eed9cc7f1b | ||
|
2f63387864 | ||
|
be12d6f273 | ||
|
21a7611820 | ||
|
ff0733d013 | ||
|
7428cb8a31 | ||
|
62de403f27 | ||
|
eb7ff7048d | ||
|
b5b27c95b1 | ||
|
6a4c815b6d | ||
|
da70134c38 | ||
|
6866779d26 | ||
|
18954ea655 | ||
|
05dbc0fb96 | ||
|
31601d710b | ||
|
d8f41e854f | ||
|
6c40fec35a | ||
|
c7d8cf4ea2 | ||
|
7dbd72cebf | ||
|
a4edb34deb | ||
|
bfff27ef34 | ||
|
ec43a2e01a | ||
|
d70c8e87e5 | ||
|
4e43a2cbcd | ||
|
ce0272b447 | ||
|
a7af4663b8 | ||
|
7f99bf478f | ||
|
9327e75a16 | ||
|
d398e59ced | ||
|
737da333fb | ||
|
caa50d6db9 | ||
|
b886742968 | ||
|
046bd02d6c | ||
|
76e1c33b68 | ||
|
bd8722652a | ||
|
33cb80335e | ||
|
d1f49bedbd | ||
|
b02d25e62c | ||
|
503ac4619a | ||
|
ec54d6152b | ||
|
715146cdd1 | ||
|
573a77ce0c | ||
|
a30b3104a0 | ||
|
cf4cefc2bc | ||
|
ef93557a5d | ||
|
3170951620 | ||
|
00d547dc06 | ||
|
8817e8e010 | ||
|
ea6a21a13b | ||
|
672ff0b7d6 | ||
|
804daa188e | ||
|
f123e5c954 | ||
|
227f0b867d | ||
|
d87c1aba5c | ||
|
6e7cf27579 | ||
|
90623f68d7 | ||
|
00caed811b | ||
|
9ae8b9f26e | ||
|
7d326c44d8 | ||
|
49f34d3243 | ||
|
d44ec9e809 | ||
|
a3ef1be1ab | ||
|
ad8693cb8a | ||
|
dbeaf46631 | ||
|
0caaa1fa3b | ||
|
23fce6385f | ||
|
189d474633 | ||
|
86fa8b1301 | ||
|
cc39b3ecbf | ||
|
64c463e889 | ||
|
4c327a6c16 | ||
|
0d93d190f8 | ||
|
56e91d008e | ||
|
fcb9c24bb5 | ||
|
49ec89b28f | ||
|
03d7d1851c | ||
|
d9aee43129 | ||
|
ccb9486d21 | ||
|
a19fc3d208 | ||
|
8e02fdc537 | ||
|
ea13c9edbf | ||
|
c3d69c7c2b | ||
|
4227404aee | ||
|
f4008f0fb2 | ||
|
39df711eac | ||
|
942d4995c5 | ||
|
392b567f23 | ||
|
48b44fbdc5 | ||
|
c962afc566 | ||
|
2f2b54a3d6 | ||
|
24b6b736b2 | ||
|
bedbd32fb7 | ||
|
34500bd8b2 | ||
|
0a6eaa1173 | ||
|
c02c218c71 | ||
|
1b42de0658 | ||
|
ec2ff0d4bf | ||
|
54456b1160 | ||
|
c1a62fdb05 | ||
|
e515964a73 | ||
|
15672ab66c | ||
|
a59b2803be | ||
|
dbafdf849e | ||
|
750ce383ec | ||
|
ec77f439fb | ||
|
1b6fb4c29c | ||
|
cd1cc911e7 | ||
|
ddc3a01f09 | ||
|
6e64983f82 | ||
|
a032367e26 | ||
|
5a90e9bd64 | ||
|
89369fd8ec | ||
|
6195029215 | ||
|
8ba146071d | ||
|
3a4f520da7 | ||
|
300b58093b | ||
|
f44ff2cfe7 | ||
|
4bc3b7885c | ||
|
fe5c3bdee5 | ||
|
e31947a452 | ||
|
f0c2f12222 | ||
|
4e9ad1fd75 | ||
|
82b1dbd836 | ||
|
9452655fcd | ||
|
72e8607e47 | ||
|
06490cae8b | ||
|
c7f114d90b |
380 changed files with 42521 additions and 28359 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -2,6 +2,7 @@
|
|||
* text eol=lf
|
||||
/benchmarks export-ignore
|
||||
/tests export-ignore
|
||||
/examples export-ignore
|
||||
/tools export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,5 +1,7 @@
|
|||
.idea/
|
||||
composer.phar
|
||||
.phpcs-cache
|
||||
composer.lock
|
||||
composer.phar
|
||||
phpcs.xml
|
||||
phpstan.neon
|
||||
vendor/
|
||||
bin/
|
||||
/.idea
|
||||
|
|
29
.scrutinizer.yml
Normal file
29
.scrutinizer.yml
Normal file
|
@ -0,0 +1,29 @@
|
|||
build:
|
||||
nodes:
|
||||
analysis:
|
||||
environment:
|
||||
php:
|
||||
version: 7.1
|
||||
cache:
|
||||
disabled: false
|
||||
directories:
|
||||
- ~/.composer/cache
|
||||
project_setup:
|
||||
override: true
|
||||
tests:
|
||||
override:
|
||||
- php-scrutinizer-run
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
- composer install --ignore-platform-reqs --no-interaction
|
||||
|
||||
tools:
|
||||
external_code_coverage:
|
||||
timeout: 900
|
||||
|
||||
build_failure_conditions:
|
||||
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
|
||||
- 'issues.label("coding-style").new.exists' # No new coding style issues allowed
|
||||
- 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity
|
||||
- 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection
|
77
.travis.yml
77
.travis.yml
|
@ -1,33 +1,66 @@
|
|||
dist: trusty
|
||||
language: php
|
||||
|
||||
dist: trusty
|
||||
|
||||
php:
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- nightly
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 7.4snapshot
|
||||
- nightly
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- EXECUTOR= DEPENDENCIES=--prefer-lowest
|
||||
- EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest
|
||||
- EXECUTOR=
|
||||
- EXECUTOR=coroutine
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_PHP_VERSION" != "5.6" ]]; then phpenv config-rm xdebug.ini || true; fi
|
||||
- phpenv config-rm xdebug.ini || true
|
||||
- composer selfupdate
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
|
||||
- travis_retry composer self-update
|
||||
|
||||
install:
|
||||
- composer install --dev --prefer-dist
|
||||
- composer require react/promise:2.*
|
||||
- composer require psr/http-message:1.*
|
||||
install: travis_retry composer update --prefer-dist
|
||||
|
||||
script: if [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then bin/phpunit --coverage-clover build/logs/clover.xml --group default,ReactPromise; else bin/phpunit --group default,ReactPromise; fi
|
||||
script: ./vendor/bin/phpunit --group default,ReactPromise
|
||||
|
||||
jobs:
|
||||
allow_failures:
|
||||
- php: 7.4snapshot
|
||||
- php: nightly
|
||||
|
||||
include:
|
||||
- stage: Test
|
||||
install:
|
||||
- travis_retry composer update --prefer-dist {$DEPENDENCIES}
|
||||
|
||||
- stage: Test
|
||||
env: COVERAGE
|
||||
before_script:
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
|
||||
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
|
||||
script:
|
||||
- ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor.cov
|
||||
- EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor-coroutine.cov
|
||||
after_script:
|
||||
- ./vendor/bin/phpcov merge /tmp/coverage --clover /tmp/clover.xml
|
||||
- wget https://github.com/scrutinizer-ci/ocular/releases/download/1.5.2/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover /tmp/clover.xml
|
||||
|
||||
- stage: Code Quality
|
||||
php: 7.1
|
||||
env: CODING_STANDARD
|
||||
install: travis_retry composer install --prefer-dist
|
||||
script:
|
||||
- ./vendor/bin/phpcs
|
||||
|
||||
- stage: Code Quality
|
||||
php: 7.1
|
||||
env: STATIC_ANALYSIS
|
||||
install: travis_retry composer install --prefer-dist
|
||||
script: composer static-analysis
|
||||
|
||||
after_success:
|
||||
- if [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then composer require "satooshi/php-coveralls:^1.0" && travis_retry php bin/coveralls -v; fi
|
||||
|
|
62
CHANGELOG.md
62
CHANGELOG.md
|
@ -1,4 +1,66 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
- Add schema validation: Input Objects must not contain non-nullable circular references (#492)
|
||||
|
||||
#### v0.13.5
|
||||
- Fix coroutine executor when using with promise (#486)
|
||||
|
||||
#### v0.13.4
|
||||
- Force int when setting max query depth (#477)
|
||||
|
||||
#### v0.13.3
|
||||
- Reverted minor possible breaking change (#476)
|
||||
|
||||
#### v0.13.2
|
||||
- Added QueryPlan support (#436)
|
||||
- Fixed an issue with NodeList iteration over missing keys (#475)
|
||||
|
||||
#### v0.13.1
|
||||
- Better validation of field/directive arguments
|
||||
- Support for apollo client/server persisted queries
|
||||
- Minor tweaks and fixes
|
||||
|
||||
## v0.13.0
|
||||
This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details.
|
||||
|
||||
New features and notable changes:
|
||||
- PHP version required: 7.1+
|
||||
- Spec compliance: error `category` and extensions are displayed under `extensions` key when using default formatting (#389)
|
||||
- New experimental executor with improved performance (#314).<br>
|
||||
It is a one-line switch: `GraphQL::useExperimentalExecutor()`.<br>
|
||||
<br>
|
||||
**Please try it and post your feedback at https://github.com/webonyx/graphql-php/issues/397**
|
||||
(as it may become the default one in future)
|
||||
<br>
|
||||
<br>
|
||||
- Ported `extendSchema` from the reference implementation under `GraphQL\Utils\SchemaExtender` (#362)
|
||||
- Added ability to override standard types via `GraphQL::overrideStandardTypes(array $types)` (#401)
|
||||
- Added flag `Debug::RETHROW_UNSAFE_EXCEPTIONS` which would only rethrow app-specific exceptions (#337)
|
||||
- Several classes were renamed (see [UPGRADE.md](UPGRADE.md))
|
||||
- Schema Validation improvements
|
||||
|
||||
#### v0.12.6
|
||||
- Bugfix: Call to a member function getLocation() on null (#336)
|
||||
- Fixed several errors discovered by static analysis (#329)
|
||||
|
||||
#### v0.12.5
|
||||
- Execution performance optimization for lists
|
||||
|
||||
#### v0.12.4
|
||||
- Allow stringeable objects to be serialized by StringType (#303)
|
||||
|
||||
#### v0.12.3
|
||||
- StandardServer: add support for the multipart/form-data content type (#300)
|
||||
|
||||
#### v0.12.2
|
||||
- SchemaPrinter: Use multi-line block for trailing quote (#294)
|
||||
|
||||
#### v0.12.1
|
||||
- Fixed bug in validation rule OverlappingFieldsCanBeMerged (#292)
|
||||
- Added one more breaking change note in UPGRADE.md (#291)
|
||||
- Spec compliance: remove `data` entry from response on top-level error (#281)
|
||||
|
||||
## v0.12.0
|
||||
- RFC: Block String (multi-line strings via triple-quote """string""")
|
||||
- GraphQL Schema SDL: Descriptions as strings (including multi-line)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Contributing to GraphQL PHP
|
||||
|
||||
## Workflow
|
||||
|
||||
If your contribution requires significant or breaking changes, or if you plan to propose a major new feature,
|
||||
we recommend you to create an issue on the [GitHub](https://github.com/webonyx/graphql-php/issues) with
|
||||
a brief proposal and discuss it with us first.
|
||||
|
@ -11,17 +10,46 @@ For smaller contributions just use this workflow:
|
|||
* Fork the project.
|
||||
* Add your features and or bug fixes.
|
||||
* Add tests. Tests are important for us.
|
||||
* Send a pull request
|
||||
* Check your changes using `composer check-all`.
|
||||
* Add an entry to the [Changelog's Unreleases section](CHANGELOG.md#unreleased).
|
||||
* Send a pull request.
|
||||
|
||||
## Using GraphQL PHP from a Git checkout
|
||||
```
|
||||
$ git clone https://github.com/webonyx/graphql-php.git
|
||||
$ cd graphql-php
|
||||
$ composer install
|
||||
## Setup the Development Environment
|
||||
First, copy the URL of your fork and `git clone` it to your local machine.
|
||||
|
||||
```sh
|
||||
cd graphql-php
|
||||
composer install
|
||||
```
|
||||
|
||||
## Running tests
|
||||
From the project root:
|
||||
```sh
|
||||
./vendor/bin/phpunit
|
||||
```
|
||||
$ ./bin/phpunit
|
||||
|
||||
Some tests have annotation `@see it('<description>')`. It is used for reference to same tests in [graphql-js implementation](https://github.com/graphql/graphql-js) with the same description.
|
||||
|
||||
## Coding Standard
|
||||
The coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard).
|
||||
|
||||
Run the inspections:
|
||||
```sh
|
||||
./vendor/bin/phpcs
|
||||
```
|
||||
|
||||
Apply automatic code style fixes:
|
||||
```sh
|
||||
./vendor/bin/phpcbf
|
||||
```
|
||||
|
||||
## Static analysis
|
||||
Based on [PHPStan](https://github.com/phpstan/phpstan).
|
||||
```sh
|
||||
./vendor/bin/phpstan analyse --ansi --memory-limit 256M
|
||||
```
|
||||
|
||||
## Running benchmarks
|
||||
Benchmarks are run via [PHPBench](https://github.com/phpbench/phpbench).
|
||||
```sh
|
||||
./vendor/bin/phpbench run .
|
||||
```
|
||||
|
|
34
README.md
34
README.md
|
@ -1,6 +1,6 @@
|
|||
# graphql-php
|
||||
[](https://travis-ci.org/webonyx/graphql-php)
|
||||
[](https://coveralls.io/github/webonyx/graphql-php)
|
||||
[](https://scrutinizer-ci.com/g/webonyx/graphql-php)
|
||||
[](https://packagist.org/packages/webonyx/graphql-php)
|
||||
[](https://packagist.org/packages/webonyx/graphql-php)
|
||||
|
||||
|
@ -14,7 +14,7 @@ composer require webonyx/graphql-php
|
|||
```
|
||||
|
||||
## Documentation
|
||||
Full documentation is available on the [Documentation site](http://webonyx.github.io/graphql-php/) as well
|
||||
Full documentation is available on the [Documentation site](https://webonyx.github.io/graphql-php/) as well
|
||||
as in the [docs](docs/) folder of the distribution.
|
||||
|
||||
If you don't know what GraphQL is, visit this [official website](http://graphql.org)
|
||||
|
@ -24,11 +24,29 @@ by the Facebook engineering team.
|
|||
There are several ready examples in the [examples](examples/) folder of the distribution with specific
|
||||
README file per example.
|
||||
|
||||
## Contribute
|
||||
Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute.
|
||||
## Contributors
|
||||
|
||||
## Old README.md
|
||||
Here is a [link to the old README.md](https://github.com/webonyx/graphql-php/blob/v0.9.14/README.md).
|
||||
This project exists thanks to [all the people](https://github.com/webonyx/graphql-php/graphs/contributors) who contribute. [[Contribute](CONTRIBUTING.md)].
|
||||
|
||||
Keep in mind that it relates to the version 0.9.x. It may contain outdated information for
|
||||
newer versions (even though we try to preserve backwards compatibility).
|
||||
## Backers
|
||||
|
||||
<a href="https://opencollective.com/webonyx-graphql-php#backers" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/backers.svg?width=890"></a>
|
||||
|
||||
## Sponsors
|
||||
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/webonyx-graphql-php#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/0/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/1/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/2/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/3/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/4/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/5/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/6/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/7/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/8/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/9/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/9/avatar.svg"></a>
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](LICENSE).
|
||||
|
|
120
UPGRADE.md
120
UPGRADE.md
|
@ -1,3 +1,92 @@
|
|||
## Master
|
||||
|
||||
### Breaking (major): dropped deprecations
|
||||
- dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`.
|
||||
|
||||
## Upgrade v0.12.x > v0.13.x
|
||||
|
||||
### Breaking (major): minimum supported version of PHP
|
||||
New minimum required version of PHP is **7.1+**
|
||||
|
||||
### Breaking (major): default errors formatting changed according to spec
|
||||
**Category** and extensions assigned to errors are shown under `extensions` key
|
||||
```php
|
||||
$e = new Error(
|
||||
'msg',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
['foo' => 'bar']
|
||||
);
|
||||
```
|
||||
Formatting before the change:
|
||||
```
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'msg',
|
||||
'category' => 'graphql',
|
||||
'foo' => 'bar'
|
||||
]
|
||||
]
|
||||
```
|
||||
After the change:
|
||||
```
|
||||
'errors' => [
|
||||
[
|
||||
'message' => 'msg',
|
||||
'extensions' => [
|
||||
'category' => 'graphql',
|
||||
'foo' => 'bar',
|
||||
],
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
Note: if error extensions contain `category` key - it has a priority over default category.
|
||||
|
||||
You can always switch to [custom error formatting](https://webonyx.github.io/graphql-php/error-handling/#custom-error-handling-and-formatting) to revert to the old format.
|
||||
|
||||
### Try it: Experimental Executor with improved performance
|
||||
It is disabled by default. To enable it, do the following
|
||||
```php
|
||||
<?php
|
||||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Experimental\Executor\CoroutineExecutor;
|
||||
|
||||
Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
|
||||
```
|
||||
|
||||
**Please post your feedback about new executor at https://github.com/webonyx/graphql-php/issues/397
|
||||
Especially if you had issues (because it may become the default in one of the next releases)**
|
||||
|
||||
### Breaking: multiple interfaces separated with & in SDL
|
||||
Before the change:
|
||||
```graphql
|
||||
type Foo implements Bar, Baz { field: Type }
|
||||
```
|
||||
|
||||
After the change:
|
||||
```graphql
|
||||
type Foo implements Bar & Baz { field: Type }
|
||||
```
|
||||
|
||||
To allow for an adaptive migration, use `allowLegacySDLImplementsInterfaces` option of parser:
|
||||
```php
|
||||
Parser::parse($source, [ 'allowLegacySDLImplementsInterfaces' => true])
|
||||
```
|
||||
|
||||
### Breaking: several classes renamed
|
||||
|
||||
- `AbstractValidationRule` renamed to `ValidationRule` (NS `GraphQL\Validator\Rules`)
|
||||
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
|
||||
- `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`)
|
||||
|
||||
### Breaking: new constructors
|
||||
|
||||
`GraphQL\Type\Definition\ResolveInfo` now takes 10 arguments instead of one array.
|
||||
|
||||
## Upgrade v0.11.x > v0.12.x
|
||||
|
||||
### Breaking: Minimum supported version is PHP5.6
|
||||
|
@ -10,6 +99,33 @@ Exception inside `parseLiteral()`, `parseValue()` and `serialize()`.
|
|||
|
||||
Returning null from any of these methods will now be treated as valid result.
|
||||
|
||||
### Breaking: Custom scalar types parseLiteral() declaration changed
|
||||
A new parameter was added to `parseLiteral()`, which also needs to be added to any custom scalar type extending from `ScalarType`
|
||||
|
||||
Before:
|
||||
```php
|
||||
class MyType extends ScalarType {
|
||||
|
||||
...
|
||||
|
||||
public function parseLiteral($valueNode) {
|
||||
//custom implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
```php
|
||||
class MyType extends ScalarType {
|
||||
|
||||
...
|
||||
|
||||
public function parseLiteral($valueNode, array $variables = null) {
|
||||
//custom implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Breaking: Descriptions in comments are not used as descriptions by default anymore
|
||||
Descriptions now need to be inside Strings or BlockStrings in order to be picked up as
|
||||
description. If you want to keep the old behaviour you can supply the option `commentDescriptions`
|
||||
|
@ -42,6 +158,10 @@ type Dog {
|
|||
}
|
||||
```
|
||||
|
||||
### Breaking: Cached AST of version 0.11.x is not compatible with 0.12.x.
|
||||
That's because description in AST is now a separate node, not just a string.
|
||||
Make sure to renew caches.
|
||||
|
||||
### Breaking: Most of previously deprecated classes and methods were removed
|
||||
See deprecation notices for previous versions in details.
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?php
|
||||
namespace GraphQL\Benchmarks;
|
||||
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Benchmarks\Utils\QueryGenerator;
|
||||
use GraphQL\Benchmarks\Utils\SchemaGenerator;
|
||||
use GraphQL\Type\LazyResolution;
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\SchemaConfig;
|
||||
|
||||
/**
|
||||
* @BeforeMethods({"setUp"})
|
||||
|
@ -16,18 +16,12 @@ use GraphQL\Type\LazyResolution;
|
|||
*/
|
||||
class HugeSchemaBench
|
||||
{
|
||||
/**
|
||||
* @var SchemaGenerator
|
||||
*/
|
||||
/** @var SchemaGenerator */
|
||||
private $schemaBuilder;
|
||||
|
||||
private $schema;
|
||||
|
||||
private $lazySchema;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
private $smallQuery;
|
||||
|
||||
public function setUp()
|
||||
|
@ -36,12 +30,12 @@ class HugeSchemaBench
|
|||
'totalTypes' => 600,
|
||||
'fieldsPerType' => 8,
|
||||
'listFieldsPerType' => 2,
|
||||
'nestingLevel' => 10
|
||||
'nestingLevel' => 10,
|
||||
]);
|
||||
|
||||
$this->schema = $this->schemaBuilder->buildSchema();
|
||||
|
||||
$queryBuilder = new QueryGenerator($this->schema, 0.05);
|
||||
$queryBuilder = new QueryGenerator($this->schema, 0.05);
|
||||
$this->smallQuery = $queryBuilder->buildQuery();
|
||||
}
|
||||
|
||||
|
@ -49,8 +43,7 @@ class HugeSchemaBench
|
|||
{
|
||||
$this->schemaBuilder
|
||||
->buildSchema()
|
||||
->getTypeMap()
|
||||
;
|
||||
->getTypeMap();
|
||||
}
|
||||
|
||||
public function benchSchemaLazy()
|
||||
|
@ -60,21 +53,21 @@ class HugeSchemaBench
|
|||
|
||||
public function benchSmallQuery()
|
||||
{
|
||||
$result = GraphQL::execute($this->schema, $this->smallQuery);
|
||||
$result = GraphQL::executeQuery($this->schema, $this->smallQuery);
|
||||
}
|
||||
|
||||
public function benchSmallQueryLazy()
|
||||
{
|
||||
$schema = $this->createLazySchema();
|
||||
$result = GraphQL::execute($schema, $this->smallQuery);
|
||||
$result = GraphQL::executeQuery($schema, $this->smallQuery);
|
||||
}
|
||||
|
||||
private function createLazySchema()
|
||||
{
|
||||
return new Schema(
|
||||
\GraphQL\Type\SchemaConfig::create()
|
||||
SchemaConfig::create()
|
||||
->setQuery($this->schemaBuilder->buildQueryType())
|
||||
->setTypeLoader(function($name) {
|
||||
->setTypeLoader(function ($name) {
|
||||
return $this->schemaBuilder->loadType($name);
|
||||
})
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
namespace GraphQL\Benchmarks;
|
||||
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Tests\StarWarsSchema;
|
||||
use GraphQL\Type\Introspection;
|
||||
|
@ -35,7 +36,7 @@ class StarWarsBench
|
|||
}
|
||||
';
|
||||
|
||||
GraphQL::execute(
|
||||
GraphQL::executeQuery(
|
||||
StarWarsSchema::build(),
|
||||
$q
|
||||
);
|
||||
|
@ -57,7 +58,7 @@ class StarWarsBench
|
|||
}
|
||||
}
|
||||
';
|
||||
GraphQL::execute(
|
||||
GraphQL::executeQuery(
|
||||
StarWarsSchema::build(),
|
||||
$q
|
||||
);
|
||||
|
@ -81,7 +82,7 @@ class StarWarsBench
|
|||
}
|
||||
';
|
||||
|
||||
GraphQL::execute(
|
||||
GraphQL::executeQuery(
|
||||
StarWarsSchema::build(),
|
||||
$q
|
||||
);
|
||||
|
@ -89,7 +90,7 @@ class StarWarsBench
|
|||
|
||||
public function benchStarWarsIntrospectionQuery()
|
||||
{
|
||||
GraphQL::execute(
|
||||
GraphQL::executeQuery(
|
||||
StarWarsSchema::build(),
|
||||
$this->introQuery
|
||||
);
|
||||
|
|
|
@ -7,12 +7,15 @@ use GraphQL\Language\AST\NameNode;
|
|||
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||
use GraphQL\Language\AST\SelectionSetNode;
|
||||
use GraphQL\Language\Printer;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\FieldDefinition;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\WrappingType;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Utils\Utils;
|
||||
use function count;
|
||||
use function max;
|
||||
use function round;
|
||||
|
||||
class QueryGenerator
|
||||
{
|
||||
|
@ -30,12 +33,14 @@ class QueryGenerator
|
|||
|
||||
$totalFields = 0;
|
||||
foreach ($schema->getTypeMap() as $type) {
|
||||
if ($type instanceof ObjectType) {
|
||||
$totalFields += count($type->getFields());
|
||||
if (! ($type instanceof ObjectType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalFields += count($type->getFields());
|
||||
}
|
||||
|
||||
$this->maxLeafFields = max(1, round($totalFields * $percentOfLeafFields));
|
||||
$this->maxLeafFields = max(1, round($totalFields * $percentOfLeafFields));
|
||||
$this->currentLeafFields = 0;
|
||||
}
|
||||
|
||||
|
@ -44,13 +49,12 @@ class QueryGenerator
|
|||
$qtype = $this->schema->getQueryType();
|
||||
|
||||
$ast = new DocumentNode([
|
||||
'definitions' => [
|
||||
new OperationDefinitionNode([
|
||||
'name' => new NameNode(['value' => 'TestQuery']),
|
||||
'operation' => 'query',
|
||||
'selectionSet' => $this->buildSelectionSet($qtype->getFields())
|
||||
])
|
||||
]
|
||||
'definitions' => [new OperationDefinitionNode([
|
||||
'name' => new NameNode(['value' => 'TestQuery']),
|
||||
'operation' => 'query',
|
||||
'selectionSet' => $this->buildSelectionSet($qtype->getFields()),
|
||||
]),
|
||||
],
|
||||
]);
|
||||
|
||||
return Printer::doPrint($ast);
|
||||
|
@ -58,12 +62,13 @@ class QueryGenerator
|
|||
|
||||
/**
|
||||
* @param FieldDefinition[] $fields
|
||||
*
|
||||
* @return SelectionSetNode
|
||||
*/
|
||||
public function buildSelectionSet($fields)
|
||||
{
|
||||
$selections[] = new FieldNode([
|
||||
'name' => new NameNode(['value' => '__typename'])
|
||||
'name' => new NameNode(['value' => '__typename']),
|
||||
]);
|
||||
$this->currentLeafFields++;
|
||||
|
||||
|
@ -87,12 +92,12 @@ class QueryGenerator
|
|||
|
||||
$selections[] = new FieldNode([
|
||||
'name' => new NameNode(['value' => $field->name]),
|
||||
'selectionSet' => $selectionSet
|
||||
'selectionSet' => $selectionSet,
|
||||
]);
|
||||
}
|
||||
|
||||
$selectionSet = new SelectionSetNode([
|
||||
'selections' => $selections
|
||||
'selections' => $selections,
|
||||
]);
|
||||
|
||||
return $selectionSet;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?php
|
||||
namespace GraphQL\Benchmarks\Utils;
|
||||
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
|
||||
class SchemaGenerator
|
||||
{
|
||||
|
@ -152,7 +152,7 @@ class SchemaGenerator
|
|||
];
|
||||
}
|
||||
|
||||
public function resolveField($value, $args, $context, $resolveInfo)
|
||||
public function resolveField($objectValue, $args, $context, $resolveInfo)
|
||||
{
|
||||
return $resolveInfo->fieldName . '-value';
|
||||
}
|
||||
|
|
|
@ -9,21 +9,25 @@
|
|||
"API"
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6",
|
||||
"php": "^7.1||^8.0",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8",
|
||||
"psr/http-message": "^1.0"
|
||||
"doctrine/coding-standard": "^6.0",
|
||||
"phpbench/phpbench": "^0.14.0",
|
||||
"phpstan/phpstan": "^0.11.12",
|
||||
"phpstan/phpstan-phpunit": "^0.11.2",
|
||||
"phpstan/phpstan-strict-rules": "^0.11.1",
|
||||
"phpunit/phpcov": "^5.0",
|
||||
"phpunit/phpunit": "^7.2",
|
||||
"psr/http-message": "^1.0",
|
||||
"react/promise": "2.*"
|
||||
},
|
||||
"config": {
|
||||
"bin-dir": "bin",
|
||||
"platform": {
|
||||
"php": "5.6.0"
|
||||
},
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true
|
||||
},
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GraphQL\\": "src/"
|
||||
|
@ -39,5 +43,14 @@
|
|||
"suggest": {
|
||||
"react/promise": "To leverage async resolving on React PHP platform",
|
||||
"psr/http-message": "To use standard GraphQL server"
|
||||
},
|
||||
"scripts": {
|
||||
"api-docs": "php tools/gendocs.php",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
# Integrations
|
||||
|
||||
* [Integration with Relay](https://github.com/ivome/graphql-relay-php)
|
||||
* [Integration with Laravel 5](https://github.com/Folkloreatelier/laravel-graphql) + [Relay Helpers for Laravel](https://github.com/nuwave/laravel-graphql-relay) + [Nuwave Lighthouse](https://github.com/nuwave/lighthouse)
|
||||
* [Symfony Bundle](https://github.com/overblog/GraphQLBundle) by Overblog
|
||||
* Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)) via [Standard Server](executing-queries.md/#using-server)
|
||||
* [Standard Server](executing-queries.md/#using-server) – Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)).
|
||||
* [Relay Library for graphql-php](https://github.com/ivome/graphql-relay-php) – Helps construct Relay related schema definitions.
|
||||
* [Lighthouse](https://github.com/nuwave/lighthouse) – Laravel based, uses Schema Definition Language
|
||||
* [Laravel GraphQL](https://github.com/rebing/graphql-laravel) - Laravel wrapper for Facebook's GraphQL
|
||||
* [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) – Bundle for Symfony
|
||||
* [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress
|
||||
|
||||
# GraphQL PHP Tools
|
||||
|
||||
* Define types with Doctrine ORM annotations ([for PHP7.1](https://github.com/Ecodev/graphql-doctrine), for [earlier PHP versions](https://github.com/rahuljayaraman/doctrine-graphql))
|
||||
* [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
|
||||
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental)
|
||||
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server
|
||||
* [GraphQLite](https://graphqlite.thecodingmachine.io) – Define your complete schema with annotations
|
||||
* [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) – Define types with Doctrine ORM annotations
|
||||
* [DataLoaderPHP](https://github.com/overblog/dataloader-php) – as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
|
||||
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) – A PSR-15 middleware to support file uploads in GraphQL.
|
||||
* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) – Provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches.
|
||||
* [GraphQL Utils](https://github.com/simPod/GraphQL-Utils) – Objective schema definition builders (no need for arrays anymore) and `DateTime` scalar
|
||||
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server _(experimental)_
|
||||
|
||||
# General GraphQL Tools
|
||||
|
||||
* [GraphiQL](https://github.com/graphql/graphiql) - An in-browser IDE for exploring GraphQL
|
||||
* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) – GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).
|
||||
* [GraphiQL](https://github.com/graphql/graphiql) – An in-browser IDE for exploring GraphQL
|
||||
* [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
|
||||
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) -
|
||||
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) –
|
||||
GraphiQL as Google Chrome extension
|
||||
|
|
|
@ -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) : $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
|
||||
|
||||
|
@ -270,4 +271,4 @@ Where **$promiseAdapter** is an instance of:
|
|||
* Other platforms: write your own class implementing interface: <br>
|
||||
[`GraphQL\Executor\Promise\PromiseAdapter`](reference.md#graphqlexecutorpromisepromiseadapter).
|
||||
|
||||
Then your **resolve** functions should return promises of your platform instead of `GraphQL\Deferred`s.
|
||||
Then your **resolve** functions should return promises of your platform instead of `GraphQL\Deferred`s.
|
||||
|
|
|
@ -84,7 +84,7 @@ To change default **"Internal server error"** message to something else, use:
|
|||
GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error");
|
||||
```
|
||||
|
||||
#Debugging tools
|
||||
# Debugging tools
|
||||
|
||||
During development or debugging use `$result->toArray(true)` to add **debugMessage** key to
|
||||
each formatted error entry. If you also want to add exception trace - pass flags instead:
|
||||
|
@ -116,7 +116,7 @@ This will make each error entry to look like this:
|
|||
];
|
||||
```
|
||||
|
||||
If you prefer first resolver exception to be re-thrown, use following flags:
|
||||
If you prefer the first resolver exception to be re-thrown, use following flags:
|
||||
```php
|
||||
<?php
|
||||
use GraphQL\GraphQL;
|
||||
|
@ -127,6 +127,9 @@ $debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::RETHROW_INTERNAL_EXCEPTIONS;
|
|||
$result = GraphQL::executeQuery(/*args*/)->toArray($debug);
|
||||
```
|
||||
|
||||
If you only want to re-throw Exceptions that are not marked as safe through the `ClientAware` interface, use
|
||||
the flag `Debug::RETHROW_UNSAFE_EXCEPTIONS`.
|
||||
|
||||
# Custom Error Handling and Formatting
|
||||
It is possible to define custom **formatter** and **handler** for result errors.
|
||||
|
||||
|
|
|
@ -136,13 +136,13 @@ So for example following batch will require single DB request (if user field is
|
|||
```json
|
||||
[
|
||||
{
|
||||
"query": "{user(id: 1)} { id }"
|
||||
"query": "{user(id: 1) { id }}"
|
||||
},
|
||||
{
|
||||
"query": "{user(id: 2)} { id }"
|
||||
"query": "{user(id: 2) { id }}"
|
||||
},
|
||||
{
|
||||
"query": "{user(id: 3)} { id }"
|
||||
"query": "{user(id: 3) { id }}"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
|
@ -4,7 +4,7 @@ first learn about GraphQL on [the official website](http://graphql.org/learn/).
|
|||
|
||||
# Installation
|
||||
|
||||
Using [composer](https://getcomposer.org/doc/00-intro.md), simply run:
|
||||
Using [composer](https://getcomposer.org/doc/00-intro.md), run:
|
||||
|
||||
```sh
|
||||
composer require webonyx/graphql-php
|
||||
|
@ -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'];
|
||||
}
|
||||
],
|
||||
],
|
||||
|
@ -106,7 +106,7 @@ echo json_encode($output);
|
|||
|
||||
Our example is finished. Try it by running:
|
||||
```sh
|
||||
php -S localhost:8000 graphql.php
|
||||
php -S localhost:8080 graphql.php
|
||||
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
|
||||
```
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# About GraphQL
|
||||
|
||||
GraphQL is a modern way to build HTTP APIs consumed by the web and mobile clients.
|
||||
It is intended to be a replacement for REST and SOAP APIs (even for **existing applications**).
|
||||
It is intended to be an alternative to REST and SOAP APIs (even for **existing applications**).
|
||||
|
||||
GraphQL itself is a [specification](https://github.com/facebook/graphql) designed by Facebook
|
||||
engineers. Various implementations of this specification were written
|
||||
|
@ -44,8 +44,8 @@ existing PHP frameworks, add support for Relay, etc.
|
|||
## Current Status
|
||||
The first version of this library (v0.1) was released on August 10th 2015.
|
||||
|
||||
The current version (v0.10) supports all features described by GraphQL specification
|
||||
(including April 2016 add-ons) as well as some experimental features like
|
||||
The current version supports all features described by GraphQL specification
|
||||
as well as some experimental features like
|
||||
[Schema Language parser](type-system/type-language.md) and
|
||||
[Schema printer](reference.md#graphqlutilsschemaprinter).
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -58,4 +58,4 @@ $trackDirective = new Directive([
|
|||
```
|
||||
|
||||
See possible directive locations in
|
||||
[`GraphQL\Type\Definition\DirectiveLocation`](../reference.md#graphqltypedefinitiondirectivelocation).
|
||||
[`GraphQL\Language\DirectiveLocation`](../reference.md#graphqllanguagedirectivelocation).
|
||||
|
|
|
@ -158,7 +158,7 @@ $heroType = new ObjectType([
|
|||
'args' => [
|
||||
'episode' => Type::nonNull($enumType)
|
||||
],
|
||||
'resolve' => function($_value, $args) {
|
||||
'resolve' => function($hero, $args) {
|
||||
return $args['episode'] === 5 ? true : false;
|
||||
}
|
||||
]
|
||||
|
|
|
@ -114,7 +114,7 @@ Option | Type | Notes
|
|||
name | `string` | **Required.** Name of the input field. When not set - inferred from **fields** array key
|
||||
type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**Scalar**, **Enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers)
|
||||
description | `string` | Plain-text description of this input field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
|
||||
defaultValue | `scalar` | Default value of this input field
|
||||
defaultValue | `scalar` | Default value of this input field. Use the internal value if specifying a default for an **enum** type
|
||||
|
||||
# Using Input Object Type
|
||||
In the example above we defined our InputObjectType. Now let's use it in one of field arguments:
|
||||
|
@ -131,7 +131,7 @@ $queryType = new ObjectType([
|
|||
'type' => Type::listOf($storyType),
|
||||
'args' => [
|
||||
'filters' => [
|
||||
'type' => Type::nonNull($filters),
|
||||
'type' => $filters,
|
||||
'defaultValue' => [
|
||||
'popular' => true
|
||||
]
|
||||
|
|
|
@ -80,7 +80,7 @@ Option | Type | Notes
|
|||
name | `string` | **Required.** Name of the field. When not set - inferred from **fields** array key (read about [shorthand field definition](#shorthand-field-definitions) below)
|
||||
type | `Type` | **Required.** An instance of internal or custom type. Note: type must be represented by a single instance within one schema (see also [Type Registry](index.md#type-registry))
|
||||
args | `array` | An array of possible type arguments. Each entry is expected to be an array with keys: **name**, **type**, **description**, **defaultValue**. See [Field Arguments](#field-arguments) section below.
|
||||
resolve | `callable` | **function($value, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$value** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
|
||||
resolve | `callable` | **function($objectValue, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$objectValue** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
|
||||
complexity | `callable` | **function($childrenComplexity, $args)**<br> Used to restrict query complexity. The feature is disabled by default, read about [Security](../security.md#query-complexity-analysis) to use it.
|
||||
description | `string` | Plain-text description of this field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
|
||||
deprecationReason | `string` | Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced)
|
||||
|
@ -94,7 +94,7 @@ Option | Type | Notes
|
|||
name | `string` | **Required.** Name of the argument. When not set - inferred from **args** array key
|
||||
type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**scalar**, **enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers)
|
||||
description | `string` | Plain-text description of this argument for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
|
||||
defaultValue | `scalar` | Default value for this argument
|
||||
defaultValue | `scalar` | Default value for this argument. Use the internal value if specifying a default for an **enum** type
|
||||
|
||||
# Shorthand field definitions
|
||||
Fields can be also defined in **shorthand** notation (with only **name** and **type** options):
|
||||
|
|
|
@ -96,10 +96,11 @@ class EmailType extends ScalarType
|
|||
* }
|
||||
*
|
||||
* @param \GraphQL\Language\AST\Node $valueNode
|
||||
* @param array|null $variables
|
||||
* @return string
|
||||
* @throws Error
|
||||
*/
|
||||
public function parseLiteral($valueNode)
|
||||
public function parseLiteral($valueNode, array $variables = null)
|
||||
{
|
||||
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
|
||||
// error location in query:
|
||||
|
@ -124,6 +125,6 @@ $emailType = new CustomScalarType([
|
|||
'name' => 'Email',
|
||||
'serialize' => function($value) {/* See function body above */},
|
||||
'parseValue' => function($value) {/* See function body above */},
|
||||
'parseLiteral' => function($valueNode) {/* See function body above */},
|
||||
'parseLiteral' => function($valueNode, array $variables = null) {/* See function body above */},
|
||||
]);
|
||||
```
|
||||
|
|
|
@ -62,7 +62,7 @@ $mutationType = new ObjectType([
|
|||
'episode' => $episodeEnum,
|
||||
'review' => $reviewInputObject
|
||||
],
|
||||
'resolve' => function($val, $args) {
|
||||
'resolve' => function($rootValue, $args) {
|
||||
// TODOC
|
||||
}
|
||||
]
|
||||
|
|
|
@ -38,6 +38,30 @@ By default, such schema is created without any resolvers.
|
|||
We have to rely on [default field resolver](../data-fetching.md#default-field-resolver) and **root value** in
|
||||
order to execute a query against this schema.
|
||||
|
||||
# Defining resolvers
|
||||
Since 0.10.0
|
||||
|
||||
In order to enable **Interfaces**, **Unions** and custom field resolvers you can pass the second argument:
|
||||
**type config decorator** to schema builder.
|
||||
|
||||
It accepts default type config produced by the builder and is expected to add missing options like
|
||||
[**resolveType**](interfaces.md#configuration-options) for interface types or
|
||||
[**resolveField**](object-types.md#configuration-options) for object types.
|
||||
|
||||
```php
|
||||
<?php
|
||||
use GraphQL\Utils\BuildSchema;
|
||||
|
||||
$typeConfigDecorator = function($typeConfig, $typeDefinitionNode) {
|
||||
$name = $typeConfig['name'];
|
||||
// ... add missing options to $typeConfig based on type $name
|
||||
return $typeConfig;
|
||||
};
|
||||
|
||||
$contents = file_get_contents('schema.graphql');
|
||||
$schema = BuildSchema::build($contents, $typeConfigDecorator);
|
||||
```
|
||||
|
||||
# Performance considerations
|
||||
Since 0.10.0
|
||||
|
||||
|
@ -57,7 +81,7 @@ $cacheFilename = 'cached_schema.php';
|
|||
|
||||
if (!file_exists($cacheFilename)) {
|
||||
$document = Parser::parse(file_get_contents('./schema.graphql'));
|
||||
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true));
|
||||
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true) . ";\n");
|
||||
} else {
|
||||
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ try {
|
|||
'args' => [
|
||||
'message' => ['type' => Type::string()],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
return $root['prefix'] . $args['message'];
|
||||
'resolve' => function ($rootValue, $args) {
|
||||
return $rootValue['prefix'] . $args['message'];
|
||||
}
|
||||
],
|
||||
],
|
||||
|
@ -35,7 +35,7 @@ try {
|
|||
'x' => ['type' => Type::int()],
|
||||
'y' => ['type' => Type::int()],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
'resolve' => function ($calc, $args) {
|
||||
return $args['x'] + $args['y'];
|
||||
},
|
||||
],
|
||||
|
|
|
@ -35,12 +35,12 @@ class CommentType extends ObjectType
|
|||
Types::htmlField('body')
|
||||
];
|
||||
},
|
||||
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||
'resolveField' => function($comment, $args, $context, ResolveInfo $info) {
|
||||
$method = 'resolve' . ucfirst($info->fieldName);
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}($value, $args, $context, $info);
|
||||
return $this->{$method}($comment, $args, $context, $info);
|
||||
} else {
|
||||
return $value->{$info->fieldName};
|
||||
return $comment->{$info->fieldName};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -57,8 +57,8 @@ class QueryType extends ObjectType
|
|||
],
|
||||
'hello' => Type::string()
|
||||
],
|
||||
'resolveField' => function($val, $args, $context, ResolveInfo $info) {
|
||||
return $this->{$info->fieldName}($val, $args, $context, $info);
|
||||
'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) {
|
||||
return $this->{$info->fieldName}($rootValue, $args, $context, $info);
|
||||
}
|
||||
];
|
||||
parent::__construct($config);
|
||||
|
|
|
@ -75,12 +75,12 @@ class StoryType extends ObjectType
|
|||
'interfaces' => [
|
||||
Types::node()
|
||||
],
|
||||
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||
'resolveField' => function($story, $args, $context, ResolveInfo $info) {
|
||||
$method = 'resolve' . ucfirst($info->fieldName);
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}($value, $args, $context, $info);
|
||||
return $this->{$method}($story, $args, $context, $info);
|
||||
} else {
|
||||
return $value->{$info->fieldName};
|
||||
return $story->{$info->fieldName};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -44,12 +44,12 @@ class UserType extends ObjectType
|
|||
'interfaces' => [
|
||||
Types::node()
|
||||
],
|
||||
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||
'resolveField' => function($user, $args, $context, ResolveInfo $info) {
|
||||
$method = 'resolve' . ucfirst($info->fieldName);
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}($value, $args, $context, $info);
|
||||
return $this->{$method}($user, $args, $context, $info);
|
||||
} else {
|
||||
return $value->{$info->fieldName};
|
||||
return $user->{$info->fieldName};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -18,7 +18,7 @@ try {
|
|||
$query = $input['query'];
|
||||
$variableValues = isset($input['variables']) ? $input['variables'] : null;
|
||||
|
||||
$result = GraphQL::execute($schema, $query, $rootValue, null, $variableValues);
|
||||
$result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variableValues);
|
||||
} catch (\Exception $e) {
|
||||
$result = [
|
||||
'error' => [
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
|
||||
interface Resolver {
|
||||
public function resolve($root, $args, $context);
|
||||
public function resolve($rootValue, $args, $context);
|
||||
}
|
||||
|
||||
class Addition implements Resolver
|
||||
{
|
||||
public function resolve($root, $args, $context)
|
||||
public function resolve($rootValue, $args, $context)
|
||||
{
|
||||
return $args['x'] + $args['y'];
|
||||
}
|
||||
|
@ -14,22 +14,22 @@ class Addition implements Resolver
|
|||
|
||||
class Echoer implements Resolver
|
||||
{
|
||||
public function resolve($root, $args, $context)
|
||||
public function resolve($rootValue, $args, $context)
|
||||
{
|
||||
return $root['prefix'].$args['message'];
|
||||
return $rootValue['prefix'].$args['message'];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'sum' => function($root, $args, $context) {
|
||||
'sum' => function($rootValue, $args, $context) {
|
||||
$sum = new Addition();
|
||||
|
||||
return $sum->resolve($root, $args, $context);
|
||||
return $sum->resolve($rootValue, $args, $context);
|
||||
},
|
||||
'echo' => function($root, $args, $context) {
|
||||
'echo' => function($rootValue, $args, $context) {
|
||||
$echo = new Echoer();
|
||||
|
||||
return $echo->resolve($root, $args, $context);
|
||||
return $echo->resolve($rootValue, $args, $context);
|
||||
},
|
||||
'prefix' => 'You said: ',
|
||||
];
|
||||
|
|
|
@ -19,8 +19,8 @@ try {
|
|||
'args' => [
|
||||
'message' => ['type' => Type::string()],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
return $root['prefix'] . $args['message'];
|
||||
'resolve' => function ($rootValue, $args) {
|
||||
return $rootValue['prefix'] . $args['message'];
|
||||
}
|
||||
],
|
||||
],
|
||||
|
@ -35,7 +35,7 @@ try {
|
|||
'x' => ['type' => Type::int()],
|
||||
'y' => ['type' => Type::int()],
|
||||
],
|
||||
'resolve' => function ($root, $args) {
|
||||
'resolve' => function ($calc, $args) {
|
||||
return $args['x'] + $args['y'];
|
||||
},
|
||||
],
|
||||
|
|
102
phpcs.xml.dist
Normal file
102
phpcs.xml.dist
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0"?>
|
||||
<ruleset>
|
||||
<arg name="basepath" value="." />
|
||||
<arg name="extensions" value="php" />
|
||||
<arg name="parallel" value="80" />
|
||||
<arg name="cache" value=".phpcs-cache" />
|
||||
<arg name="colors" />
|
||||
|
||||
<!-- Ignore warnings, show progress of the run and show sniff names -->
|
||||
<arg value="nps" />
|
||||
|
||||
<file>src</file>
|
||||
<file>tests</file>
|
||||
|
||||
<rule ref="Doctrine">
|
||||
<!-- Disable PHP7+ features that might cause BC breaks for now -->
|
||||
<exclude name="SlevomatCodingStandard.Classes.ClassConstantVisibility.MissingConstantVisibility" />
|
||||
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint" />
|
||||
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint" />
|
||||
<!-- Enable when Slevomat starts supporting variadics, see https://github.com/slevomat/coding-standard/issues/251 -->
|
||||
<exclude name="SlevomatCodingStandard.Namespaces.UnusedUses.UnusedUse" />
|
||||
</rule>
|
||||
|
||||
<!--@api annotation is required for now -->
|
||||
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration">
|
||||
<properties>
|
||||
<property
|
||||
name="usefulAnnotations"
|
||||
type="array"
|
||||
value="
|
||||
@after,
|
||||
@afterClass,
|
||||
@AfterMethods,
|
||||
@api,
|
||||
@Attribute,
|
||||
@Attributes,
|
||||
@before,
|
||||
@beforeClass,
|
||||
@BeforeMethods,
|
||||
@covers,
|
||||
@coversDefaultClass,
|
||||
@coversNothing,
|
||||
@dataProvider,
|
||||
@depends,
|
||||
@deprecated,
|
||||
@doesNotPerformAssertions,
|
||||
@Enum,
|
||||
@expectedDeprecation,
|
||||
@expectedException,
|
||||
@expectedExceptionCode,
|
||||
@expectedExceptionMessage,
|
||||
@expectedExceptionMessageRegExp,
|
||||
@group,
|
||||
@Groups,
|
||||
@IgnoreAnnotation,
|
||||
@internal,
|
||||
@Iterations,
|
||||
@link,
|
||||
@ODM\,
|
||||
@ORM\,
|
||||
@requires,
|
||||
@Required,
|
||||
@Revs,
|
||||
@runInSeparateProcess,
|
||||
@runTestsInSeparateProcesses,
|
||||
@see,
|
||||
@Target,
|
||||
@test,
|
||||
@throws,
|
||||
@uses
|
||||
"
|
||||
/>
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<rule ref="SlevomatCodingStandard.Commenting.ForbiddenAnnotations">
|
||||
<properties>
|
||||
<property
|
||||
name="forbiddenAnnotations"
|
||||
type="array"
|
||||
value="
|
||||
@author,
|
||||
@category,
|
||||
@copyright,
|
||||
@created,
|
||||
@license,
|
||||
@package,
|
||||
@since,
|
||||
@subpackage,
|
||||
@version
|
||||
"
|
||||
/>
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<!-- IDEs sort by PSR12, Slevomat coding standard uses old sorting for BC -->
|
||||
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
|
||||
<properties>
|
||||
<property name="psr12Compatible" type="bool" value="true" />
|
||||
</properties>
|
||||
</rule>
|
||||
</ruleset>
|
17
phpstan.neon.dist
Normal file
17
phpstan.neon.dist
Normal file
|
@ -0,0 +1,17 @@
|
|||
parameters:
|
||||
level: 1
|
||||
|
||||
paths:
|
||||
- %currentWorkingDirectory%/src
|
||||
- %currentWorkingDirectory%/tests
|
||||
|
||||
ignoreErrors:
|
||||
- "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~"
|
||||
- "~(Method|Property) .+::.+(\\(\\))? (has parameter \\$\\w+ with no|has no return|has no) typehint specified~"
|
||||
- "~Variable property access on .+~"
|
||||
- "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" # TODO get rid of
|
||||
|
||||
includes:
|
||||
- vendor/phpstan/phpstan-phpunit/extension.neon
|
||||
- vendor/phpstan/phpstan-phpunit/rules.neon
|
||||
- vendor/phpstan/phpstan-strict-rules/rules.neon
|
|
@ -1,16 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="E_ALL"/>
|
||||
</php>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="webonyx/graphql-php Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
|
@ -26,21 +23,6 @@
|
|||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">./src</directory>
|
||||
<exclude>
|
||||
<directory>./bin</directory>
|
||||
<directory>./docs</directory>
|
||||
<directory>./build</directory>
|
||||
<directory>./tests</directory>
|
||||
<directory>./vendor</directory>
|
||||
<directory>./examples</directory>
|
||||
<directory>./benchmarks</directory>
|
||||
<file>./src/deprecated.php</file>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<php>
|
||||
<ini name="error_reporting" value="E_ALL"/>
|
||||
</php>
|
||||
|
||||
</phpunit>
|
||||
|
|
|
@ -1,60 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL;
|
||||
|
||||
use Exception;
|
||||
use GraphQL\Executor\Promise\Adapter\SyncPromise;
|
||||
use SplQueue;
|
||||
use Throwable;
|
||||
|
||||
class Deferred
|
||||
{
|
||||
/**
|
||||
* @var \SplQueue
|
||||
*/
|
||||
/** @var SplQueue|null */
|
||||
private static $queue;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
/** @var callable */
|
||||
private $callback;
|
||||
|
||||
/**
|
||||
* @var SyncPromise
|
||||
*/
|
||||
/** @var SyncPromise */
|
||||
public $promise;
|
||||
|
||||
public static function getQueue()
|
||||
{
|
||||
return self::$queue ?: self::$queue = new \SplQueue();
|
||||
}
|
||||
|
||||
public static function runQueue()
|
||||
{
|
||||
$q = self::$queue;
|
||||
while ($q && !$q->isEmpty()) {
|
||||
/** @var self $dfd */
|
||||
$dfd = $q->dequeue();
|
||||
$dfd->run();
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct(callable $callback)
|
||||
{
|
||||
$this->callback = $callback;
|
||||
$this->promise = new SyncPromise();
|
||||
$this->promise = new SyncPromise();
|
||||
self::getQueue()->enqueue($this);
|
||||
}
|
||||
|
||||
public static function getQueue() : SplQueue
|
||||
{
|
||||
if (self::$queue === null) {
|
||||
self::$queue = new SplQueue();
|
||||
}
|
||||
|
||||
return self::$queue;
|
||||
}
|
||||
|
||||
public static function runQueue() : void
|
||||
{
|
||||
$queue = self::getQueue();
|
||||
while (! $queue->isEmpty()) {
|
||||
/** @var self $dequeuedNodeValue */
|
||||
$dequeuedNodeValue = $queue->dequeue();
|
||||
$dequeuedNodeValue->run();
|
||||
}
|
||||
}
|
||||
|
||||
public function then($onFulfilled = null, $onRejected = null)
|
||||
{
|
||||
return $this->promise->then($onFulfilled, $onRejected);
|
||||
}
|
||||
|
||||
private function run()
|
||||
public function run() : void
|
||||
{
|
||||
try {
|
||||
$cb = $this->callback;
|
||||
$this->promise->resolve($cb());
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->promise->reject($e);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->promise->reject($e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
/**
|
||||
|
@ -14,8 +17,9 @@ interface ClientAware
|
|||
/**
|
||||
* Returns true when exception message is safe to be displayed to a client.
|
||||
*
|
||||
* @api
|
||||
* @return bool
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function isClientSafe();
|
||||
|
||||
|
@ -24,8 +28,9 @@ interface ClientAware
|
|||
*
|
||||
* Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.
|
||||
*
|
||||
* @api
|
||||
* @return string
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getCategory();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
/**
|
||||
|
@ -6,7 +9,8 @@ namespace GraphQL\Error;
|
|||
*/
|
||||
class Debug
|
||||
{
|
||||
const INCLUDE_DEBUG_MESSAGE = 1;
|
||||
const INCLUDE_TRACE = 2;
|
||||
const INCLUDE_DEBUG_MESSAGE = 1;
|
||||
const INCLUDE_TRACE = 2;
|
||||
const RETHROW_INTERNAL_EXCEPTIONS = 4;
|
||||
const RETHROW_UNSAFE_EXCEPTIONS = 8;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use Exception;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Utils\Utils;
|
||||
use JsonSerializable;
|
||||
use Throwable;
|
||||
use Traversable;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function is_array;
|
||||
use function iterator_to_array;
|
||||
|
||||
/**
|
||||
* Describes an Error found during the parse, validate, or
|
||||
|
@ -20,9 +32,9 @@ use GraphQL\Utils\Utils;
|
|||
* Class extends standard PHP `\Exception`, so all standard methods of base `\Exception` class
|
||||
* are available in addition to those listed below.
|
||||
*/
|
||||
class Error extends \Exception implements \JsonSerializable, ClientAware
|
||||
class Error extends Exception implements JsonSerializable, ClientAware
|
||||
{
|
||||
const CATEGORY_GRAPHQL = 'graphql';
|
||||
const CATEGORY_GRAPHQL = 'graphql';
|
||||
const CATEGORY_INTERNAL = 'internal';
|
||||
|
||||
/**
|
||||
|
@ -32,23 +44,21 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||
*/
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* @var SourceLocation[]
|
||||
*/
|
||||
/** @var SourceLocation[] */
|
||||
private $locations;
|
||||
|
||||
/**
|
||||
* An array describing the JSON-path into the execution response which
|
||||
* corresponds to this error. Only included for errors during execution.
|
||||
*
|
||||
* @var array
|
||||
* @var mixed[]|null
|
||||
*/
|
||||
public $path;
|
||||
|
||||
/**
|
||||
* An array of GraphQL AST Nodes corresponding to this error.
|
||||
*
|
||||
* @var array
|
||||
* @var Node[]|null
|
||||
*/
|
||||
public $nodes;
|
||||
|
||||
|
@ -62,34 +72,75 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||
*/
|
||||
private $source;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var int[]|null */
|
||||
private $positions;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
/** @var bool */
|
||||
private $isClientSafe;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $category;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var mixed[]|null */
|
||||
protected $extensions;
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param Node|Node[]|Traversable|null $nodes
|
||||
* @param mixed[]|null $positions
|
||||
* @param mixed[]|null $path
|
||||
* @param Throwable $previous
|
||||
* @param mixed[] $extensions
|
||||
*/
|
||||
public function __construct(
|
||||
$message,
|
||||
$nodes = null,
|
||||
?Source $source = null,
|
||||
$positions = null,
|
||||
$path = null,
|
||||
$previous = null,
|
||||
array $extensions = []
|
||||
) {
|
||||
parent::__construct($message, 0, $previous);
|
||||
|
||||
// Compute list of blame nodes.
|
||||
if ($nodes instanceof Traversable) {
|
||||
$nodes = iterator_to_array($nodes);
|
||||
} elseif ($nodes && ! is_array($nodes)) {
|
||||
$nodes = [$nodes];
|
||||
}
|
||||
|
||||
$this->nodes = $nodes;
|
||||
$this->source = $source;
|
||||
$this->positions = $positions;
|
||||
$this->path = $path;
|
||||
$this->extensions = $extensions ?: (
|
||||
$previous && $previous instanceof self
|
||||
? $previous->extensions
|
||||
: []
|
||||
);
|
||||
|
||||
if ($previous instanceof ClientAware) {
|
||||
$this->isClientSafe = $previous->isClientSafe();
|
||||
$this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL;
|
||||
} elseif ($previous) {
|
||||
$this->isClientSafe = false;
|
||||
$this->category = self::CATEGORY_INTERNAL;
|
||||
} else {
|
||||
$this->isClientSafe = true;
|
||||
$this->category = self::CATEGORY_GRAPHQL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an arbitrary Error, presumably thrown while attempting to execute a
|
||||
* GraphQL operation, produce a new GraphQLError aware of the location in the
|
||||
* document responsible for the original Error.
|
||||
*
|
||||
* @param $error
|
||||
* @param array|null $nodes
|
||||
* @param array|null $path
|
||||
* @param mixed $error
|
||||
* @param Node[]|null $nodes
|
||||
* @param mixed[]|null $path
|
||||
*
|
||||
* @return Error
|
||||
*/
|
||||
public static function createLocatedError($error, $nodes = null, $path = null)
|
||||
|
@ -97,24 +148,24 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||
if ($error instanceof self) {
|
||||
if ($error->path && $error->nodes) {
|
||||
return $error;
|
||||
} else {
|
||||
$nodes = $nodes ?: $error->nodes;
|
||||
$path = $path ?: $error->path;
|
||||
}
|
||||
|
||||
$nodes = $nodes ?: $error->nodes;
|
||||
$path = $path ?: $error->path;
|
||||
}
|
||||
|
||||
$source = $positions = $originalError = null;
|
||||
$source = $positions = $originalError = null;
|
||||
$extensions = [];
|
||||
|
||||
if ($error instanceof self) {
|
||||
$message = $error->getMessage();
|
||||
$message = $error->getMessage();
|
||||
$originalError = $error;
|
||||
$nodes = $error->nodes ?: $nodes;
|
||||
$source = $error->source;
|
||||
$positions = $error->positions;
|
||||
$extensions = $error->extensions;
|
||||
} else if ($error instanceof \Exception || $error instanceof \Throwable) {
|
||||
$message = $error->getMessage();
|
||||
$nodes = $error->nodes ?: $nodes;
|
||||
$source = $error->source;
|
||||
$positions = $error->positions;
|
||||
$extensions = $error->extensions;
|
||||
} elseif ($error instanceof Exception || $error instanceof Throwable) {
|
||||
$message = $error->getMessage();
|
||||
$originalError = $error;
|
||||
} else {
|
||||
$message = (string) $error;
|
||||
|
@ -131,66 +182,14 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Error $error
|
||||
* @return array
|
||||
* @return mixed[]
|
||||
*/
|
||||
public static function formatError(Error $error)
|
||||
{
|
||||
return $error->toSerializableArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array|Node|null $nodes
|
||||
* @param Source $source
|
||||
* @param array|null $positions
|
||||
* @param array|null $path
|
||||
* @param \Throwable $previous
|
||||
* @param array $extensions
|
||||
*/
|
||||
public function __construct(
|
||||
$message,
|
||||
$nodes = null,
|
||||
Source $source = null,
|
||||
$positions = null,
|
||||
$path = null,
|
||||
$previous = null,
|
||||
array $extensions = []
|
||||
)
|
||||
{
|
||||
parent::__construct($message, 0, $previous);
|
||||
|
||||
// Compute list of blame nodes.
|
||||
if ($nodes instanceof \Traversable) {
|
||||
$nodes = iterator_to_array($nodes);
|
||||
} else if ($nodes && !is_array($nodes)) {
|
||||
$nodes = [$nodes];
|
||||
}
|
||||
|
||||
$this->nodes = $nodes;
|
||||
$this->source = $source;
|
||||
$this->positions = $positions;
|
||||
$this->path = $path;
|
||||
$this->extensions = $extensions ?: (
|
||||
$previous && $previous instanceof self
|
||||
? $previous->extensions
|
||||
: []
|
||||
);
|
||||
|
||||
if ($previous instanceof ClientAware) {
|
||||
$this->isClientSafe = $previous->isClientSafe();
|
||||
$this->category = $previous->getCategory() ?: static::CATEGORY_INTERNAL;
|
||||
} else if ($previous) {
|
||||
$this->isClientSafe = false;
|
||||
$this->category = static::CATEGORY_INTERNAL;
|
||||
} else {
|
||||
$this->isClientSafe = true;
|
||||
$this->category = static::CATEGORY_GRAPHQL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -212,29 +211,38 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||
*/
|
||||
public function getSource()
|
||||
{
|
||||
if (null === $this->source) {
|
||||
if (!empty($this->nodes[0]) && !empty($this->nodes[0]->loc)) {
|
||||
if ($this->source === null) {
|
||||
if (! empty($this->nodes[0]) && ! empty($this->nodes[0]->loc)) {
|
||||
$this->source = $this->nodes[0]->loc->source;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @return int[]
|
||||
*/
|
||||
public function getPositions()
|
||||
{
|
||||
if (null === $this->positions) {
|
||||
if (!empty($this->nodes)) {
|
||||
$positions = array_map(function($node) {
|
||||
if ($this->positions === null && ! empty($this->nodes)) {
|
||||
$positions = array_map(
|
||||
static function ($node) {
|
||||
return isset($node->loc) ? $node->loc->start : null;
|
||||
}, $this->nodes);
|
||||
$this->positions = array_filter($positions, function($p) {
|
||||
},
|
||||
$this->nodes
|
||||
);
|
||||
|
||||
$positions = array_filter(
|
||||
$positions,
|
||||
static function ($p) {
|
||||
return $p !== null;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->positions = array_values($positions);
|
||||
}
|
||||
|
||||
return $this->positions;
|
||||
}
|
||||
|
||||
|
@ -249,26 +257,36 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||
* point out to field mentioned in multiple fragments. Errors during execution include a
|
||||
* single location, the field which produced the error.
|
||||
*
|
||||
* @api
|
||||
* @return SourceLocation[]
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getLocations()
|
||||
{
|
||||
if (null === $this->locations) {
|
||||
if ($this->locations === null) {
|
||||
$positions = $this->getPositions();
|
||||
$source = $this->getSource();
|
||||
$nodes = $this->nodes;
|
||||
$source = $this->getSource();
|
||||
$nodes = $this->nodes;
|
||||
|
||||
if ($positions && $source) {
|
||||
$this->locations = array_map(function ($pos) use ($source) {
|
||||
return $source->getLocation($pos);
|
||||
}, $positions);
|
||||
} else if ($nodes) {
|
||||
$this->locations = array_filter(array_map(function ($node) {
|
||||
if ($node->loc) {
|
||||
return $node->loc->source->getLocation($node->loc->start);
|
||||
}
|
||||
}, $nodes));
|
||||
$this->locations = array_map(
|
||||
static function ($pos) use ($source) {
|
||||
return $source->getLocation($pos);
|
||||
},
|
||||
$positions
|
||||
);
|
||||
} elseif ($nodes) {
|
||||
$locations = array_filter(
|
||||
array_map(
|
||||
static function ($node) {
|
||||
if ($node->loc && $node->loc->source) {
|
||||
return $node->loc->source->getLocation($node->loc->start);
|
||||
}
|
||||
},
|
||||
$nodes
|
||||
)
|
||||
);
|
||||
$this->locations = array_values($locations);
|
||||
} else {
|
||||
$this->locations = [];
|
||||
}
|
||||
|
@ -278,7 +296,7 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array|Node[]|null
|
||||
* @return Node[]|null
|
||||
*/
|
||||
public function getNodes()
|
||||
{
|
||||
|
@ -289,8 +307,9 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||
* Returns an array describing the path from the root value to the field which produced this error.
|
||||
* Only included for execution errors.
|
||||
*
|
||||
* @return mixed[]|null
|
||||
*
|
||||
* @api
|
||||
* @return array|null
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
|
@ -298,7 +317,7 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getExtensions()
|
||||
{
|
||||
|
@ -309,40 +328,44 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
|||
* Returns array representation of error suitable for serialization
|
||||
*
|
||||
* @deprecated Use FormattedError::createFromException() instead
|
||||
* @return array
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function toSerializableArray()
|
||||
{
|
||||
$arr = [
|
||||
'message' => $this->getMessage()
|
||||
'message' => $this->getMessage(),
|
||||
];
|
||||
|
||||
if ($this->getExtensions()) {
|
||||
$arr = array_merge($this->getExtensions(), $arr);
|
||||
}
|
||||
$locations = Utils::map(
|
||||
$this->getLocations(),
|
||||
static function (SourceLocation $loc) {
|
||||
return $loc->toSerializableArray();
|
||||
}
|
||||
);
|
||||
|
||||
$locations = Utils::map($this->getLocations(), function(SourceLocation $loc) {
|
||||
return $loc->toSerializableArray();
|
||||
});
|
||||
|
||||
if (!empty($locations)) {
|
||||
if (! empty($locations)) {
|
||||
$arr['locations'] = $locations;
|
||||
}
|
||||
if (!empty($this->path)) {
|
||||
if (! empty($this->path)) {
|
||||
$arr['path'] = $this->path;
|
||||
}
|
||||
if (! empty($this->extensions)) {
|
||||
$arr['extensions'] = $this->extensions;
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify data which should be serialized to JSON
|
||||
*
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
*
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
* @since 5.4.0
|
||||
*/
|
||||
function jsonSerialize()
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toSerializableArray();
|
||||
}
|
||||
|
|
|
@ -1,12 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use Countable;
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Language\SourceLocation;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\WrappingType;
|
||||
use GraphQL\Utils\Utils;
|
||||
use Throwable;
|
||||
use function addcslashes;
|
||||
use function array_filter;
|
||||
use function array_intersect_key;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function gettype;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_object;
|
||||
use function is_scalar;
|
||||
use function is_string;
|
||||
use function mb_strlen;
|
||||
use function preg_split;
|
||||
use function sprintf;
|
||||
use function str_repeat;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* This class is used for [default error formatting](error-handling.md).
|
||||
|
@ -15,14 +42,16 @@ use GraphQL\Utils\Utils;
|
|||
*/
|
||||
class FormattedError
|
||||
{
|
||||
/** @var string */
|
||||
private static $internalErrorMessage = 'Internal server error';
|
||||
|
||||
/**
|
||||
* Set default error message for internal errors formatted using createFormattedError().
|
||||
* This value can be overridden by passing 3rd argument to `createFormattedError()`.
|
||||
*
|
||||
* @api
|
||||
* @param string $msg
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public static function setInternalErrorMessage($msg)
|
||||
{
|
||||
|
@ -33,7 +62,6 @@ class FormattedError
|
|||
* Prints a GraphQLError to a string, representing useful location information
|
||||
* about the error's position in the source.
|
||||
*
|
||||
* @param Error $error
|
||||
* @return string
|
||||
*/
|
||||
public static function printError(Error $error)
|
||||
|
@ -41,63 +69,65 @@ class FormattedError
|
|||
$printedLocations = [];
|
||||
if ($error->nodes) {
|
||||
/** @var Node $node */
|
||||
foreach($error->nodes as $node) {
|
||||
if ($node->loc) {
|
||||
$printedLocations[] = self::highlightSourceAtLocation(
|
||||
$node->loc->source,
|
||||
$node->loc->source->getLocation($node->loc->start)
|
||||
);
|
||||
foreach ($error->nodes as $node) {
|
||||
if (! $node->loc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node->loc->source === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$printedLocations[] = self::highlightSourceAtLocation(
|
||||
$node->loc->source,
|
||||
$node->loc->source->getLocation($node->loc->start)
|
||||
);
|
||||
}
|
||||
} else if ($error->getSource() && $error->getLocations()) {
|
||||
} elseif ($error->getSource() && $error->getLocations()) {
|
||||
$source = $error->getSource();
|
||||
foreach($error->getLocations() as $location) {
|
||||
foreach ($error->getLocations() as $location) {
|
||||
$printedLocations[] = self::highlightSourceAtLocation($source, $location);
|
||||
}
|
||||
}
|
||||
|
||||
return !$printedLocations
|
||||
return ! $printedLocations
|
||||
? $error->getMessage()
|
||||
: join("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
|
||||
: implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a helpful description of the location of the error in the GraphQL
|
||||
* Source document.
|
||||
*
|
||||
* @param Source $source
|
||||
* @param SourceLocation $location
|
||||
* @return string
|
||||
*/
|
||||
private static function highlightSourceAtLocation(Source $source, SourceLocation $location)
|
||||
{
|
||||
$line = $location->line;
|
||||
$lineOffset = $source->locationOffset->line - 1;
|
||||
$columnOffset = self::getColumnOffset($source, $location);
|
||||
$contextLine = $line + $lineOffset;
|
||||
$line = $location->line;
|
||||
$lineOffset = $source->locationOffset->line - 1;
|
||||
$columnOffset = self::getColumnOffset($source, $location);
|
||||
$contextLine = $line + $lineOffset;
|
||||
$contextColumn = $location->column + $columnOffset;
|
||||
$prevLineNum = (string) ($contextLine - 1);
|
||||
$lineNum = (string) $contextLine;
|
||||
$nextLineNum = (string) ($contextLine + 1);
|
||||
$padLen = strlen($nextLineNum);
|
||||
$lines = preg_split('/\r\n|[\n\r]/', $source->body);
|
||||
$prevLineNum = (string) ($contextLine - 1);
|
||||
$lineNum = (string) $contextLine;
|
||||
$nextLineNum = (string) ($contextLine + 1);
|
||||
$padLen = strlen($nextLineNum);
|
||||
$lines = preg_split('/\r\n|[\n\r]/', $source->body);
|
||||
|
||||
$lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0];
|
||||
|
||||
$outputLines = [
|
||||
"{$source->name} ($contextLine:$contextColumn)",
|
||||
sprintf('%s (%s:%s)', $source->name, $contextLine, $contextColumn),
|
||||
$line >= 2 ? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null,
|
||||
self::lpad($padLen, $lineNum) . ': ' . $lines[$line - 1],
|
||||
self::whitespace(2 + $padLen + $contextColumn - 1) . '^',
|
||||
$line < count($lines)? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null
|
||||
$line < count($lines) ? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null,
|
||||
];
|
||||
|
||||
return join("\n", array_filter($outputLines));
|
||||
return implode("\n", array_filter($outputLines));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Source $source
|
||||
* @param SourceLocation $location
|
||||
* @return int
|
||||
*/
|
||||
private static function getColumnOffset(Source $source, SourceLocation $location)
|
||||
|
@ -107,17 +137,21 @@ class FormattedError
|
|||
|
||||
/**
|
||||
* @param int $len
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function whitespace($len) {
|
||||
private static function whitespace($len)
|
||||
{
|
||||
return str_repeat(' ', $len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $len
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function lpad($len, $str) {
|
||||
private static function lpad($len, $str)
|
||||
{
|
||||
return self::whitespace($len - mb_strlen($str)) . $str;
|
||||
}
|
||||
|
||||
|
@ -130,18 +164,21 @@ class FormattedError
|
|||
*
|
||||
* For a list of available debug flags see GraphQL\Error\Debug constants.
|
||||
*
|
||||
* @param Throwable $e
|
||||
* @param bool|int $debug
|
||||
* @param string $internalErrorMessage
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @throws Throwable
|
||||
*
|
||||
* @api
|
||||
* @param \Throwable $e
|
||||
* @param bool|int $debug
|
||||
* @param string $internalErrorMessage
|
||||
* @return array
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public static function createFromException($e, $debug = false, $internalErrorMessage = null)
|
||||
{
|
||||
Utils::invariant(
|
||||
$e instanceof \Exception || $e instanceof \Throwable,
|
||||
"Expected exception, got %s",
|
||||
$e instanceof Exception || $e instanceof Throwable,
|
||||
'Expected exception, got %s',
|
||||
Utils::getVariableType($e)
|
||||
);
|
||||
|
||||
|
@ -149,31 +186,36 @@ class FormattedError
|
|||
|
||||
if ($e instanceof ClientAware) {
|
||||
$formattedError = [
|
||||
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
|
||||
'category' => $e->getCategory()
|
||||
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
|
||||
'extensions' => [
|
||||
'category' => $e->getCategory(),
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$formattedError = [
|
||||
'message' => $internalErrorMessage,
|
||||
'category' => Error::CATEGORY_INTERNAL
|
||||
'message' => $internalErrorMessage,
|
||||
'extensions' => [
|
||||
'category' => Error::CATEGORY_INTERNAL,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($e instanceof Error) {
|
||||
if ($e->getExtensions()) {
|
||||
$formattedError = array_merge($e->getExtensions(), $formattedError);
|
||||
}
|
||||
|
||||
$locations = Utils::map($e->getLocations(), function(SourceLocation $loc) {
|
||||
return $loc->toSerializableArray();
|
||||
});
|
||||
|
||||
if (!empty($locations)) {
|
||||
$locations = Utils::map(
|
||||
$e->getLocations(),
|
||||
static function (SourceLocation $loc) {
|
||||
return $loc->toSerializableArray();
|
||||
}
|
||||
);
|
||||
if (! empty($locations)) {
|
||||
$formattedError['locations'] = $locations;
|
||||
}
|
||||
if (!empty($e->path)) {
|
||||
if (! empty($e->path)) {
|
||||
$formattedError['path'] = $e->path;
|
||||
}
|
||||
if (! empty($e->getExtensions())) {
|
||||
$formattedError['extensions'] = $e->getExtensions() + $formattedError['extensions'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($debug) {
|
||||
|
@ -187,56 +229,67 @@ class FormattedError
|
|||
* Decorates spec-compliant $formattedError with debug entries according to $debug flags
|
||||
* (see GraphQL\Error\Debug for available flags)
|
||||
*
|
||||
* @param array $formattedError
|
||||
* @param \Throwable $e
|
||||
* @param bool $debug
|
||||
* @return array
|
||||
* @throws \Throwable
|
||||
* @param mixed[] $formattedError
|
||||
* @param Throwable $e
|
||||
* @param bool|int $debug
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
public static function addDebugEntries(array $formattedError, $e, $debug)
|
||||
{
|
||||
if (!$debug) {
|
||||
if (! $debug) {
|
||||
return $formattedError;
|
||||
}
|
||||
|
||||
Utils::invariant(
|
||||
$e instanceof \Exception || $e instanceof \Throwable,
|
||||
"Expected exception, got %s",
|
||||
$e instanceof Exception || $e instanceof Throwable,
|
||||
'Expected exception, got %s',
|
||||
Utils::getVariableType($e)
|
||||
);
|
||||
|
||||
$debug = (int) $debug;
|
||||
|
||||
if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
|
||||
if (!$e instanceof Error) {
|
||||
if (! $e instanceof Error) {
|
||||
throw $e;
|
||||
} else if ($e->getPrevious()) {
|
||||
}
|
||||
|
||||
if ($e->getPrevious()) {
|
||||
throw $e->getPrevious();
|
||||
}
|
||||
}
|
||||
|
||||
$isInternal = !$e instanceof ClientAware || !$e->isClientSafe();
|
||||
$isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
|
||||
|
||||
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isInternal) {
|
||||
if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) && $isUnsafe) {
|
||||
if ($e->getPrevious()) {
|
||||
throw $e->getPrevious();
|
||||
}
|
||||
}
|
||||
|
||||
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isUnsafe) {
|
||||
// Displaying debugMessage as a first entry:
|
||||
$formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
|
||||
}
|
||||
|
||||
if ($debug & Debug::INCLUDE_TRACE) {
|
||||
if ($e instanceof \ErrorException || $e instanceof \Error) {
|
||||
if ($e instanceof ErrorException || $e instanceof \Error) {
|
||||
$formattedError += [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
];
|
||||
}
|
||||
|
||||
$isTrivial = $e instanceof Error && !$e->getPrevious();
|
||||
$isTrivial = $e instanceof Error && ! $e->getPrevious();
|
||||
|
||||
if (!$isTrivial) {
|
||||
$debugging = $e->getPrevious() ?: $e;
|
||||
if (! $isTrivial) {
|
||||
$debugging = $e->getPrevious() ?: $e;
|
||||
$formattedError['trace'] = static::toSafeTrace($debugging);
|
||||
}
|
||||
}
|
||||
|
||||
return $formattedError;
|
||||
}
|
||||
|
||||
|
@ -244,67 +297,71 @@ class FormattedError
|
|||
* Prepares final error formatter taking in account $debug flags.
|
||||
* If initial formatter is not set, FormattedError::createFromException is used
|
||||
*
|
||||
* @param callable|null $formatter
|
||||
* @param $debug
|
||||
* @return callable|\Closure
|
||||
* @param bool|int $debug
|
||||
*
|
||||
* @return callable|callable
|
||||
*/
|
||||
public static function prepareFormatter(callable $formatter = null, $debug)
|
||||
public static function prepareFormatter(?callable $formatter = null, $debug)
|
||||
{
|
||||
$formatter = $formatter ?: function($e) {
|
||||
$formatter = $formatter ?: static function ($e) {
|
||||
return FormattedError::createFromException($e);
|
||||
};
|
||||
if ($debug) {
|
||||
$formatter = function($e) use ($formatter, $debug) {
|
||||
$formatter = static function ($e) use ($formatter, $debug) {
|
||||
return FormattedError::addDebugEntries($formatter($e), $e, $debug);
|
||||
};
|
||||
}
|
||||
|
||||
return $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns error trace as serializable array
|
||||
*
|
||||
* @param Throwable $error
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @api
|
||||
* @param \Throwable $error
|
||||
* @return array
|
||||
*/
|
||||
public static function toSafeTrace($error)
|
||||
{
|
||||
$trace = $error->getTrace();
|
||||
|
||||
// Remove invariant entries as they don't provide much value:
|
||||
if (
|
||||
isset($trace[0]['function']) && isset($trace[0]['class']) &&
|
||||
('GraphQL\Utils\Utils::invariant' === $trace[0]['class'].'::'.$trace[0]['function'])) {
|
||||
if (isset($trace[0]['function']) && isset($trace[0]['class']) &&
|
||||
// Remove invariant entries as they don't provide much value:
|
||||
($trace[0]['class'] . '::' . $trace[0]['function'] === 'GraphQL\Utils\Utils::invariant')) {
|
||||
array_shift($trace);
|
||||
} elseif (! isset($trace[0]['file'])) {
|
||||
// Remove root call as it's likely error handler trace:
|
||||
array_shift($trace);
|
||||
}
|
||||
|
||||
// Remove root call as it's likely error handler trace:
|
||||
else if (!isset($trace[0]['file'])) {
|
||||
array_shift($trace);
|
||||
}
|
||||
return array_map(
|
||||
static function ($err) {
|
||||
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
|
||||
|
||||
return array_map(function($err) {
|
||||
$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']) : [];
|
||||
$funcStr = $func . '(' . implode(', ', $args) . ')';
|
||||
|
||||
if (isset($err['function'])) {
|
||||
$func = $err['function'];
|
||||
$args = !empty($err['args']) ? array_map([__CLASS__, 'printVar'], $err['args']) : [];
|
||||
$funcStr = $func . '(' . implode(", ", $args) . ')';
|
||||
|
||||
if (isset($err['class'])) {
|
||||
$safeErr['call'] = $err['class'] . '::' . $funcStr;
|
||||
} else {
|
||||
$safeErr['function'] = $funcStr;
|
||||
if (isset($err['class'])) {
|
||||
$safeErr['call'] = $err['class'] . '::' . $funcStr;
|
||||
} else {
|
||||
$safeErr['function'] = $funcStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $safeErr;
|
||||
}, $trace);
|
||||
return $safeErr;
|
||||
},
|
||||
$trace
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $var
|
||||
* @param mixed $var
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function printVar($var)
|
||||
|
@ -314,16 +371,17 @@ class FormattedError
|
|||
if ($var instanceof WrappingType) {
|
||||
$var = $var->getWrappedType(true);
|
||||
}
|
||||
|
||||
return 'GraphQLType: ' . $var->name;
|
||||
}
|
||||
|
||||
if (is_object($var)) {
|
||||
return 'instance of ' . get_class($var) . ($var instanceof \Countable ? '(' . count($var) . ')' : '');
|
||||
return 'instance of ' . get_class($var) . ($var instanceof Countable ? '(' . count($var) . ')' : '');
|
||||
}
|
||||
if (is_array($var)) {
|
||||
return 'array(' . count($var) . ')';
|
||||
}
|
||||
if ('' === $var) {
|
||||
if ($var === '') {
|
||||
return '(empty string)';
|
||||
}
|
||||
if (is_string($var)) {
|
||||
|
@ -335,42 +393,48 @@ class FormattedError
|
|||
if (is_scalar($var)) {
|
||||
return $var;
|
||||
}
|
||||
if (null === $var) {
|
||||
if ($var === null) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
return gettype($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated as of v0.8.0
|
||||
* @param $error
|
||||
*
|
||||
* @param string $error
|
||||
* @param SourceLocation[] $locations
|
||||
* @return array
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public static function create($error, array $locations = [])
|
||||
{
|
||||
$formatted = [
|
||||
'message' => $error
|
||||
];
|
||||
$formatted = ['message' => $error];
|
||||
|
||||
if (!empty($locations)) {
|
||||
$formatted['locations'] = array_map(function($loc) { return $loc->toArray();}, $locations);
|
||||
if (! empty($locations)) {
|
||||
$formatted['locations'] = array_map(
|
||||
static function ($loc) {
|
||||
return $loc->toArray();
|
||||
},
|
||||
$locations
|
||||
);
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ErrorException $e
|
||||
* @deprecated as of v0.10.0, use general purpose method createFromException() instead
|
||||
* @return array
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public static function createFromPHPError(\ErrorException $e)
|
||||
public static function createFromPHPError(ErrorException $e)
|
||||
{
|
||||
return [
|
||||
'message' => $e->getMessage(),
|
||||
'message' => $e->getMessage(),
|
||||
'severity' => $e->getSeverity(),
|
||||
'trace' => self::toSafeTrace($e)
|
||||
'trace' => self::toSafeTrace($e),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* Class InvariantVoilation
|
||||
*
|
||||
* Note:
|
||||
* This exception should not inherit base Error exception as it is raised when there is an error somewhere in
|
||||
* user-land code
|
||||
*
|
||||
* @package GraphQL\Error
|
||||
*/
|
||||
class InvariantViolation extends \LogicException
|
||||
class InvariantViolation extends LogicException
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use GraphQL\Language\Source;
|
||||
use function sprintf;
|
||||
|
||||
class SyntaxError extends Error
|
||||
{
|
||||
/**
|
||||
* @param Source $source
|
||||
* @param int $position
|
||||
* @param int $position
|
||||
* @param string $description
|
||||
*/
|
||||
public function __construct(Source $source, $position, $description)
|
||||
{
|
||||
parent::__construct(
|
||||
"Syntax Error: $description",
|
||||
sprintf('Syntax Error: %s', $description),
|
||||
null,
|
||||
$source,
|
||||
[$position]
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class UserError
|
||||
*
|
||||
* Error caused by actions of GraphQL clients. Can be safely displayed to a client...
|
||||
*
|
||||
* @package GraphQL\Error
|
||||
*/
|
||||
class UserError extends \RuntimeException implements ClientAware
|
||||
class UserError extends RuntimeException implements ClientAware
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Error;
|
||||
|
||||
use GraphQL\Exception\InvalidArgument;
|
||||
use function is_int;
|
||||
use function trigger_error;
|
||||
use const E_USER_WARNING;
|
||||
|
||||
/**
|
||||
* Encapsulates warnings produced by the library.
|
||||
*
|
||||
|
@ -9,27 +17,29 @@ namespace GraphQL\Error;
|
|||
*/
|
||||
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;
|
||||
|
||||
static $enableWarnings = self::ALL;
|
||||
/** @var int */
|
||||
private static $enableWarnings = self::ALL;
|
||||
|
||||
static $warned = [];
|
||||
/** @var mixed[] */
|
||||
private static $warned = [];
|
||||
|
||||
static private $warningHandler;
|
||||
/** @var callable|null */
|
||||
private static $warningHandler;
|
||||
|
||||
/**
|
||||
* Sets warning handler which can intercept all system warnings.
|
||||
* When not set, trigger_error() is used to notify about warnings.
|
||||
*
|
||||
* @api
|
||||
* @param callable|null $warningHandler
|
||||
*/
|
||||
public static function setWarningHandler(callable $warningHandler = null)
|
||||
public static function setWarningHandler(?callable $warningHandler = null) : void
|
||||
{
|
||||
self::$warningHandler = $warningHandler;
|
||||
}
|
||||
|
@ -42,18 +52,20 @@ final class Warning
|
|||
*
|
||||
* When passing true - suppresses all warnings.
|
||||
*
|
||||
* @api
|
||||
* @param bool|int $suppress
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
static function suppress($suppress = true)
|
||||
public static function suppress($suppress = true) : void
|
||||
{
|
||||
if (true === $suppress) {
|
||||
if ($suppress === true) {
|
||||
self::$enableWarnings = 0;
|
||||
} else if (false === $suppress) {
|
||||
} elseif ($suppress === false) {
|
||||
self::$enableWarnings = self::ALL;
|
||||
} else {
|
||||
$suppress = (int) $suppress;
|
||||
} elseif (is_int($suppress)) {
|
||||
self::$enableWarnings &= ~$suppress;
|
||||
} else {
|
||||
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,38 +77,40 @@ final class Warning
|
|||
*
|
||||
* When passing true - re-enables all warnings.
|
||||
*
|
||||
* @api
|
||||
* @param bool|int $enable
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public static function enable($enable = true)
|
||||
public static function enable($enable = true) : void
|
||||
{
|
||||
if (true === $enable) {
|
||||
if ($enable === true) {
|
||||
self::$enableWarnings = self::ALL;
|
||||
} else if (false === $enable) {
|
||||
} elseif ($enable === false) {
|
||||
self::$enableWarnings = 0;
|
||||
} else {
|
||||
$enable = (int) $enable;
|
||||
} elseif (is_int($enable)) {
|
||||
self::$enableWarnings |= $enable;
|
||||
} else {
|
||||
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable);
|
||||
}
|
||||
}
|
||||
|
||||
static function warnOnce($errorMessage, $warningId, $messageLevel = null)
|
||||
public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
|
||||
{
|
||||
if (self::$warningHandler) {
|
||||
if (self::$warningHandler !== null) {
|
||||
$fn = self::$warningHandler;
|
||||
$fn($errorMessage, $warningId);
|
||||
} else if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
|
||||
} elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) {
|
||||
self::$warned[$warningId] = true;
|
||||
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
static function warn($errorMessage, $warningId, $messageLevel = null)
|
||||
public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
|
||||
{
|
||||
if (self::$warningHandler) {
|
||||
if (self::$warningHandler !== null) {
|
||||
$fn = self::$warningHandler;
|
||||
$fn($errorMessage, $warningId);
|
||||
} else if ((self::$enableWarnings & $warningId) > 0) {
|
||||
} elseif ((self::$enableWarnings & $warningId) > 0) {
|
||||
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
|
20
src/Exception/InvalidArgument.php
Normal file
20
src/Exception/InvalidArgument.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Exception;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use function gettype;
|
||||
use function sprintf;
|
||||
|
||||
final class InvalidArgument extends InvalidArgumentException
|
||||
{
|
||||
/**
|
||||
* @param mixed $argument
|
||||
*/
|
||||
public static function fromExpectedTypeAndArgument(string $expectedType, $argument) : self
|
||||
{
|
||||
return new self(sprintf('Expected type "%s", got "%s"', $expectedType, gettype($argument)));
|
||||
}
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||
use GraphQL\Type\Schema;
|
||||
|
@ -10,78 +14,65 @@ 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
|
||||
*/
|
||||
class ExecutionContext
|
||||
{
|
||||
/**
|
||||
* @var Schema
|
||||
*/
|
||||
/** @var Schema */
|
||||
public $schema;
|
||||
|
||||
/**
|
||||
* @var FragmentDefinitionNode[]
|
||||
*/
|
||||
/** @var FragmentDefinitionNode[] */
|
||||
public $fragments;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
/** @var mixed */
|
||||
public $rootValue;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
/** @var mixed */
|
||||
public $contextValue;
|
||||
|
||||
/**
|
||||
* @var OperationDefinitionNode
|
||||
*/
|
||||
/** @var OperationDefinitionNode */
|
||||
public $operation;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var mixed[] */
|
||||
public $variableValues;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
/** @var callable */
|
||||
public $fieldResolver;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var Error[] */
|
||||
public $errors;
|
||||
|
||||
/** @var PromiseAdapter */
|
||||
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->contextValue = $contextValue;
|
||||
$this->operation = $operation;
|
||||
$this->variableValues = $variables;
|
||||
$this->errors = $errors ?: [];
|
||||
$this->fieldResolver = $fieldResolver;
|
||||
$this->promises = $promiseAdapter;
|
||||
) {
|
||||
$this->schema = $schema;
|
||||
$this->fragments = $fragments;
|
||||
$this->rootValue = $rootValue;
|
||||
$this->contextValue = $contextValue;
|
||||
$this->operation = $operation;
|
||||
$this->variableValues = $variableValues;
|
||||
$this->errors = $errors ?: [];
|
||||
$this->fieldResolver = $fieldResolver;
|
||||
$this->promiseAdapter = $promiseAdapter;
|
||||
}
|
||||
|
||||
public function addError(Error $error)
|
||||
{
|
||||
$this->errors[] = $error;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Error\FormattedError;
|
||||
use JsonSerializable;
|
||||
use function array_map;
|
||||
|
||||
/**
|
||||
* Returned after [query execution](executing-queries.md).
|
||||
|
@ -11,13 +17,13 @@ use GraphQL\Error\FormattedError;
|
|||
* Could be converted to [spec-compliant](https://facebook.github.io/graphql/#sec-Response-Format)
|
||||
* serializable array using `toArray()`
|
||||
*/
|
||||
class ExecutionResult implements \JsonSerializable
|
||||
class ExecutionResult implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Data collected from resolvers during query execution
|
||||
*
|
||||
* @api
|
||||
* @var array
|
||||
* @var mixed[]
|
||||
*/
|
||||
public $data;
|
||||
|
||||
|
@ -28,7 +34,7 @@ class ExecutionResult implements \JsonSerializable
|
|||
* contain original exception.
|
||||
*
|
||||
* @api
|
||||
* @var \GraphQL\Error\Error[]
|
||||
* @var Error[]
|
||||
*/
|
||||
public $errors;
|
||||
|
||||
|
@ -37,29 +43,25 @@ class ExecutionResult implements \JsonSerializable
|
|||
* Conforms to
|
||||
*
|
||||
* @api
|
||||
* @var array
|
||||
* @var mixed[]
|
||||
*/
|
||||
public $extensions;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
/** @var callable */
|
||||
private $errorFormatter;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
/** @var callable */
|
||||
private $errorsHandler;
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $errors
|
||||
* @param array $extensions
|
||||
* @param mixed[] $data
|
||||
* @param Error[] $errors
|
||||
* @param mixed[] $extensions
|
||||
*/
|
||||
public function __construct(array $data = null, array $errors = [], array $extensions = [])
|
||||
public function __construct($data = null, array $errors = [], array $extensions = [])
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->errors = $errors;
|
||||
$this->data = $data;
|
||||
$this->errors = $errors;
|
||||
$this->extensions = $extensions;
|
||||
}
|
||||
|
||||
|
@ -76,13 +78,14 @@ class ExecutionResult implements \JsonSerializable
|
|||
* // ... other keys
|
||||
* );
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
* @api
|
||||
* @param callable $errorFormatter
|
||||
* @return $this
|
||||
*/
|
||||
public function setErrorFormatter(callable $errorFormatter)
|
||||
{
|
||||
$this->errorFormatter = $errorFormatter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -96,16 +99,25 @@ class ExecutionResult implements \JsonSerializable
|
|||
* return array_map($formatter, $errors);
|
||||
* }
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
* @api
|
||||
* @param callable $handler
|
||||
* @return $this
|
||||
*/
|
||||
public function setErrorsHandler(callable $handler)
|
||||
{
|
||||
$this->errorsHandler = $handler;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts GraphQL query result to spec-compliant serializable array using provided
|
||||
* errors handler and formatter.
|
||||
|
@ -116,42 +128,35 @@ class ExecutionResult implements \JsonSerializable
|
|||
* $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from
|
||||
* GraphQL\Error\Debug
|
||||
*
|
||||
* @api
|
||||
* @param bool|int $debug
|
||||
* @return array
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function toArray($debug = false)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
if (!empty($this->errors)) {
|
||||
$errorsHandler = $this->errorsHandler ?: function(array $errors, callable $formatter) {
|
||||
if (! empty($this->errors)) {
|
||||
$errorsHandler = $this->errorsHandler ?: static function (array $errors, callable $formatter) {
|
||||
return array_map($formatter, $errors);
|
||||
};
|
||||
|
||||
$result['errors'] = $errorsHandler(
|
||||
$this->errors,
|
||||
FormattedError::prepareFormatter($this->errorFormatter, $debug)
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $this->data) {
|
||||
if ($this->data !== null) {
|
||||
$result['data'] = $this->data;
|
||||
}
|
||||
|
||||
if (!empty($this->extensions)) {
|
||||
$result['extensions'] = (array) $this->extensions;
|
||||
if (! empty($this->extensions)) {
|
||||
$result['extensions'] = $this->extensions;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of \JsonSerializable interface
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
15
src/Executor/ExecutorImplementation.php
Normal file
15
src/Executor/ExecutorImplementation.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
use GraphQL\Executor\Promise\Promise;
|
||||
|
||||
interface ExecutorImplementation
|
||||
{
|
||||
/**
|
||||
* Returns promise of {@link ExecutionResult}. Promise should always resolve, never reject.
|
||||
*/
|
||||
public function doExecute() : Promise;
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Executor\Promise\Adapter;
|
||||
|
||||
use GraphQL\Executor\Promise\Promise;
|
||||
|
@ -6,6 +9,9 @@ use GraphQL\Executor\Promise\PromiseAdapter;
|
|||
use GraphQL\Utils\Utils;
|
||||
use React\Promise\Promise as ReactPromise;
|
||||
use React\Promise\PromiseInterface as ReactPromiseInterface;
|
||||
use function React\Promise\all;
|
||||
use function React\Promise\reject;
|
||||
use function React\Promise\resolve;
|
||||
|
||||
class ReactPromiseAdapter implements PromiseAdapter
|
||||
{
|
||||
|
@ -28,10 +34,11 @@ class ReactPromiseAdapter implements PromiseAdapter
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null)
|
||||
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null)
|
||||
{
|
||||
/** @var $adoptedPromise ReactPromiseInterface */
|
||||
/** @var ReactPromiseInterface $adoptedPromise */
|
||||
$adoptedPromise = $promise->adoptedPromise;
|
||||
|
||||
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
|
||||
}
|
||||
|
||||
|
@ -41,6 +48,7 @@ class ReactPromiseAdapter implements PromiseAdapter
|
|||
public function create(callable $resolver)
|
||||
{
|
||||
$promise = new ReactPromise($resolver);
|
||||
|
||||
return new Promise($promise, $this);
|
||||
}
|
||||
|
||||
|
@ -49,7 +57,8 @@ class ReactPromiseAdapter implements PromiseAdapter
|
|||
*/
|
||||
public function createFulfilled($value = null)
|
||||
{
|
||||
$promise = \React\Promise\resolve($value);
|
||||
$promise = resolve($value);
|
||||
|
||||
return new Promise($promise, $this);
|
||||
}
|
||||
|
||||
|
@ -58,7 +67,8 @@ class ReactPromiseAdapter implements PromiseAdapter
|
|||
*/
|
||||
public function createRejected($reason)
|
||||
{
|
||||
$promise = \React\Promise\reject($reason);
|
||||
$promise = reject($reason);
|
||||
|
||||
return new Promise($promise, $this);
|
||||
}
|
||||
|
||||
|
@ -68,11 +78,14 @@ class ReactPromiseAdapter implements PromiseAdapter
|
|||
public function all(array $promisesOrValues)
|
||||
{
|
||||
// TODO: rework with generators when PHP minimum required version is changed to 5.5+
|
||||
$promisesOrValues = Utils::map($promisesOrValues, function ($item) {
|
||||
return $item instanceof Promise ? $item->adoptedPromise : $item;
|
||||
});
|
||||
$promisesOrValues = Utils::map(
|
||||
$promisesOrValues,
|
||||
static function ($item) {
|
||||
return $item instanceof Promise ? $item->adoptedPromise : $item;
|
||||
}
|
||||
);
|
||||
|
||||
$promise = \React\Promise\all($promisesOrValues)->then(function($values) use ($promisesOrValues) {
|
||||
$promise = all($promisesOrValues)->then(static function ($values) use ($promisesOrValues) {
|
||||
$orderedResults = [];
|
||||
|
||||
foreach ($promisesOrValues as $key => $value) {
|
||||
|
@ -81,6 +94,7 @@ class ReactPromiseAdapter implements PromiseAdapter
|
|||
|
||||
return $orderedResults;
|
||||
});
|
||||
|
||||
return new Promise($promise, $this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,117 +1,164 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Executor\Promise\Adapter;
|
||||
|
||||
use Exception;
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\Utils\Utils;
|
||||
use SplQueue;
|
||||
use Throwable;
|
||||
use function is_object;
|
||||
use function method_exists;
|
||||
|
||||
/**
|
||||
* Class SyncPromise
|
||||
*
|
||||
* Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
|
||||
* (using queue to defer promises execution)
|
||||
*
|
||||
* @package GraphQL\Executor\Promise\Adapter
|
||||
*/
|
||||
class SyncPromise
|
||||
{
|
||||
const PENDING = 'pending';
|
||||
const PENDING = 'pending';
|
||||
const FULFILLED = 'fulfilled';
|
||||
const REJECTED = 'rejected';
|
||||
const REJECTED = 'rejected';
|
||||
|
||||
/**
|
||||
* @var \SplQueue
|
||||
*/
|
||||
/** @var SplQueue */
|
||||
public static $queue;
|
||||
|
||||
public static function getQueue()
|
||||
{
|
||||
return self::$queue ?: self::$queue = new \SplQueue();
|
||||
}
|
||||
/** @var string */
|
||||
public $state = self::PENDING;
|
||||
|
||||
public static function runQueue()
|
||||
/** @var ExecutionResult|Throwable */
|
||||
public $result;
|
||||
|
||||
/**
|
||||
* Promises created in `then` method of this promise and awaiting for resolution of this promise
|
||||
*
|
||||
* @var mixed[][]
|
||||
*/
|
||||
private $waiting = [];
|
||||
|
||||
public static function runQueue() : void
|
||||
{
|
||||
$q = self::$queue;
|
||||
while ($q && !$q->isEmpty()) {
|
||||
while ($q !== null && ! $q->isEmpty()) {
|
||||
$task = $q->dequeue();
|
||||
$task();
|
||||
}
|
||||
}
|
||||
|
||||
public $state = self::PENDING;
|
||||
|
||||
public $result;
|
||||
|
||||
/**
|
||||
* Promises created in `then` method of this promise and awaiting for resolution of this promise
|
||||
* @var array
|
||||
*/
|
||||
private $waiting = [];
|
||||
|
||||
public function reject($reason)
|
||||
{
|
||||
if (!$reason instanceof \Exception && !$reason instanceof \Throwable) {
|
||||
throw new \Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
|
||||
}
|
||||
|
||||
switch ($this->state) {
|
||||
case self::PENDING:
|
||||
$this->state = self::REJECTED;
|
||||
$this->result = $reason;
|
||||
$this->enqueueWaitingPromises();
|
||||
break;
|
||||
case self::REJECTED:
|
||||
if ($reason !== $this->result) {
|
||||
throw new \Exception("Cannot change rejection reason");
|
||||
}
|
||||
break;
|
||||
case self::FULFILLED:
|
||||
throw new \Exception("Cannot reject fulfilled promise");
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function resolve($value)
|
||||
public function resolve($value) : self
|
||||
{
|
||||
switch ($this->state) {
|
||||
case self::PENDING:
|
||||
if ($value === $this) {
|
||||
throw new \Exception("Cannot resolve promise with self");
|
||||
throw new Exception('Cannot resolve promise with self');
|
||||
}
|
||||
if (is_object($value) && method_exists($value, 'then')) {
|
||||
$value->then(
|
||||
function($resolvedValue) {
|
||||
function ($resolvedValue) {
|
||||
$this->resolve($resolvedValue);
|
||||
},
|
||||
function($reason) {
|
||||
function ($reason) {
|
||||
$this->reject($reason);
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->state = self::FULFILLED;
|
||||
$this->state = self::FULFILLED;
|
||||
$this->result = $value;
|
||||
$this->enqueueWaitingPromises();
|
||||
break;
|
||||
case self::FULFILLED:
|
||||
if ($this->result !== $value) {
|
||||
throw new \Exception("Cannot change value of fulfilled promise");
|
||||
throw new Exception('Cannot change value of fulfilled promise');
|
||||
}
|
||||
break;
|
||||
case self::REJECTED:
|
||||
throw new \Exception("Cannot resolve rejected promise");
|
||||
throw new Exception('Cannot resolve rejected promise');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function then(callable $onFulfilled = null, callable $onRejected = null)
|
||||
public function reject($reason) : self
|
||||
{
|
||||
if ($this->state === self::REJECTED && !$onRejected) {
|
||||
if (! $reason instanceof Exception && ! $reason instanceof Throwable) {
|
||||
throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
|
||||
}
|
||||
|
||||
switch ($this->state) {
|
||||
case self::PENDING:
|
||||
$this->state = self::REJECTED;
|
||||
$this->result = $reason;
|
||||
$this->enqueueWaitingPromises();
|
||||
break;
|
||||
case self::REJECTED:
|
||||
if ($reason !== $this->result) {
|
||||
throw new Exception('Cannot change rejection reason');
|
||||
}
|
||||
break;
|
||||
case self::FULFILLED:
|
||||
throw new Exception('Cannot reject fulfilled promise');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function enqueueWaitingPromises() : void
|
||||
{
|
||||
Utils::invariant(
|
||||
$this->state !== self::PENDING,
|
||||
'Cannot enqueue derived promises when parent is still pending'
|
||||
);
|
||||
|
||||
foreach ($this->waiting as $descriptor) {
|
||||
self::getQueue()->enqueue(function () use ($descriptor) {
|
||||
/** @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);
|
||||
}
|
||||
} elseif ($this->state === self::REJECTED) {
|
||||
try {
|
||||
if ($onRejected === null) {
|
||||
$promise->reject($this->result);
|
||||
} else {
|
||||
$promise->resolve($onRejected($this->result));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$promise->reject($e);
|
||||
} catch (Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
$this->waiting = [];
|
||||
}
|
||||
|
||||
public static function getQueue() : SplQueue
|
||||
{
|
||||
return self::$queue ?: self::$queue = new SplQueue();
|
||||
}
|
||||
|
||||
public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
|
||||
{
|
||||
if ($this->state === self::REJECTED && $onRejected === null) {
|
||||
return $this;
|
||||
}
|
||||
if ($this->state === self::FULFILLED && !$onFulfilled) {
|
||||
if ($this->state === self::FULFILLED && $onFulfilled === null) {
|
||||
return $this;
|
||||
}
|
||||
$tmp = new self();
|
||||
$tmp = new self();
|
||||
$this->waiting[] = [$tmp, $onFulfilled, $onRejected];
|
||||
|
||||
if ($this->state !== self::PENDING) {
|
||||
|
@ -120,39 +167,4 @@ class SyncPromise
|
|||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
private function enqueueWaitingPromises()
|
||||
{
|
||||
Utils::invariant($this->state !== self::PENDING, 'Cannot enqueue derived promises when parent is still pending');
|
||||
|
||||
foreach ($this->waiting as $descriptor) {
|
||||
self::getQueue()->enqueue(function () use ($descriptor) {
|
||||
/** @var $promise self */
|
||||
list($promise, $onFulfilled, $onRejected) = $descriptor;
|
||||
|
||||
if ($this->state === self::FULFILLED) {
|
||||
try {
|
||||
$promise->resolve($onFulfilled ? $onFulfilled($this->result) : $this->result);
|
||||
} catch (\Exception $e) {
|
||||
$promise->reject($e);
|
||||
} catch (\Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
} else if ($this->state === self::REJECTED) {
|
||||
try {
|
||||
if ($onRejected) {
|
||||
$promise->resolve($onRejected($this->result));
|
||||
} else {
|
||||
$promise->reject($this->result);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$promise->reject($e);
|
||||
} catch (\Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
$this->waiting = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
<?php
|
||||
|
||||
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;
|
||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||
use GraphQL\Utils\Utils;
|
||||
use Throwable;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Class SyncPromiseAdapter
|
||||
*
|
||||
* Allows changing order of field resolution even in sync environments
|
||||
* (by leveraging queue of deferreds and promises)
|
||||
*
|
||||
* @package GraphQL\Executor\Promise\Adapter
|
||||
*/
|
||||
class SyncPromiseAdapter implements PromiseAdapter
|
||||
{
|
||||
|
@ -30,20 +33,22 @@ class SyncPromiseAdapter implements PromiseAdapter
|
|||
*/
|
||||
public function convertThenable($thenable)
|
||||
{
|
||||
if (!$thenable instanceof Deferred) {
|
||||
if (! $thenable instanceof Deferred) {
|
||||
throw new InvariantViolation('Expected instance of GraphQL\Deferred, got ' . Utils::printSafe($thenable));
|
||||
}
|
||||
|
||||
return new Promise($thenable->promise, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null)
|
||||
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null)
|
||||
{
|
||||
/** @var SyncPromise $promise */
|
||||
$promise = $promise->adoptedPromise;
|
||||
return new Promise($promise->then($onFulfilled, $onRejected), $this);
|
||||
/** @var SyncPromise $adoptedPromise */
|
||||
$adoptedPromise = $promise->adoptedPromise;
|
||||
|
||||
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,12 +60,18 @@ class SyncPromiseAdapter implements PromiseAdapter
|
|||
|
||||
try {
|
||||
$resolver(
|
||||
[$promise, 'resolve'],
|
||||
[$promise, 'reject']
|
||||
[
|
||||
$promise,
|
||||
'resolve',
|
||||
],
|
||||
[
|
||||
$promise,
|
||||
'reject',
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$promise->reject($e);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
|
||||
|
@ -73,6 +84,7 @@ class SyncPromiseAdapter implements PromiseAdapter
|
|||
public function createFulfilled($value = null)
|
||||
{
|
||||
$promise = new SyncPromise();
|
||||
|
||||
return new Promise($promise->resolve($value), $this);
|
||||
}
|
||||
|
||||
|
@ -82,6 +94,7 @@ class SyncPromiseAdapter implements PromiseAdapter
|
|||
public function createRejected($reason)
|
||||
{
|
||||
$promise = new SyncPromise();
|
||||
|
||||
return new Promise($promise->reject($reason), $this);
|
||||
}
|
||||
|
||||
|
@ -92,20 +105,22 @@ class SyncPromiseAdapter implements PromiseAdapter
|
|||
{
|
||||
$all = new SyncPromise();
|
||||
|
||||
$total = count($promisesOrValues);
|
||||
$count = 0;
|
||||
$total = count($promisesOrValues);
|
||||
$count = 0;
|
||||
$result = [];
|
||||
|
||||
foreach ($promisesOrValues as $index => $promiseOrValue) {
|
||||
if ($promiseOrValue instanceof Promise) {
|
||||
$result[$index] = null;
|
||||
$promiseOrValue->then(
|
||||
function($value) use ($index, &$count, $total, &$result, $all) {
|
||||
static function ($value) use ($index, &$count, $total, &$result, $all) {
|
||||
$result[$index] = $value;
|
||||
$count++;
|
||||
if ($count >= $total) {
|
||||
$all->resolve($result);
|
||||
if ($count < $total) {
|
||||
return;
|
||||
}
|
||||
|
||||
$all->resolve($result);
|
||||
},
|
||||
[$all, 'reject']
|
||||
);
|
||||
|
@ -117,24 +132,23 @@ class SyncPromiseAdapter implements PromiseAdapter
|
|||
if ($count === $total) {
|
||||
$all->resolve($result);
|
||||
}
|
||||
|
||||
return new Promise($all, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously wait when promise completes
|
||||
*
|
||||
* @param Promise $promise
|
||||
* @return mixed
|
||||
* @return ExecutionResult
|
||||
*/
|
||||
public function wait(Promise $promise)
|
||||
{
|
||||
$this->beforeWait($promise);
|
||||
$dfdQueue = Deferred::getQueue();
|
||||
$dfdQueue = Deferred::getQueue();
|
||||
$promiseQueue = SyncPromise::getQueue();
|
||||
|
||||
while (
|
||||
$promise->adoptedPromise->state === SyncPromise::PENDING &&
|
||||
!($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
|
||||
while ($promise->adoptedPromise->state === SyncPromise::PENDING &&
|
||||
! ($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
|
||||
) {
|
||||
Deferred::runQueue();
|
||||
SyncPromise::runQueue();
|
||||
|
@ -146,17 +160,17 @@ class SyncPromiseAdapter implements PromiseAdapter
|
|||
|
||||
if ($syncPromise->state === SyncPromise::FULFILLED) {
|
||||
return $syncPromise->result;
|
||||
} else if ($syncPromise->state === SyncPromise::REJECTED) {
|
||||
}
|
||||
|
||||
if ($syncPromise->state === SyncPromise::REJECTED) {
|
||||
throw $syncPromise->result;
|
||||
}
|
||||
|
||||
throw new InvariantViolation("Could not resolve promise");
|
||||
throw new InvariantViolation('Could not resolve promise');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute just before starting to run promise completion
|
||||
*
|
||||
* @param Promise $promise
|
||||
*/
|
||||
protected function beforeWait(Promise $promise)
|
||||
{
|
||||
|
@ -164,8 +178,6 @@ class SyncPromiseAdapter implements PromiseAdapter
|
|||
|
||||
/**
|
||||
* Execute while running promise completion
|
||||
*
|
||||
* @param Promise $promise
|
||||
*/
|
||||
protected function onWait(Promise $promise)
|
||||
{
|
||||
|
|
|
@ -1,38 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Executor\Promise;
|
||||
|
||||
use GraphQL\Executor\Promise\Adapter\SyncPromise;
|
||||
use GraphQL\Utils\Utils;
|
||||
use React\Promise\Promise as ReactPromise;
|
||||
|
||||
/**
|
||||
* Convenience wrapper for promises represented by Promise Adapter
|
||||
*/
|
||||
class Promise
|
||||
{
|
||||
private $adapter;
|
||||
|
||||
/** @var SyncPromise|ReactPromise */
|
||||
public $adoptedPromise;
|
||||
|
||||
/** @var PromiseAdapter */
|
||||
private $adapter;
|
||||
|
||||
/**
|
||||
* Promise constructor.
|
||||
*
|
||||
* @param mixed $adoptedPromise
|
||||
* @param PromiseAdapter $adapter
|
||||
*/
|
||||
public function __construct($adoptedPromise, PromiseAdapter $adapter)
|
||||
{
|
||||
Utils::invariant(!$adoptedPromise instanceof self, 'Expecting promise from adapted system, got ' . __CLASS__);
|
||||
Utils::invariant(! $adoptedPromise instanceof self, 'Expecting promise from adapted system, got ' . self::class);
|
||||
|
||||
$this->adapter = $adapter;
|
||||
$this->adapter = $adapter;
|
||||
$this->adoptedPromise = $adoptedPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|null $onFulfilled
|
||||
* @param callable|null $onRejected
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function then(callable $onFulfilled = null, callable $onRejected = null)
|
||||
public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
|
||||
{
|
||||
return $this->adapter->then($this, $onFulfilled, $onRejected);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Executor\Promise;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Provides a means for integration of async PHP platforms ([related docs](data-fetching.md#async-php))
|
||||
*/
|
||||
|
@ -9,18 +14,22 @@ interface PromiseAdapter
|
|||
/**
|
||||
* Return true if the value is a promise or a deferred of the underlying platform
|
||||
*
|
||||
* @api
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function isThenable($value);
|
||||
|
||||
/**
|
||||
* Converts thenable of the underlying platform into GraphQL\Executor\Promise\Promise instance
|
||||
*
|
||||
* @api
|
||||
* @param object $thenable
|
||||
*
|
||||
* @return Promise
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function convertThenable($thenable);
|
||||
|
||||
|
@ -28,14 +37,11 @@ interface PromiseAdapter
|
|||
* Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described
|
||||
* in Promises/A+ specs. Then returns new wrapped instance of GraphQL\Executor\Promise\Promise.
|
||||
*
|
||||
* @api
|
||||
* @param Promise $promise
|
||||
* @param callable|null $onFulfilled
|
||||
* @param callable|null $onRejected
|
||||
*
|
||||
* @return Promise
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null);
|
||||
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null);
|
||||
|
||||
/**
|
||||
* Creates a Promise
|
||||
|
@ -43,18 +49,20 @@ interface PromiseAdapter
|
|||
* Expected resolver signature:
|
||||
* function(callable $resolve, callable $reject)
|
||||
*
|
||||
* @api
|
||||
* @param callable $resolver
|
||||
* @return Promise
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function create(callable $resolver);
|
||||
|
||||
/**
|
||||
* Creates a fulfilled Promise for a value if the value is not a promise.
|
||||
*
|
||||
* @api
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return Promise
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function createFulfilled($value = null);
|
||||
|
||||
|
@ -62,9 +70,11 @@ interface PromiseAdapter
|
|||
* Creates a rejected promise for a reason if the reason is not a promise. If
|
||||
* the provided reason is a promise, then it is returned as-is.
|
||||
*
|
||||
* @api
|
||||
* @param \Throwable $reason
|
||||
* @param Throwable $reason
|
||||
*
|
||||
* @return Promise
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function createRejected($reason);
|
||||
|
||||
|
@ -72,9 +82,11 @@ interface PromiseAdapter
|
|||
* Given an array of promises (or values), returns a promise that is fulfilled when all the
|
||||
* items in the array are fulfilled.
|
||||
*
|
||||
* @api
|
||||
* @param array $promisesOrValues Promises or values.
|
||||
* @param Promise[]|mixed[] $promisesOrValues Promises or values.
|
||||
*
|
||||
* @return Promise
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function all(array $promisesOrValues);
|
||||
}
|
||||
|
|
1361
src/Executor/ReferenceExecutor.php
Normal file
1361
src/Executor/ReferenceExecutor.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Executor;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
|
@ -9,20 +12,27 @@ use GraphQL\Language\AST\FieldDefinitionNode;
|
|||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||
use GraphQL\Language\AST\InlineFragmentNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeList;
|
||||
use GraphQL\Language\AST\VariableNode;
|
||||
use GraphQL\Language\AST\ValueNode;
|
||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||
use GraphQL\Language\AST\VariableNode;
|
||||
use GraphQL\Language\Printer;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\FieldDefinition;
|
||||
use GraphQL\Type\Definition\InputType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Utils\AST;
|
||||
use GraphQL\Utils\TypeInfo;
|
||||
use GraphQL\Utils\Utils;
|
||||
use GraphQL\Utils\Value;
|
||||
use stdClass;
|
||||
use Throwable;
|
||||
use function array_key_exists;
|
||||
use function array_map;
|
||||
use function sprintf;
|
||||
|
||||
class Values
|
||||
{
|
||||
|
@ -31,46 +41,36 @@ class Values
|
|||
* variable definitions and arbitrary input. If the input cannot be coerced
|
||||
* to match the variable definitions, a Error will be thrown.
|
||||
*
|
||||
* @param Schema $schema
|
||||
* @param VariableDefinitionNode[] $varDefNodes
|
||||
* @param array $inputs
|
||||
* @return array
|
||||
* @param mixed[] $inputs
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public static function getVariableValues(Schema $schema, $varDefNodes, array $inputs)
|
||||
{
|
||||
$errors = [];
|
||||
$errors = [];
|
||||
$coercedValues = [];
|
||||
foreach ($varDefNodes as $varDefNode) {
|
||||
$varName = $varDefNode->variable->name->value;
|
||||
/** @var InputType|Type $varType */
|
||||
$varType = TypeInfo::typeFromAST($schema, $varDefNode->type);
|
||||
|
||||
if (!Type::isInputType($varType)) {
|
||||
$errors[] = new Error(
|
||||
"Variable \"\$$varName\" expected value of type " .
|
||||
'"' . Printer::doPrint($varDefNode->type) . '" which cannot be used as an input type.',
|
||||
[$varDefNode->type]
|
||||
);
|
||||
} else {
|
||||
if (!array_key_exists($varName, $inputs)) {
|
||||
if ($varType instanceof NonNull) {
|
||||
$errors[] = new Error(
|
||||
"Variable \"\$$varName\" of required type " .
|
||||
"\"{$varType}\" was not provided.",
|
||||
[$varDefNode]
|
||||
);
|
||||
} else if ($varDefNode->defaultValue) {
|
||||
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
|
||||
}
|
||||
} else {
|
||||
$value = $inputs[$varName];
|
||||
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 ($coercionErrors) {
|
||||
$messagePrelude = "Variable \"\$$varName\" got invalid value " . Utils::printSafeJson($value) . '; ';
|
||||
if (empty($coercionErrors)) {
|
||||
$coercedValues[$varName] = $coerced['value'];
|
||||
} else {
|
||||
$messagePrelude = sprintf(
|
||||
'Variable "$%s" got invalid value %s; ',
|
||||
$varName,
|
||||
Utils::printSafeJson($value)
|
||||
);
|
||||
|
||||
foreach($coercionErrors as $error) {
|
||||
foreach ($coercionErrors as $error) {
|
||||
$errors[] = new Error(
|
||||
$messagePrelude . $error->getMessage(),
|
||||
$error->getNodes(),
|
||||
|
@ -81,90 +81,38 @@ class Values
|
|||
$error->getExtensions()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$coercedValues[$varName] = $coerced['value'];
|
||||
}
|
||||
} else {
|
||||
if ($varType instanceof NonNull) {
|
||||
$errors[] = new Error(
|
||||
sprintf(
|
||||
'Variable "$%s" of required type "%s" was not provided.',
|
||||
$varName,
|
||||
$varType
|
||||
),
|
||||
[$varDefNode]
|
||||
);
|
||||
} elseif ($varDefNode->defaultValue !== null) {
|
||||
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ['errors' => $errors, 'coerced' => $errors ? null : $coercedValues];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an object map of argument values given a list of argument
|
||||
* definitions and list of argument AST nodes.
|
||||
*
|
||||
* @param FieldDefinition|Directive $def
|
||||
* @param FieldNode|\GraphQL\Language\AST\DirectiveNode $node
|
||||
* @param $variableValues
|
||||
* @return array
|
||||
* @throws Error
|
||||
*/
|
||||
public static function getArgumentValues($def, $node, $variableValues = null)
|
||||
{
|
||||
$argDefs = $def->args;
|
||||
$argNodes = $node->arguments;
|
||||
|
||||
if (!$argDefs || null === $argNodes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$coercedValues = [];
|
||||
|
||||
/** @var ArgumentNode[] $argNodeMap */
|
||||
$argNodeMap = $argNodes ? Utils::keyMap($argNodes, function (ArgumentNode $arg) {
|
||||
return $arg->name->value;
|
||||
}) : [];
|
||||
|
||||
foreach ($argDefs as $argDef) {
|
||||
$name = $argDef->name;
|
||||
$argType = $argDef->getType();
|
||||
$argumentNode = isset($argNodeMap[$name]) ? $argNodeMap[$name] : null;
|
||||
|
||||
if (!$argumentNode) {
|
||||
if ($argDef->defaultValueExists()) {
|
||||
$coercedValues[$name] = $argDef->defaultValue;
|
||||
} else if ($argType instanceof NonNull) {
|
||||
throw new Error(
|
||||
'Argument "' . $name . '" of required type ' .
|
||||
'"' . Utils::printSafe($argType) . '" was not provided.',
|
||||
[$node]
|
||||
);
|
||||
}
|
||||
} else if ($argumentNode->value instanceof VariableNode) {
|
||||
$variableName = $argumentNode->value->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];
|
||||
} else if ($argDef->defaultValueExists()) {
|
||||
$coercedValues[$name] = $argDef->defaultValue;
|
||||
} else if ($argType instanceof NonNull) {
|
||||
throw new Error(
|
||||
'Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' .
|
||||
'provided the variable "$' . $variableName . '" which was not provided ' .
|
||||
'a runtime value.',
|
||||
[ $argumentNode->value ]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$valueNode = $argumentNode->value;
|
||||
$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) . '.',
|
||||
[ $argumentNode->value ]
|
||||
);
|
||||
}
|
||||
$coercedValues[$name] = $coercedValue;
|
||||
$errors[] = new Error(
|
||||
sprintf(
|
||||
'Variable "$%s" expected value of type "%s" which cannot be used as an input type.',
|
||||
$varName,
|
||||
Printer::doPrint($varDefNode->type)
|
||||
),
|
||||
[$varDefNode->type]
|
||||
);
|
||||
}
|
||||
}
|
||||
return $coercedValues;
|
||||
|
||||
if (! empty($errors)) {
|
||||
return [$errors, null];
|
||||
}
|
||||
|
||||
return [null, $coercedValues];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -174,50 +122,158 @@ class Values
|
|||
*
|
||||
* If the directive does not exist on the node, returns undefined.
|
||||
*
|
||||
* @param Directive $directiveDef
|
||||
* @param FragmentSpreadNode | FieldNode | InlineFragmentNode | EnumValueDefinitionNode | FieldDefinitionNode $node
|
||||
* @param array|null $variableValues
|
||||
* @param FragmentSpreadNode|FieldNode|InlineFragmentNode|EnumValueDefinitionNode|FieldDefinitionNode $node
|
||||
* @param mixed[]|null $variableValues
|
||||
*
|
||||
* @return array|null
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
public static function getDirectiveValues(Directive $directiveDef, $node, $variableValues = null)
|
||||
{
|
||||
if (isset($node->directives) && $node->directives instanceof NodeList) {
|
||||
$directiveNode = Utils::find($node->directives, function(DirectiveNode $directive) use ($directiveDef) {
|
||||
return $directive->name->value === $directiveDef->name;
|
||||
});
|
||||
$directiveNode = Utils::find(
|
||||
$node->directives,
|
||||
static function (DirectiveNode $directive) use ($directiveDef) {
|
||||
return $directive->name->value === $directiveDef->name;
|
||||
}
|
||||
);
|
||||
|
||||
if ($directiveNode) {
|
||||
if ($directiveNode !== null) {
|
||||
return self::getArgumentValues($directiveDef, $directiveNode, $variableValues);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an object map of argument values given a list of argument
|
||||
* definitions and list of argument AST nodes.
|
||||
*
|
||||
* @param FieldDefinition|Directive $def
|
||||
* @param FieldNode|DirectiveNode $node
|
||||
* @param mixed[] $variableValues
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
public static function getArgumentValues($def, $node, $variableValues = null)
|
||||
{
|
||||
if (empty($def->args)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$argumentNodes = $node->arguments;
|
||||
if (empty($argumentNodes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$argumentValueMap = [];
|
||||
foreach ($argumentNodes as $argumentNode) {
|
||||
$argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
|
||||
}
|
||||
|
||||
return static::getArgumentValuesForMap($def, $argumentValueMap, $variableValues, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FieldDefinition|Directive $fieldDefinition
|
||||
* @param ArgumentNode[] $argumentValueMap
|
||||
* @param mixed[] $variableValues
|
||||
* @param Node|null $referenceNode
|
||||
*
|
||||
* @return mixed[]
|
||||
*
|
||||
* @throws Error
|
||||
*/
|
||||
public static function getArgumentValuesForMap($fieldDefinition, $argumentValueMap, $variableValues = null, $referenceNode = null)
|
||||
{
|
||||
$argumentDefinitions = $fieldDefinition->args;
|
||||
$coercedValues = [];
|
||||
|
||||
foreach ($argumentDefinitions as $argumentDefinition) {
|
||||
$name = $argumentDefinition->name;
|
||||
$argType = $argumentDefinition->getType();
|
||||
$argumentValueNode = $argumentValueMap[$name] ?? null;
|
||||
|
||||
if ($argumentValueNode === null) {
|
||||
if ($argumentDefinition->defaultValueExists()) {
|
||||
$coercedValues[$name] = $argumentDefinition->defaultValue;
|
||||
} elseif ($argType instanceof NonNull) {
|
||||
throw new Error(
|
||||
'Argument "' . $name . '" of required type ' .
|
||||
'"' . Utils::printSafe($argType) . '" was not provided.',
|
||||
$referenceNode
|
||||
);
|
||||
}
|
||||
} elseif ($argumentValueNode instanceof VariableNode) {
|
||||
$variableName = $argumentValueNode->name->value;
|
||||
|
||||
if ($variableValues !== null && array_key_exists($variableName, $variableValues)) {
|
||||
// Note: this does not check that this variable value is correct.
|
||||
// This assumes that this query has been validated and the variable
|
||||
// usage here is of the correct type.
|
||||
$coercedValues[$name] = $variableValues[$variableName];
|
||||
} elseif ($argumentDefinition->defaultValueExists()) {
|
||||
$coercedValues[$name] = $argumentDefinition->defaultValue;
|
||||
} elseif ($argType instanceof NonNull) {
|
||||
throw new Error(
|
||||
'Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' .
|
||||
'provided the variable "$' . $variableName . '" which was not provided ' .
|
||||
'a runtime value.',
|
||||
[$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]
|
||||
);
|
||||
}
|
||||
$coercedValues[$name] = $coercedValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $coercedValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated as of 8.0 (Moved to \GraphQL\Utils\AST::valueFromAST)
|
||||
*
|
||||
* @param $valueNode
|
||||
* @param InputType $type
|
||||
* @param null $variables
|
||||
* @return array|null|\stdClass
|
||||
* @param ValueNode $valueNode
|
||||
* @param mixed[]|null $variables
|
||||
*
|
||||
* @return mixed[]|stdClass|null
|
||||
*/
|
||||
public static function valueFromAST($valueNode, InputType $type, $variables = null)
|
||||
public static function valueFromAST($valueNode, InputType $type, ?array $variables = null)
|
||||
{
|
||||
return AST::valueFromAST($valueNode, $type, $variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated as of 0.12 (Use coerceValue() directly for richer information)
|
||||
* @param $value
|
||||
* @param InputType $type
|
||||
* @return array
|
||||
*
|
||||
* @param mixed[] $value
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function isValidPHPValue($value, InputType $type)
|
||||
{
|
||||
$errors = Value::coerceValue($value, $type)['errors'];
|
||||
|
||||
return $errors
|
||||
? array_map(function(/*\Throwable */$error) { return $error->getMessage(); }, $errors)
|
||||
? array_map(
|
||||
static function (Throwable $error) {
|
||||
return $error->getMessage();
|
||||
},
|
||||
$errors
|
||||
)
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
|
283
src/Experimental/Executor/Collector.php
Normal file
283
src/Experimental/Executor/Collector.php
Normal file
|
@ -0,0 +1,283 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Experimental\Executor;
|
||||
|
||||
use Generator;
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\DefinitionNode;
|
||||
use GraphQL\Language\AST\DocumentNode;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||
use GraphQL\Language\AST\InlineFragmentNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeKind;
|
||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||
use GraphQL\Language\AST\SelectionSetNode;
|
||||
use GraphQL\Language\AST\ValueNode;
|
||||
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 sprintf;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Collector
|
||||
{
|
||||
/** @var Schema */
|
||||
private $schema;
|
||||
|
||||
/** @var Runtime */
|
||||
private $runtime;
|
||||
|
||||
/** @var OperationDefinitionNode|null */
|
||||
public $operation = null;
|
||||
|
||||
/** @var FragmentDefinitionNode[] */
|
||||
public $fragments = [];
|
||||
|
||||
/** @var ObjectType|null */
|
||||
public $rootType;
|
||||
|
||||
/** @var FieldNode[][] */
|
||||
private $fields;
|
||||
|
||||
/** @var string[] */
|
||||
private $visitedFragments;
|
||||
|
||||
public function __construct(Schema $schema, Runtime $runtime)
|
||||
{
|
||||
$this->schema = $schema;
|
||||
$this->runtime = $runtime;
|
||||
}
|
||||
|
||||
public function initialize(DocumentNode $documentNode, ?string $operationName = null)
|
||||
{
|
||||
$hasMultipleAssumedOperations = false;
|
||||
|
||||
foreach ($documentNode->definitions as $definitionNode) {
|
||||
/** @var DefinitionNode|Node $definitionNode */
|
||||
|
||||
if ($definitionNode instanceof OperationDefinitionNode) {
|
||||
/** @var OperationDefinitionNode $definitionNode */
|
||||
if ($operationName === null && $this->operation !== null) {
|
||||
$hasMultipleAssumedOperations = true;
|
||||
}
|
||||
if ($operationName === null ||
|
||||
(isset($definitionNode->name) && $definitionNode->name->value === $operationName)
|
||||
) {
|
||||
$this->operation = $definitionNode;
|
||||
}
|
||||
} elseif ($definitionNode instanceof FragmentDefinitionNode) {
|
||||
/** @var FragmentDefinitionNode $definitionNode */
|
||||
$this->fragments[$definitionNode->name->value] = $definitionNode;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->operation === null) {
|
||||
if ($operationName !== null) {
|
||||
$this->runtime->addError(new Error(sprintf('Unknown operation named "%s".', $operationName)));
|
||||
} else {
|
||||
$this->runtime->addError(new Error('Must provide an operation.'));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($hasMultipleAssumedOperations) {
|
||||
$this->runtime->addError(new Error('Must provide operation name if query contains multiple operations.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->operation->operation === 'query') {
|
||||
$this->rootType = $this->schema->getQueryType();
|
||||
} elseif ($this->operation->operation === 'mutation') {
|
||||
$this->rootType = $this->schema->getMutationType();
|
||||
} elseif ($this->operation->operation === 'subscription') {
|
||||
$this->rootType = $this->schema->getSubscriptionType();
|
||||
} else {
|
||||
$this->runtime->addError(new Error(sprintf('Cannot initialize collector with operation type "%s".', $this->operation->operation)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Generator
|
||||
*/
|
||||
public function collectFields(ObjectType $runtimeType, ?SelectionSetNode $selectionSet)
|
||||
{
|
||||
$this->fields = [];
|
||||
$this->visitedFragments = [];
|
||||
|
||||
$this->doCollectFields($runtimeType, $selectionSet);
|
||||
|
||||
foreach ($this->fields as $resultName => $fieldNodes) {
|
||||
$fieldNode = $fieldNodes[0];
|
||||
$fieldName = $fieldNode->name->value;
|
||||
|
||||
$argumentValueMap = null;
|
||||
if (! empty($fieldNode->arguments)) {
|
||||
foreach ($fieldNode->arguments as $argumentNode) {
|
||||
$argumentValueMap = $argumentValueMap ?? [];
|
||||
$argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fieldName !== Introspection::TYPE_NAME_FIELD_NAME &&
|
||||
! ($runtimeType === $this->schema->getQueryType() && ($fieldName === Introspection::SCHEMA_FIELD_NAME || $fieldName === Introspection::TYPE_FIELD_NAME)) &&
|
||||
! $runtimeType->hasField($fieldName)
|
||||
) {
|
||||
// do not emit error
|
||||
continue;
|
||||
}
|
||||
|
||||
yield new CoroutineContextShared($fieldNodes, $fieldName, $resultName, $argumentValueMap);
|
||||
}
|
||||
}
|
||||
|
||||
private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $selectionSet)
|
||||
{
|
||||
if ($selectionSet === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($selectionSet->selections as $selection) {
|
||||
/** @var FieldNode|FragmentSpreadNode|InlineFragmentNode $selection */
|
||||
|
||||
if (! empty($selection->directives)) {
|
||||
foreach ($selection->directives as $directiveNode) {
|
||||
if ($directiveNode->name->value === Directive::SKIP_NAME) {
|
||||
/** @var ValueNode|null $condition */
|
||||
$condition = null;
|
||||
foreach ($directiveNode->arguments as $argumentNode) {
|
||||
if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
|
||||
$condition = $argumentNode->value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($condition === null) {
|
||||
$this->runtime->addError(new Error(
|
||||
sprintf('@%s directive is missing "%s" argument.', Directive::SKIP_NAME, Directive::IF_ARGUMENT_NAME),
|
||||
$selection
|
||||
));
|
||||
} else {
|
||||
if ($this->runtime->evaluate($condition, Type::boolean()) === true) {
|
||||
continue 2; // !!! advances outer loop
|
||||
}
|
||||
}
|
||||
} elseif ($directiveNode->name->value === Directive::INCLUDE_NAME) {
|
||||
/** @var ValueNode|null $condition */
|
||||
$condition = null;
|
||||
foreach ($directiveNode->arguments as $argumentNode) {
|
||||
if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
|
||||
$condition = $argumentNode->value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($condition === null) {
|
||||
$this->runtime->addError(new Error(
|
||||
sprintf('@%s directive is missing "%s" argument.', Directive::INCLUDE_NAME, Directive::IF_ARGUMENT_NAME),
|
||||
$selection
|
||||
));
|
||||
} else {
|
||||
if ($this->runtime->evaluate($condition, Type::boolean()) !== true) {
|
||||
continue 2; // !!! advances outer loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($selection instanceof FieldNode) {
|
||||
/** @var FieldNode $selection */
|
||||
|
||||
$resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value;
|
||||
|
||||
if (! isset($this->fields[$resultName])) {
|
||||
$this->fields[$resultName] = [];
|
||||
}
|
||||
|
||||
$this->fields[$resultName][] = $selection;
|
||||
} elseif ($selection instanceof FragmentSpreadNode) {
|
||||
/** @var FragmentSpreadNode $selection */
|
||||
|
||||
$fragmentName = $selection->name->value;
|
||||
|
||||
if (isset($this->visitedFragments[$fragmentName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! isset($this->fragments[$fragmentName])) {
|
||||
$this->runtime->addError(new Error(
|
||||
sprintf('Fragment "%s" does not exist.', $fragmentName),
|
||||
$selection
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->visitedFragments[$fragmentName] = true;
|
||||
|
||||
$fragmentDefinition = $this->fragments[$fragmentName];
|
||||
$conditionTypeName = $fragmentDefinition->typeCondition->name->value;
|
||||
|
||||
if (! $this->schema->hasType($conditionTypeName)) {
|
||||
$this->runtime->addError(new Error(
|
||||
sprintf('Cannot spread fragment "%s", type "%s" does not exist.', $fragmentName, $conditionTypeName),
|
||||
$selection
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
$conditionType = $this->schema->getType($conditionTypeName);
|
||||
|
||||
if ($conditionType instanceof ObjectType) {
|
||||
if ($runtimeType->name !== $conditionType->name) {
|
||||
continue;
|
||||
}
|
||||
} elseif ($conditionType instanceof AbstractType) {
|
||||
if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet);
|
||||
} elseif ($selection instanceof InlineFragmentNode) {
|
||||
/** @var InlineFragmentNode $selection */
|
||||
|
||||
if ($selection->typeCondition !== null) {
|
||||
$conditionTypeName = $selection->typeCondition->name->value;
|
||||
|
||||
if (! $this->schema->hasType($conditionTypeName)) {
|
||||
$this->runtime->addError(new Error(
|
||||
sprintf('Cannot spread inline fragment, type "%s" does not exist.', $conditionTypeName),
|
||||
$selection
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
$conditionType = $this->schema->getType($conditionTypeName);
|
||||
|
||||
if ($conditionType instanceof ObjectType) {
|
||||
if ($runtimeType->name !== $conditionType->name) {
|
||||
continue;
|
||||
}
|
||||
} elseif ($conditionType instanceof AbstractType) {
|
||||
if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->doCollectFields($runtimeType, $selection->selectionSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
57
src/Experimental/Executor/CoroutineContext.php
Normal file
57
src/Experimental/Executor/CoroutineContext.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Experimental\Executor;
|
||||
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class CoroutineContext
|
||||
{
|
||||
/** @var CoroutineContextShared */
|
||||
public $shared;
|
||||
|
||||
/** @var ObjectType */
|
||||
public $type;
|
||||
|
||||
/** @var mixed */
|
||||
public $value;
|
||||
|
||||
/** @var object */
|
||||
public $result;
|
||||
|
||||
/** @var string[] */
|
||||
public $path;
|
||||
|
||||
/** @var ResolveInfo|null */
|
||||
public $resolveInfo;
|
||||
|
||||
/** @var string[]|null */
|
||||
public $nullFence;
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param object $result
|
||||
* @param string[] $path
|
||||
* @param string[]|null $nullFence
|
||||
*/
|
||||
public function __construct(
|
||||
CoroutineContextShared $shared,
|
||||
ObjectType $type,
|
||||
$value,
|
||||
$result,
|
||||
array $path,
|
||||
?array $nullFence = null
|
||||
) {
|
||||
$this->shared = $shared;
|
||||
$this->type = $type;
|
||||
$this->value = $value;
|
||||
$this->result = $result;
|
||||
$this->path = $path;
|
||||
$this->nullFence = $nullFence;
|
||||
}
|
||||
}
|
62
src/Experimental/Executor/CoroutineContextShared.php
Normal file
62
src/Experimental/Executor/CoroutineContextShared.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Experimental\Executor;
|
||||
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\SelectionSetNode;
|
||||
use GraphQL\Language\AST\ValueNode;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class CoroutineContextShared
|
||||
{
|
||||
/** @var FieldNode[] */
|
||||
public $fieldNodes;
|
||||
|
||||
/** @var string */
|
||||
public $fieldName;
|
||||
|
||||
/** @var string */
|
||||
public $resultName;
|
||||
|
||||
/** @var ValueNode[]|null */
|
||||
public $argumentValueMap;
|
||||
|
||||
/** @var SelectionSetNode|null */
|
||||
public $mergedSelectionSet;
|
||||
|
||||
/** @var ObjectType|null */
|
||||
public $typeGuard1;
|
||||
|
||||
/** @var callable|null */
|
||||
public $resolveIfType1;
|
||||
|
||||
/** @var mixed */
|
||||
public $argumentsIfType1;
|
||||
|
||||
/** @var ResolveInfo|null */
|
||||
public $resolveInfoIfType1;
|
||||
|
||||
/** @var ObjectType|null */
|
||||
public $typeGuard2;
|
||||
|
||||
/** @var CoroutineContext[]|null */
|
||||
public $childContextsIfType2;
|
||||
|
||||
/**
|
||||
* @param FieldNode[] $fieldNodes
|
||||
* @param mixed[]|null $argumentValueMap
|
||||
*/
|
||||
public function __construct(array $fieldNodes, string $fieldName, string $resultName, ?array $argumentValueMap)
|
||||
{
|
||||
$this->fieldNodes = $fieldNodes;
|
||||
$this->fieldName = $fieldName;
|
||||
$this->resultName = $resultName;
|
||||
$this->argumentValueMap = $argumentValueMap;
|
||||
}
|
||||
}
|
952
src/Experimental/Executor/CoroutineExecutor.php
Normal file
952
src/Experimental/Executor/CoroutineExecutor.php
Normal file
|
@ -0,0 +1,952 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Experimental\Executor;
|
||||
|
||||
use Generator;
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Error\Warning;
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\Executor\ExecutorImplementation;
|
||||
use GraphQL\Executor\Promise\Promise;
|
||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||
use GraphQL\Executor\Values;
|
||||
use GraphQL\Language\AST\DocumentNode;
|
||||
use GraphQL\Language\AST\SelectionSetNode;
|
||||
use GraphQL\Language\AST\ValueNode;
|
||||
use GraphQL\Type\Definition\AbstractType;
|
||||
use GraphQL\Type\Definition\CompositeType;
|
||||
use GraphQL\Type\Definition\InputType;
|
||||
use GraphQL\Type\Definition\InterfaceType;
|
||||
use GraphQL\Type\Definition\LeafType;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use GraphQL\Type\Introspection;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Utils\AST;
|
||||
use GraphQL\Utils\Utils;
|
||||
use SplQueue;
|
||||
use stdClass;
|
||||
use Throwable;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
use function sprintf;
|
||||
|
||||
class CoroutineExecutor implements Runtime, ExecutorImplementation
|
||||
{
|
||||
/** @var object */
|
||||
private static $undefined;
|
||||
|
||||
/** @var Schema */
|
||||
private $schema;
|
||||
|
||||
/** @var callable */
|
||||
private $fieldResolver;
|
||||
|
||||
/** @var PromiseAdapter */
|
||||
private $promiseAdapter;
|
||||
|
||||
/** @var mixed|null */
|
||||
private $rootValue;
|
||||
|
||||
/** @var mixed|null */
|
||||
private $contextValue;
|
||||
|
||||
/** @var mixed|null */
|
||||
private $rawVariableValues;
|
||||
|
||||
/** @var mixed|null */
|
||||
private $variableValues;
|
||||
|
||||
/** @var DocumentNode */
|
||||
private $documentNode;
|
||||
|
||||
/** @var string|null */
|
||||
private $operationName;
|
||||
|
||||
/** @var Collector */
|
||||
private $collector;
|
||||
|
||||
/** @var Error[] */
|
||||
private $errors;
|
||||
|
||||
/** @var SplQueue */
|
||||
private $queue;
|
||||
|
||||
/** @var SplQueue */
|
||||
private $schedule;
|
||||
|
||||
/** @var stdClass */
|
||||
private $rootResult;
|
||||
|
||||
/** @var int */
|
||||
private $pending;
|
||||
|
||||
/** @var callable */
|
||||
private $doResolve;
|
||||
|
||||
public function __construct(
|
||||
PromiseAdapter $promiseAdapter,
|
||||
Schema $schema,
|
||||
DocumentNode $documentNode,
|
||||
$rootValue,
|
||||
$contextValue,
|
||||
$rawVariableValues,
|
||||
?string $operationName,
|
||||
callable $fieldResolver
|
||||
) {
|
||||
if (self::$undefined === null) {
|
||||
self::$undefined = Utils::undefined();
|
||||
}
|
||||
|
||||
$this->schema = $schema;
|
||||
$this->fieldResolver = $fieldResolver;
|
||||
$this->promiseAdapter = $promiseAdapter;
|
||||
$this->rootValue = $rootValue;
|
||||
$this->contextValue = $contextValue;
|
||||
$this->rawVariableValues = $rawVariableValues;
|
||||
$this->documentNode = $documentNode;
|
||||
$this->operationName = $operationName;
|
||||
}
|
||||
|
||||
public static function create(
|
||||
PromiseAdapter $promiseAdapter,
|
||||
Schema $schema,
|
||||
DocumentNode $documentNode,
|
||||
$rootValue,
|
||||
$contextValue,
|
||||
$variableValues,
|
||||
?string $operationName,
|
||||
callable $fieldResolver
|
||||
) {
|
||||
return new static(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$documentNode,
|
||||
$rootValue,
|
||||
$contextValue,
|
||||
$variableValues,
|
||||
$operationName,
|
||||
$fieldResolver
|
||||
);
|
||||
}
|
||||
|
||||
private static function resultToArray($value, $emptyObjectAsStdClass = true)
|
||||
{
|
||||
if ($value instanceof stdClass) {
|
||||
$array = [];
|
||||
foreach ($value as $propertyName => $propertyValue) {
|
||||
$array[$propertyName] = self::resultToArray($propertyValue);
|
||||
}
|
||||
if ($emptyObjectAsStdClass && empty($array)) {
|
||||
return new stdClass();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$array = [];
|
||||
foreach ($value as $key => $item) {
|
||||
$array[$key] = self::resultToArray($item);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function doExecute() : Promise
|
||||
{
|
||||
$this->rootResult = new stdClass();
|
||||
$this->errors = [];
|
||||
$this->queue = new SplQueue();
|
||||
$this->schedule = new SplQueue();
|
||||
$this->pending = 0;
|
||||
|
||||
$this->collector = new Collector($this->schema, $this);
|
||||
$this->collector->initialize($this->documentNode, $this->operationName);
|
||||
|
||||
if (! empty($this->errors)) {
|
||||
return $this->promiseAdapter->createFulfilled($this->finishExecute(null, $this->errors));
|
||||
}
|
||||
|
||||
[$errors, $coercedVariableValues] = Values::getVariableValues(
|
||||
$this->schema,
|
||||
$this->collector->operation->variableDefinitions ?: [],
|
||||
$this->rawVariableValues ?: []
|
||||
);
|
||||
|
||||
if (! empty($errors)) {
|
||||
return $this->promiseAdapter->createFulfilled($this->finishExecute(null, $errors));
|
||||
}
|
||||
|
||||
$this->variableValues = $coercedVariableValues;
|
||||
|
||||
foreach ($this->collector->collectFields($this->collector->rootType, $this->collector->operation->selectionSet) as $shared) {
|
||||
/** @var CoroutineContextShared $shared */
|
||||
|
||||
// !!! assign to keep object keys sorted
|
||||
$this->rootResult->{$shared->resultName} = null;
|
||||
|
||||
$ctx = new CoroutineContext(
|
||||
$shared,
|
||||
$this->collector->rootType,
|
||||
$this->rootValue,
|
||||
$this->rootResult,
|
||||
[$shared->resultName]
|
||||
);
|
||||
|
||||
$fieldDefinition = $this->findFieldDefinition($ctx);
|
||||
if (! $fieldDefinition->getType() instanceof NonNull) {
|
||||
$ctx->nullFence = [$shared->resultName];
|
||||
}
|
||||
|
||||
if ($this->collector->operation->operation === 'mutation' && ! $this->queue->isEmpty()) {
|
||||
$this->schedule->enqueue($ctx);
|
||||
} else {
|
||||
$this->queue->enqueue(new Strand($this->spawn($ctx)));
|
||||
}
|
||||
}
|
||||
|
||||
$this->run();
|
||||
|
||||
if ($this->pending > 0) {
|
||||
return $this->promiseAdapter->create(function (callable $resolve) {
|
||||
$this->doResolve = $resolve;
|
||||
});
|
||||
}
|
||||
|
||||
return $this->promiseAdapter->createFulfilled($this->finishExecute($this->rootResult, $this->errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object|null $value
|
||||
* @param Error[] $errors
|
||||
*/
|
||||
private function finishExecute($value, array $errors) : ExecutionResult
|
||||
{
|
||||
$this->rootResult = null;
|
||||
$this->errors = null;
|
||||
$this->queue = null;
|
||||
$this->schedule = null;
|
||||
$this->pending = null;
|
||||
$this->collector = null;
|
||||
$this->variableValues = null;
|
||||
|
||||
if ($value !== null) {
|
||||
$value = self::resultToArray($value, false);
|
||||
}
|
||||
|
||||
return new ExecutionResult($value, $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function evaluate(ValueNode $valueNode, InputType $type)
|
||||
{
|
||||
return AST::valueFromAST($valueNode, $type, $this->variableValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function addError($error)
|
||||
{
|
||||
$this->errors[] = $error;
|
||||
}
|
||||
|
||||
private function run()
|
||||
{
|
||||
RUN:
|
||||
while (! $this->queue->isEmpty()) {
|
||||
/** @var Strand $strand */
|
||||
$strand = $this->queue->dequeue();
|
||||
|
||||
try {
|
||||
if ($strand->success !== null) {
|
||||
RESUME:
|
||||
|
||||
if ($strand->success) {
|
||||
$strand->current->send($strand->value);
|
||||
} else {
|
||||
$strand->current->throw($strand->value);
|
||||
}
|
||||
|
||||
$strand->success = null;
|
||||
$strand->value = null;
|
||||
}
|
||||
|
||||
START:
|
||||
if ($strand->current->valid()) {
|
||||
$value = $strand->current->current();
|
||||
|
||||
if ($value instanceof Generator) {
|
||||
$strand->stack[$strand->depth++] = $strand->current;
|
||||
$strand->current = $value;
|
||||
goto START;
|
||||
} elseif ($this->isPromise($value)) {
|
||||
// !!! increment pending before calling ->then() as it may invoke the callback right away
|
||||
++$this->pending;
|
||||
|
||||
if (! $value instanceof Promise) {
|
||||
$value = $this->promiseAdapter->convertThenable($value);
|
||||
}
|
||||
|
||||
$this->promiseAdapter
|
||||
->then(
|
||||
$value,
|
||||
function ($value) use ($strand) {
|
||||
$strand->success = true;
|
||||
$strand->value = $value;
|
||||
$this->queue->enqueue($strand);
|
||||
$this->done();
|
||||
},
|
||||
function (Throwable $throwable) use ($strand) {
|
||||
$strand->success = false;
|
||||
$strand->value = $throwable;
|
||||
$this->queue->enqueue($strand);
|
||||
$this->done();
|
||||
}
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
$strand->success = true;
|
||||
$strand->value = $value;
|
||||
goto RESUME;
|
||||
}
|
||||
}
|
||||
|
||||
$strand->success = true;
|
||||
$strand->value = $strand->current->getReturn();
|
||||
} catch (Throwable $reason) {
|
||||
$strand->success = false;
|
||||
$strand->value = $reason;
|
||||
}
|
||||
|
||||
if ($strand->depth <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$current = &$strand->stack[--$strand->depth];
|
||||
$strand->current = $current;
|
||||
$current = null;
|
||||
goto RESUME;
|
||||
}
|
||||
|
||||
if ($this->pending > 0 || $this->schedule->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var CoroutineContext $ctx */
|
||||
$ctx = $this->schedule->dequeue();
|
||||
$this->queue->enqueue(new Strand($this->spawn($ctx)));
|
||||
goto RUN;
|
||||
}
|
||||
|
||||
private function done()
|
||||
{
|
||||
--$this->pending;
|
||||
|
||||
$this->run();
|
||||
|
||||
if ($this->pending > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$doResolve = $this->doResolve;
|
||||
$doResolve($this->finishExecute($this->rootResult, $this->errors));
|
||||
}
|
||||
|
||||
private function spawn(CoroutineContext $ctx)
|
||||
{
|
||||
// short-circuit evaluation for __typename
|
||||
if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
|
||||
$ctx->result->{$ctx->shared->resultName} = $ctx->type->name;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($ctx->shared->typeGuard1 === $ctx->type) {
|
||||
$resolve = $ctx->shared->resolveIfType1;
|
||||
$ctx->resolveInfo = clone $ctx->shared->resolveInfoIfType1;
|
||||
$ctx->resolveInfo->path = $ctx->path;
|
||||
$arguments = $ctx->shared->argumentsIfType1;
|
||||
$returnType = $ctx->resolveInfo->returnType;
|
||||
} else {
|
||||
$fieldDefinition = $this->findFieldDefinition($ctx);
|
||||
|
||||
if ($fieldDefinition->resolveFn !== null) {
|
||||
$resolve = $fieldDefinition->resolveFn;
|
||||
} elseif ($ctx->type->resolveFieldFn !== null) {
|
||||
$resolve = $ctx->type->resolveFieldFn;
|
||||
} else {
|
||||
$resolve = $this->fieldResolver;
|
||||
}
|
||||
|
||||
$returnType = $fieldDefinition->getType();
|
||||
|
||||
$ctx->resolveInfo = new ResolveInfo(
|
||||
$ctx->shared->fieldName,
|
||||
$ctx->shared->fieldNodes,
|
||||
$returnType,
|
||||
$ctx->type,
|
||||
$ctx->path,
|
||||
$this->schema,
|
||||
$this->collector->fragments,
|
||||
$this->rootValue,
|
||||
$this->collector->operation,
|
||||
$this->variableValues
|
||||
);
|
||||
|
||||
$arguments = Values::getArgumentValuesForMap(
|
||||
$fieldDefinition,
|
||||
$ctx->shared->argumentValueMap,
|
||||
$this->variableValues
|
||||
);
|
||||
|
||||
// !!! assign only in batch when no exception can be thrown in-between
|
||||
$ctx->shared->typeGuard1 = $ctx->type;
|
||||
$ctx->shared->resolveIfType1 = $resolve;
|
||||
$ctx->shared->argumentsIfType1 = $arguments;
|
||||
$ctx->shared->resolveInfoIfType1 = $ctx->resolveInfo;
|
||||
}
|
||||
|
||||
$value = $resolve($ctx->value, $arguments, $this->contextValue, $ctx->resolveInfo);
|
||||
|
||||
if (! $this->completeValueFast($ctx, $returnType, $value, $ctx->path, $returnValue)) {
|
||||
$returnValue = yield $this->completeValue(
|
||||
$ctx,
|
||||
$returnType,
|
||||
$value,
|
||||
$ctx->path,
|
||||
$ctx->nullFence
|
||||
);
|
||||
}
|
||||
} catch (Throwable $reason) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
$reason,
|
||||
$ctx->shared->fieldNodes,
|
||||
$ctx->path
|
||||
));
|
||||
|
||||
$returnValue = self::$undefined;
|
||||
}
|
||||
|
||||
if ($returnValue !== self::$undefined) {
|
||||
$ctx->result->{$ctx->shared->resultName} = $returnValue;
|
||||
} elseif ($ctx->resolveInfo !== null && $ctx->resolveInfo->returnType instanceof NonNull) { // !!! $ctx->resolveInfo might not have been initialized yet
|
||||
$result =& $this->rootResult;
|
||||
foreach ($ctx->nullFence ?? [] as $key) {
|
||||
if (is_string($key)) {
|
||||
$result =& $result->{$key};
|
||||
} else {
|
||||
$result =& $result[$key];
|
||||
}
|
||||
}
|
||||
$result = null;
|
||||
}
|
||||
}
|
||||
|
||||
private function findFieldDefinition(CoroutineContext $ctx)
|
||||
{
|
||||
if ($ctx->shared->fieldName === Introspection::SCHEMA_FIELD_NAME && $ctx->type === $this->schema->getQueryType()) {
|
||||
return Introspection::schemaMetaFieldDef();
|
||||
}
|
||||
|
||||
if ($ctx->shared->fieldName === Introspection::TYPE_FIELD_NAME && $ctx->type === $this->schema->getQueryType()) {
|
||||
return Introspection::typeMetaFieldDef();
|
||||
}
|
||||
|
||||
if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
|
||||
return Introspection::typeNameMetaFieldDef();
|
||||
}
|
||||
|
||||
return $ctx->type->getField($ctx->shared->fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param string[] $path
|
||||
* @param mixed $returnValue
|
||||
*/
|
||||
private function completeValueFast(CoroutineContext $ctx, Type $type, $value, array $path, &$returnValue) : bool
|
||||
{
|
||||
// special handling of Throwable inherited from JS reference implementation, but makes no sense in this PHP
|
||||
if ($this->isPromise($value) || $value instanceof Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$nonNull = false;
|
||||
if ($type instanceof NonNull) {
|
||||
$nonNull = true;
|
||||
$type = $type->getWrappedType();
|
||||
}
|
||||
|
||||
if (! $type instanceof LeafType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($type !== $this->schema->getType($type->name)) {
|
||||
$hint = '';
|
||||
if ($this->schema->getConfig()->typeLoader !== null) {
|
||||
$hint = sprintf(
|
||||
'Make sure that type loader returns the same instance as defined in %s.%s',
|
||||
$ctx->type,
|
||||
$ctx->shared->fieldName
|
||||
);
|
||||
}
|
||||
$this->addError(Error::createLocatedError(
|
||||
new InvariantViolation(
|
||||
sprintf(
|
||||
'Schema must contain unique named types but contains multiple types named "%s". %s ' .
|
||||
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
|
||||
$type->name,
|
||||
$hint
|
||||
)
|
||||
),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
$value = null;
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
$returnValue = null;
|
||||
} else {
|
||||
try {
|
||||
$returnValue = $type->serialize($value);
|
||||
} catch (Throwable $error) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
new InvariantViolation(
|
||||
'Expected a value of type "' . Utils::printSafe($type) . '" but received: ' . Utils::printSafe($value),
|
||||
0,
|
||||
$error
|
||||
),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
$returnValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($nonNull && $returnValue === null) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
new InvariantViolation(sprintf(
|
||||
'Cannot return null for non-nullable field %s.%s.',
|
||||
$ctx->type->name,
|
||||
$ctx->shared->fieldName
|
||||
)),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
$returnValue = self::$undefined;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param string[] $path
|
||||
* @param string[]|null $nullFence
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function completeValue(CoroutineContext $ctx, Type $type, $value, array $path, ?array $nullFence)
|
||||
{
|
||||
$nonNull = false;
|
||||
$returnValue = null;
|
||||
|
||||
if ($type instanceof NonNull) {
|
||||
$nonNull = true;
|
||||
$type = $type->getWrappedType();
|
||||
} else {
|
||||
$nullFence = $path;
|
||||
}
|
||||
|
||||
// !!! $value might be promise, yield to resolve
|
||||
try {
|
||||
if ($this->isPromise($value)) {
|
||||
$value = yield $value;
|
||||
}
|
||||
} catch (Throwable $reason) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
$reason,
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
if ($nonNull) {
|
||||
$returnValue = self::$undefined;
|
||||
} else {
|
||||
$returnValue = null;
|
||||
}
|
||||
goto CHECKED_RETURN;
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
$returnValue = $value;
|
||||
goto CHECKED_RETURN;
|
||||
} elseif ($value instanceof Throwable) {
|
||||
// special handling of Throwable inherited from JS reference implementation, but makes no sense in this PHP
|
||||
$this->addError(Error::createLocatedError(
|
||||
$value,
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
if ($nonNull) {
|
||||
$returnValue = self::$undefined;
|
||||
} else {
|
||||
$returnValue = null;
|
||||
}
|
||||
goto CHECKED_RETURN;
|
||||
}
|
||||
|
||||
if ($type instanceof ListOfType) {
|
||||
$returnValue = [];
|
||||
$index = -1;
|
||||
$itemType = $type->getWrappedType();
|
||||
foreach ($value as $itemValue) {
|
||||
++$index;
|
||||
|
||||
$itemPath = $path;
|
||||
$itemPath[] = $index; // !!! use arrays COW semantics
|
||||
$ctx->resolveInfo->path = $itemPath;
|
||||
|
||||
try {
|
||||
if (! $this->completeValueFast($ctx, $itemType, $itemValue, $itemPath, $itemReturnValue)) {
|
||||
$itemReturnValue = yield $this->completeValue($ctx, $itemType, $itemValue, $itemPath, $nullFence);
|
||||
}
|
||||
} catch (Throwable $reason) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
$reason,
|
||||
$ctx->shared->fieldNodes,
|
||||
$itemPath
|
||||
));
|
||||
$itemReturnValue = null;
|
||||
}
|
||||
if ($itemReturnValue === self::$undefined) {
|
||||
$returnValue = self::$undefined;
|
||||
goto CHECKED_RETURN;
|
||||
}
|
||||
$returnValue[$index] = $itemReturnValue;
|
||||
}
|
||||
|
||||
goto CHECKED_RETURN;
|
||||
} else {
|
||||
if ($type !== $this->schema->getType($type->name)) {
|
||||
$hint = '';
|
||||
if ($this->schema->getConfig()->typeLoader !== null) {
|
||||
$hint = sprintf(
|
||||
'Make sure that type loader returns the same instance as defined in %s.%s',
|
||||
$ctx->type,
|
||||
$ctx->shared->fieldName
|
||||
);
|
||||
}
|
||||
$this->addError(Error::createLocatedError(
|
||||
new InvariantViolation(
|
||||
sprintf(
|
||||
'Schema must contain unique named types but contains multiple types named "%s". %s ' .
|
||||
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
|
||||
$type->name,
|
||||
$hint
|
||||
)
|
||||
),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
$returnValue = null;
|
||||
goto CHECKED_RETURN;
|
||||
}
|
||||
|
||||
if ($type instanceof LeafType) {
|
||||
try {
|
||||
$returnValue = $type->serialize($value);
|
||||
} catch (Throwable $error) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
new InvariantViolation(
|
||||
'Expected a value of type "' . Utils::printSafe($type) . '" but received: ' . Utils::printSafe($value),
|
||||
0,
|
||||
$error
|
||||
),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
$returnValue = null;
|
||||
}
|
||||
goto CHECKED_RETURN;
|
||||
} elseif ($type instanceof CompositeType) {
|
||||
/** @var ObjectType|null $objectType */
|
||||
$objectType = null;
|
||||
if ($type instanceof InterfaceType || $type instanceof UnionType) {
|
||||
$objectType = $type->resolveType($value, $this->contextValue, $ctx->resolveInfo);
|
||||
|
||||
if ($objectType === null) {
|
||||
$objectType = yield $this->resolveTypeSlow($ctx, $value, $type);
|
||||
}
|
||||
|
||||
// !!! $objectType->resolveType() might return promise, yield to resolve
|
||||
$objectType = yield $objectType;
|
||||
if (is_string($objectType)) {
|
||||
$objectType = $this->schema->getType($objectType);
|
||||
}
|
||||
|
||||
if ($objectType === null) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
sprintf(
|
||||
'Composite type "%s" did not resolve concrete object type for value: %s.',
|
||||
$type->name,
|
||||
Utils::printSafe($value)
|
||||
),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
$returnValue = self::$undefined;
|
||||
goto CHECKED_RETURN;
|
||||
} elseif (! $objectType instanceof ObjectType) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
new InvariantViolation(sprintf(
|
||||
'Abstract type %s must resolve to an Object type at ' .
|
||||
'runtime for field %s.%s with value "%s", received "%s". ' .
|
||||
'Either the %s type should provide a "resolveType" ' .
|
||||
'function or each possible type should provide an "isTypeOf" function.',
|
||||
$type,
|
||||
$ctx->resolveInfo->parentType,
|
||||
$ctx->resolveInfo->fieldName,
|
||||
Utils::printSafe($value),
|
||||
Utils::printSafe($objectType),
|
||||
$type
|
||||
)),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
$returnValue = null;
|
||||
goto CHECKED_RETURN;
|
||||
} elseif (! $this->schema->isPossibleType($type, $objectType)) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
new InvariantViolation(sprintf(
|
||||
'Runtime Object type "%s" is not a possible type for "%s".',
|
||||
$objectType,
|
||||
$type
|
||||
)),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
$returnValue = null;
|
||||
goto CHECKED_RETURN;
|
||||
} elseif ($objectType !== $this->schema->getType($objectType->name)) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
new InvariantViolation(
|
||||
sprintf(
|
||||
'Schema must contain unique named types but contains multiple types named "%s". ' .
|
||||
'Make sure that `resolveType` function of abstract type "%s" returns the same ' .
|
||||
'type instance as referenced anywhere else within the schema ' .
|
||||
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
|
||||
$objectType,
|
||||
$type
|
||||
)
|
||||
),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
$returnValue = null;
|
||||
goto CHECKED_RETURN;
|
||||
}
|
||||
} elseif ($type instanceof ObjectType) {
|
||||
$objectType = $type;
|
||||
} else {
|
||||
$this->addError(Error::createLocatedError(
|
||||
sprintf(
|
||||
'Unexpected field type "%s".',
|
||||
Utils::printSafe($type)
|
||||
),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
$returnValue = self::$undefined;
|
||||
goto CHECKED_RETURN;
|
||||
}
|
||||
|
||||
$typeCheck = $objectType->isTypeOf($value, $this->contextValue, $ctx->resolveInfo);
|
||||
if ($typeCheck !== null) {
|
||||
// !!! $objectType->isTypeOf() might return promise, yield to resolve
|
||||
$typeCheck = yield $typeCheck;
|
||||
if (! $typeCheck) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
sprintf('Expected value of type "%s" but got: %s.', $type->name, Utils::printSafe($value)),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
$returnValue = null;
|
||||
goto CHECKED_RETURN;
|
||||
}
|
||||
}
|
||||
|
||||
$returnValue = new stdClass();
|
||||
|
||||
if ($ctx->shared->typeGuard2 === $objectType) {
|
||||
foreach ($ctx->shared->childContextsIfType2 as $childCtx) {
|
||||
$childCtx = clone $childCtx;
|
||||
$childCtx->type = $objectType;
|
||||
$childCtx->value = $value;
|
||||
$childCtx->result = $returnValue;
|
||||
$childCtx->path = $path;
|
||||
$childCtx->path[] = $childCtx->shared->resultName; // !!! uses array COW semantics
|
||||
$childCtx->nullFence = $nullFence;
|
||||
$childCtx->resolveInfo = null;
|
||||
|
||||
$this->queue->enqueue(new Strand($this->spawn($childCtx)));
|
||||
|
||||
// !!! assign null to keep object keys sorted
|
||||
$returnValue->{$childCtx->shared->resultName} = null;
|
||||
}
|
||||
} else {
|
||||
$childContexts = [];
|
||||
|
||||
foreach ($this->collector->collectFields(
|
||||
$objectType,
|
||||
$ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)
|
||||
) as $childShared) {
|
||||
/** @var CoroutineContextShared $childShared */
|
||||
|
||||
$childPath = $path;
|
||||
$childPath[] = $childShared->resultName; // !!! uses array COW semantics
|
||||
$childCtx = new CoroutineContext(
|
||||
$childShared,
|
||||
$objectType,
|
||||
$value,
|
||||
$returnValue,
|
||||
$childPath,
|
||||
$nullFence
|
||||
);
|
||||
|
||||
$childContexts[] = $childCtx;
|
||||
|
||||
$this->queue->enqueue(new Strand($this->spawn($childCtx)));
|
||||
|
||||
// !!! assign null to keep object keys sorted
|
||||
$returnValue->{$childShared->resultName} = null;
|
||||
}
|
||||
|
||||
$ctx->shared->typeGuard2 = $objectType;
|
||||
$ctx->shared->childContextsIfType2 = $childContexts;
|
||||
}
|
||||
|
||||
goto CHECKED_RETURN;
|
||||
} else {
|
||||
$this->addError(Error::createLocatedError(
|
||||
sprintf('Unhandled type "%s".', Utils::printSafe($type)),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
$returnValue = null;
|
||||
goto CHECKED_RETURN;
|
||||
}
|
||||
}
|
||||
|
||||
CHECKED_RETURN:
|
||||
if ($nonNull && $returnValue === null) {
|
||||
$this->addError(Error::createLocatedError(
|
||||
new InvariantViolation(sprintf(
|
||||
'Cannot return null for non-nullable field %s.%s.',
|
||||
$ctx->type->name,
|
||||
$ctx->shared->fieldName
|
||||
)),
|
||||
$ctx->shared->fieldNodes,
|
||||
$path
|
||||
));
|
||||
|
||||
return self::$undefined;
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
private function mergeSelectionSets(CoroutineContext $ctx)
|
||||
{
|
||||
$selections = [];
|
||||
|
||||
foreach ($ctx->shared->fieldNodes as $fieldNode) {
|
||||
if ($fieldNode->selectionSet === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fieldNode->selectionSet->selections as $selection) {
|
||||
$selections[] = $selection;
|
||||
}
|
||||
}
|
||||
|
||||
return $ctx->shared->mergedSelectionSet = new SelectionSetNode(['selections' => $selections]);
|
||||
}
|
||||
|
||||
private function resolveTypeSlow(CoroutineContext $ctx, $value, AbstractType $abstractType)
|
||||
{
|
||||
if ($value !== null &&
|
||||
is_array($value) &&
|
||||
isset($value['__typename']) &&
|
||||
is_string($value['__typename'])
|
||||
) {
|
||||
return $this->schema->getType($value['__typename']);
|
||||
}
|
||||
|
||||
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader !== null) {
|
||||
Warning::warnOnce(
|
||||
sprintf(
|
||||
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
|
||||
'for value: %s. Switching to slow resolution method using `isTypeOf` ' .
|
||||
'of all possible implementations. It requires full schema scan and degrades query performance significantly. ' .
|
||||
' Make sure your `resolveType` always returns valid implementation or throws.',
|
||||
$abstractType->name,
|
||||
Utils::printSafe($value)
|
||||
),
|
||||
Warning::WARNING_FULL_SCHEMA_SCAN
|
||||
);
|
||||
}
|
||||
|
||||
$possibleTypes = $this->schema->getPossibleTypes($abstractType);
|
||||
|
||||
// to be backward-compatible with old executor, ->isTypeOf() is called for all possible types,
|
||||
// it cannot short-circuit when the match is found
|
||||
|
||||
$selectedType = null;
|
||||
foreach ($possibleTypes as $type) {
|
||||
$typeCheck = yield $type->isTypeOf($value, $this->contextValue, $ctx->resolveInfo);
|
||||
if ($selectedType !== null || $typeCheck !== true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectedType = $type;
|
||||
}
|
||||
|
||||
return $selectedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isPromise($value)
|
||||
{
|
||||
return $value instanceof Promise || $this->promiseAdapter->isThenable($value);
|
||||
}
|
||||
}
|
18
src/Experimental/Executor/Runtime.php
Normal file
18
src/Experimental/Executor/Runtime.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Experimental\Executor;
|
||||
|
||||
use GraphQL\Language\AST\ValueNode;
|
||||
use GraphQL\Type\Definition\InputType;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface Runtime
|
||||
{
|
||||
public function evaluate(ValueNode $valueNode, InputType $type);
|
||||
|
||||
public function addError($error);
|
||||
}
|
35
src/Experimental/Executor/Strand.php
Normal file
35
src/Experimental/Executor/Strand.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Experimental\Executor;
|
||||
|
||||
use Generator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Strand
|
||||
{
|
||||
/** @var Generator */
|
||||
public $current;
|
||||
|
||||
/** @var Generator[] */
|
||||
public $stack;
|
||||
|
||||
/** @var int */
|
||||
public $depth;
|
||||
|
||||
/** @var bool|null */
|
||||
public $success;
|
||||
|
||||
/** @var mixed */
|
||||
public $value;
|
||||
|
||||
public function __construct(Generator $coroutine)
|
||||
{
|
||||
$this->current = $coroutine;
|
||||
$this->stack = [];
|
||||
$this->depth = 0;
|
||||
}
|
||||
}
|
229
src/GraphQL.php
229
src/GraphQL.php
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
|
@ -6,15 +9,21 @@ use GraphQL\Executor\ExecutionResult;
|
|||
use GraphQL\Executor\Executor;
|
||||
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
|
||||
use GraphQL\Executor\Promise\Promise;
|
||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||
use GraphQL\Executor\ReferenceExecutor;
|
||||
use GraphQL\Experimental\Executor\CoroutineExecutor;
|
||||
use GraphQL\Language\AST\DocumentNode;
|
||||
use GraphQL\Language\Parser;
|
||||
use GraphQL\Language\Source;
|
||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||
use GraphQL\Type\Definition\Directive;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema as SchemaType;
|
||||
use GraphQL\Validator\DocumentValidator;
|
||||
use GraphQL\Validator\Rules\AbstractValidationRule;
|
||||
use GraphQL\Validator\Rules\QueryComplexity;
|
||||
use GraphQL\Validator\Rules\ValidationRule;
|
||||
use function array_values;
|
||||
use function trigger_error;
|
||||
use const E_USER_DEPRECATED;
|
||||
|
||||
/**
|
||||
* This is the primary facade for fulfilling GraphQL operations.
|
||||
|
@ -57,33 +66,37 @@ class GraphQL
|
|||
* Empty array would allow to skip query validation (may be convenient for persisted
|
||||
* queries which are validated before persisting and assumed valid during execution)
|
||||
*
|
||||
* @api
|
||||
* @param \GraphQL\Type\Schema $schema
|
||||
* @param string|DocumentNode $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $context
|
||||
* @param array|null $variableValues
|
||||
* @param string|null $operationName
|
||||
* @param callable $fieldResolver
|
||||
* @param array $validationRules
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $context
|
||||
* @param mixed[]|null $variableValues
|
||||
* @param ValidationRule[] $validationRules
|
||||
*
|
||||
* @return ExecutionResult
|
||||
* @api
|
||||
*/
|
||||
public static function executeQuery(
|
||||
\GraphQL\Type\Schema $schema,
|
||||
SchemaType $schema,
|
||||
$source,
|
||||
$rootValue = null,
|
||||
$context = null,
|
||||
$variableValues = null,
|
||||
$operationName = null,
|
||||
callable $fieldResolver = null,
|
||||
array $validationRules = null
|
||||
)
|
||||
{
|
||||
?string $operationName = null,
|
||||
?callable $fieldResolver = null,
|
||||
?array $validationRules = null
|
||||
) : ExecutionResult {
|
||||
$promiseAdapter = new SyncPromiseAdapter();
|
||||
|
||||
$promise = self::promiseToExecute($promiseAdapter, $schema, $source, $rootValue, $context,
|
||||
$variableValues, $operationName, $fieldResolver, $validationRules);
|
||||
$promise = self::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$source,
|
||||
$rootValue,
|
||||
$context,
|
||||
$variableValues,
|
||||
$operationName,
|
||||
$fieldResolver,
|
||||
$validationRules
|
||||
);
|
||||
|
||||
return $promiseAdapter->wait($promise);
|
||||
}
|
||||
|
@ -92,31 +105,25 @@ class GraphQL
|
|||
* Same as executeQuery(), but requires PromiseAdapter and always returns a Promise.
|
||||
* Useful for Async PHP platforms.
|
||||
*
|
||||
* @api
|
||||
* @param PromiseAdapter $promiseAdapter
|
||||
* @param \GraphQL\Type\Schema $schema
|
||||
* @param string|DocumentNode $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $context
|
||||
* @param array|null $variableValues
|
||||
* @param string|null $operationName
|
||||
* @param callable $fieldResolver
|
||||
* @param array $validationRules
|
||||
* @param string|DocumentNode $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $context
|
||||
* @param mixed[]|null $variableValues
|
||||
* @param ValidationRule[]|null $validationRules
|
||||
*
|
||||
* @return Promise
|
||||
* @api
|
||||
*/
|
||||
public static function promiseToExecute(
|
||||
PromiseAdapter $promiseAdapter,
|
||||
\GraphQL\Type\Schema $schema,
|
||||
SchemaType $schema,
|
||||
$source,
|
||||
$rootValue = null,
|
||||
$context = null,
|
||||
$variableValues = null,
|
||||
$operationName = null,
|
||||
callable $fieldResolver = null,
|
||||
array $validationRules = null
|
||||
)
|
||||
{
|
||||
?string $operationName = null,
|
||||
?callable $fieldResolver = null,
|
||||
?array $validationRules = null
|
||||
) : Promise {
|
||||
try {
|
||||
if ($source instanceof DocumentNode) {
|
||||
$documentNode = $source;
|
||||
|
@ -125,36 +132,38 @@ class GraphQL
|
|||
}
|
||||
|
||||
// FIXME
|
||||
if (!empty($validationRules)) {
|
||||
foreach ($validationRules as $rule) {
|
||||
if ($rule instanceof QueryComplexity) {
|
||||
$rule->setRawVariableValues($variableValues);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (empty($validationRules)) {
|
||||
/** @var QueryComplexity $queryComplexity */
|
||||
$queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
|
||||
$queryComplexity->setRawVariableValues($variableValues);
|
||||
} else {
|
||||
foreach ($validationRules as $rule) {
|
||||
if (! ($rule instanceof QueryComplexity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rule->setRawVariableValues($variableValues);
|
||||
}
|
||||
}
|
||||
|
||||
$validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);
|
||||
|
||||
if (!empty($validationErrors)) {
|
||||
if (! empty($validationErrors)) {
|
||||
return $promiseAdapter->createFulfilled(
|
||||
new ExecutionResult(null, $validationErrors)
|
||||
);
|
||||
} else {
|
||||
return Executor::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$documentNode,
|
||||
$rootValue,
|
||||
$context,
|
||||
$variableValues,
|
||||
$operationName,
|
||||
$fieldResolver
|
||||
);
|
||||
}
|
||||
|
||||
return Executor::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$documentNode,
|
||||
$rootValue,
|
||||
$context,
|
||||
$variableValues,
|
||||
$operationName,
|
||||
$fieldResolver
|
||||
);
|
||||
} catch (Error $e) {
|
||||
return $promiseAdapter->createFulfilled(
|
||||
new ExecutionResult(null, [$e])
|
||||
|
@ -165,29 +174,29 @@ class GraphQL
|
|||
/**
|
||||
* @deprecated Use executeQuery()->toArray() instead
|
||||
*
|
||||
* @param \GraphQL\Type\Schema $schema
|
||||
* @param string|DocumentNode $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $contextValue
|
||||
* @param array|null $variableValues
|
||||
* @param string|null $operationName
|
||||
* @return Promise|array
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $contextValue
|
||||
* @param mixed[]|null $variableValues
|
||||
*
|
||||
* @return Promise|mixed[]
|
||||
*/
|
||||
public static function execute(
|
||||
\GraphQL\Type\Schema $schema,
|
||||
SchemaType $schema,
|
||||
$source,
|
||||
$rootValue = null,
|
||||
$contextValue = null,
|
||||
$variableValues = null,
|
||||
$operationName = null
|
||||
)
|
||||
{
|
||||
?string $operationName = null
|
||||
) {
|
||||
trigger_error(
|
||||
__METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$result = self::promiseToExecute(
|
||||
$promiseAdapter = Executor::getPromiseAdapter(),
|
||||
|
||||
$promiseAdapter = Executor::getPromiseAdapter();
|
||||
$result = self::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$source,
|
||||
$rootValue,
|
||||
|
@ -199,40 +208,40 @@ class GraphQL
|
|||
if ($promiseAdapter instanceof SyncPromiseAdapter) {
|
||||
$result = $promiseAdapter->wait($result)->toArray();
|
||||
} else {
|
||||
$result = $result->then(function(ExecutionResult $r) {
|
||||
$result = $result->then(static function (ExecutionResult $r) {
|
||||
return $r->toArray();
|
||||
});
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated renamed to executeQuery()
|
||||
*
|
||||
* @param \GraphQL\Type\Schema $schema
|
||||
* @param string|DocumentNode $source
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $contextValue
|
||||
* @param array|null $variableValues
|
||||
* @param string|null $operationName
|
||||
* @param mixed $rootValue
|
||||
* @param mixed $contextValue
|
||||
* @param mixed[]|null $variableValues
|
||||
*
|
||||
* @return ExecutionResult|Promise
|
||||
*/
|
||||
public static function executeAndReturnResult(
|
||||
\GraphQL\Type\Schema $schema,
|
||||
SchemaType $schema,
|
||||
$source,
|
||||
$rootValue = null,
|
||||
$contextValue = null,
|
||||
$variableValues = null,
|
||||
$operationName = null
|
||||
)
|
||||
{
|
||||
?string $operationName = null
|
||||
) {
|
||||
trigger_error(
|
||||
__METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$result = self::promiseToExecute(
|
||||
$promiseAdapter = Executor::getPromiseAdapter(),
|
||||
|
||||
$promiseAdapter = Executor::getPromiseAdapter();
|
||||
$result = self::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$source,
|
||||
$rootValue,
|
||||
|
@ -240,19 +249,22 @@ class GraphQL
|
|||
$variableValues,
|
||||
$operationName
|
||||
);
|
||||
|
||||
if ($promiseAdapter instanceof SyncPromiseAdapter) {
|
||||
$result = $promiseAdapter->wait($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns directives defined in GraphQL spec
|
||||
*
|
||||
* @api
|
||||
* @return Directive[]
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public static function getStandardDirectives()
|
||||
public static function getStandardDirectives() : array
|
||||
{
|
||||
return array_values(Directive::getInternalDirectives());
|
||||
}
|
||||
|
@ -260,48 +272,79 @@ class GraphQL
|
|||
/**
|
||||
* Returns types defined in GraphQL spec
|
||||
*
|
||||
* @api
|
||||
* @return Type[]
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public static function getStandardTypes()
|
||||
public static function getStandardTypes() : array
|
||||
{
|
||||
return array_values(Type::getInternalTypes());
|
||||
return array_values(Type::getStandardTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces standard types with types from this list (matching by name)
|
||||
* Standard types not listed here remain untouched.
|
||||
*
|
||||
* @param Type[] $types
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public static function overrideStandardTypes(array $types)
|
||||
{
|
||||
Type::overrideStandardTypes($types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns standard validation rules implementing GraphQL spec
|
||||
*
|
||||
* @return ValidationRule[]
|
||||
*
|
||||
* @api
|
||||
* @return AbstractValidationRule[]
|
||||
*/
|
||||
public static function getStandardValidationRules()
|
||||
public static function getStandardValidationRules() : array
|
||||
{
|
||||
return array_values(DocumentValidator::defaultRules());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $fn
|
||||
* Set default resolver implementation
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public static function setDefaultFieldResolver(callable $fn)
|
||||
public static function setDefaultFieldResolver(callable $fn) : void
|
||||
{
|
||||
Executor::setDefaultFieldResolver($fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PromiseAdapter|null $promiseAdapter
|
||||
*/
|
||||
public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
|
||||
public static function setPromiseAdapter(?PromiseAdapter $promiseAdapter = null) : void
|
||||
{
|
||||
Executor::setPromiseAdapter($promiseAdapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Experimental: Switch to the new executor
|
||||
*/
|
||||
public static function useExperimentalExecutor()
|
||||
{
|
||||
Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Experimental: Switch back to the default executor
|
||||
*/
|
||||
public static function useReferenceExecutor()
|
||||
{
|
||||
Executor::setImplementationFactory([ReferenceExecutor::class, 'create']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns directives defined in GraphQL spec
|
||||
*
|
||||
* @deprecated Renamed to getStandardDirectives
|
||||
*
|
||||
* @return Directive[]
|
||||
*/
|
||||
public static function getInternalDirectives()
|
||||
public static function getInternalDirectives() : array
|
||||
{
|
||||
return self::getStandardDirectives();
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class ArgumentNode extends Node
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::ARGUMENT;
|
||||
|
||||
/**
|
||||
* @var ValueNode
|
||||
*/
|
||||
/** @var ValueNode */
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class BooleanValueNode extends Node implements ValueNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::BOOLEAN;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var bool */
|
||||
public $value;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
interface DefinitionNode
|
||||
{
|
||||
/**
|
||||
* export type DefinitionNode =
|
||||
* | ExecutableDefinitionNode
|
||||
* | TypeSystemDefinitionNode; // experimental non-spec addition.
|
||||
*/
|
||||
interface DefinitionNode
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class DirectiveDefinitionNode extends Node implements TypeSystemDefinitionNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $kind = NodeKind::DIRECTIVE_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var ArgumentNode[]
|
||||
*/
|
||||
/** @var ArgumentNode[] */
|
||||
public $arguments;
|
||||
|
||||
/**
|
||||
* @var NameNode[]
|
||||
*/
|
||||
/** @var NameNode[] */
|
||||
public $locations;
|
||||
|
||||
/**
|
||||
* @var StringValueNode|null
|
||||
*/
|
||||
|
||||
/** @var StringValueNode|null */
|
||||
public $description;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class DirectiveNode extends Node
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::DIRECTIVE;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var ArgumentNode[]
|
||||
*/
|
||||
/** @var ArgumentNode[] */
|
||||
public $arguments;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class DocumentNode extends Node
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::DOCUMENT;
|
||||
|
||||
/**
|
||||
* @var DefinitionNode[]
|
||||
*/
|
||||
/** @var NodeList|DefinitionNode[] */
|
||||
public $definitions;
|
||||
}
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class EnumTypeDefinitionNode extends Node implements TypeDefinitionNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $kind = NodeKind::ENUM_TYPE_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]
|
||||
*/
|
||||
/** @var DirectiveNode[] */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var EnumValueDefinitionNode[]|null|NodeList
|
||||
*/
|
||||
/** @var EnumValueDefinitionNode[]|NodeList|null */
|
||||
public $values;
|
||||
|
||||
/**
|
||||
* @var StringValueNode|null
|
||||
*/
|
||||
/** @var StringValueNode|null */
|
||||
public $description;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class EnumTypeExtensionNode extends Node implements TypeExtensionNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $kind = NodeKind::ENUM_TYPE_EXTENSION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]|null
|
||||
*/
|
||||
/** @var DirectiveNode[]|null */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var EnumValueDefinitionNode[]|null
|
||||
*/
|
||||
/** @var EnumValueDefinitionNode[]|null */
|
||||
public $values;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class EnumValueDefinitionNode extends Node
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $kind = NodeKind::ENUM_VALUE_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]
|
||||
*/
|
||||
/** @var DirectiveNode[] */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var StringValueNode|null
|
||||
*/
|
||||
/** @var StringValueNode|null */
|
||||
public $description;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class EnumValueNode extends Node implements ValueNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::ENUM;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $value;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
interface ExecutableDefinitionNode extends DefinitionNode
|
||||
{
|
||||
/**
|
||||
* export type ExecutableDefinitionNode =
|
||||
* | OperationDefinitionNode
|
||||
* | FragmentDefinitionNode;
|
||||
*/
|
||||
interface ExecutableDefinitionNode extends DefinitionNode
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,35 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class FieldDefinitionNode extends Node
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $kind = NodeKind::FIELD_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var InputValueDefinitionNode[]|NodeList
|
||||
*/
|
||||
/** @var InputValueDefinitionNode[]|NodeList */
|
||||
public $arguments;
|
||||
|
||||
/**
|
||||
* @var TypeNode
|
||||
*/
|
||||
/** @var TypeNode */
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]|NodeList
|
||||
*/
|
||||
/** @var DirectiveNode[]|NodeList */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var StringValueNode|null
|
||||
*/
|
||||
/** @var StringValueNode|null */
|
||||
public $description;
|
||||
}
|
||||
|
|
|
@ -1,32 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class FieldNode extends Node implements SelectionNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::FIELD;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var NameNode|null
|
||||
*/
|
||||
/** @var NameNode|null */
|
||||
public $alias;
|
||||
|
||||
/**
|
||||
* @var ArgumentNode[]|null
|
||||
*/
|
||||
/** @var ArgumentNode[]|null */
|
||||
public $arguments;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]|null
|
||||
*/
|
||||
/** @var DirectiveNode[]|null */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var SelectionSetNode|null
|
||||
*/
|
||||
/** @var SelectionSetNode|null */
|
||||
public $selectionSet;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class FloatValueNode extends Node implements ValueNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::FLOAT;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $value;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, HasSelectionSet
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::FRAGMENT_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
|
@ -18,18 +20,12 @@ class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, H
|
|||
*/
|
||||
public $variableDefinitions;
|
||||
|
||||
/**
|
||||
* @var NamedTypeNode
|
||||
*/
|
||||
/** @var NamedTypeNode */
|
||||
public $typeCondition;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]|NodeList
|
||||
*/
|
||||
/** @var DirectiveNode[]|NodeList */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var SelectionSetNode
|
||||
*/
|
||||
/** @var SelectionSetNode */
|
||||
public $selectionSet;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class FragmentSpreadNode extends Node implements SelectionNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::FRAGMENT_SPREAD;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]
|
||||
*/
|
||||
/** @var DirectiveNode[] */
|
||||
public $directives;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
interface HasSelectionSet
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class InlineFragmentNode extends Node implements SelectionNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::INLINE_FRAGMENT;
|
||||
|
||||
/**
|
||||
* @var NamedTypeNode
|
||||
*/
|
||||
/** @var NamedTypeNode */
|
||||
public $typeCondition;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]|null
|
||||
*/
|
||||
/** @var DirectiveNode[]|null */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var SelectionSetNode
|
||||
*/
|
||||
/** @var SelectionSetNode */
|
||||
public $selectionSet;
|
||||
}
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class InputObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $kind = NodeKind::INPUT_OBJECT_TYPE_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]|null
|
||||
*/
|
||||
/** @var DirectiveNode[]|null */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var InputValueDefinitionNode[]|null
|
||||
*/
|
||||
/** @var InputValueDefinitionNode[]|null */
|
||||
public $fields;
|
||||
|
||||
/**
|
||||
* @var StringValueNode|null
|
||||
*/
|
||||
/** @var StringValueNode|null */
|
||||
public $description;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class InputObjectTypeExtensionNode extends Node implements TypeExtensionNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $kind = NodeKind::INPUT_OBJECT_TYPE_EXTENSION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]|null
|
||||
*/
|
||||
/** @var DirectiveNode[]|null */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var InputValueDefinitionNode[]|null
|
||||
*/
|
||||
/** @var InputValueDefinitionNode[]|null */
|
||||
public $fields;
|
||||
}
|
||||
|
|
|
@ -1,35 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class InputValueDefinitionNode extends Node
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $kind = NodeKind::INPUT_VALUE_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var TypeNode
|
||||
*/
|
||||
/** @var TypeNode */
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var ValueNode
|
||||
*/
|
||||
/** @var ValueNode */
|
||||
public $defaultValue;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]
|
||||
*/
|
||||
/** @var DirectiveNode[] */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var StringValueNode|null
|
||||
*/
|
||||
/** @var StringValueNode|null */
|
||||
public $description;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class IntValueNode extends Node implements ValueNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::INT;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $value;
|
||||
}
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class InterfaceTypeDefinitionNode extends Node implements TypeDefinitionNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $kind = NodeKind::INTERFACE_TYPE_DEFINITION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]|null
|
||||
*/
|
||||
/** @var DirectiveNode[]|null */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var FieldDefinitionNode[]|null
|
||||
*/
|
||||
/** @var FieldDefinitionNode[]|null */
|
||||
public $fields;
|
||||
|
||||
/**
|
||||
* @var StringValueNode|null
|
||||
*/
|
||||
/** @var StringValueNode|null */
|
||||
public $description;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class InterfaceTypeExtensionNode extends Node implements TypeExtensionNode
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $kind = NodeKind::INTERFACE_TYPE_EXTENSION;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var DirectiveNode[]|null
|
||||
*/
|
||||
/** @var DirectiveNode[]|null */
|
||||
public $directives;
|
||||
|
||||
/**
|
||||
* @var FieldDefinitionNode[]|null
|
||||
*/
|
||||
/** @var FieldDefinitionNode[]|null */
|
||||
public $fields;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class ListTypeNode extends Node implements TypeNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::LIST_TYPE;
|
||||
|
||||
/**
|
||||
* @var Node
|
||||
*/
|
||||
/** @var TypeNode */
|
||||
public $type;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class ListValueNode extends Node implements ValueNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::LST;
|
||||
|
||||
/**
|
||||
* @var ValueNode[]|NodeList
|
||||
*/
|
||||
/** @var ValueNode[]|NodeList */
|
||||
public $values;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
use GraphQL\Language\Source;
|
||||
|
@ -46,27 +49,31 @@ class Location
|
|||
public $source;
|
||||
|
||||
/**
|
||||
* @param $start
|
||||
* @param $end
|
||||
* @param int $start
|
||||
* @param int $end
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function create($start, $end)
|
||||
{
|
||||
$tmp = new static();
|
||||
$tmp = new static();
|
||||
$tmp->start = $start;
|
||||
$tmp->end = $end;
|
||||
$tmp->end = $end;
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
public function __construct(Token $startToken = null, Token $endToken = null, Source $source = null)
|
||||
public function __construct(?Token $startToken = null, ?Token $endToken = null, ?Source $source = null)
|
||||
{
|
||||
$this->startToken = $startToken;
|
||||
$this->endToken = $endToken;
|
||||
$this->source = $source;
|
||||
$this->endToken = $endToken;
|
||||
$this->source = $source;
|
||||
|
||||
if ($startToken && $endToken) {
|
||||
$this->start = $startToken->start;
|
||||
$this->end = $endToken->end;
|
||||
if ($startToken === null || $endToken === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->start = $startToken->start;
|
||||
$this->end = $endToken->end;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class NameNode extends Node implements TypeNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::NAME;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
public $value;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class NamedTypeNode extends Node implements TypeNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::NAMED_TYPE;
|
||||
|
||||
/**
|
||||
* @var NameNode
|
||||
*/
|
||||
/** @var NameNode */
|
||||
public $name;
|
||||
}
|
||||
|
|
|
@ -1,54 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
use GraphQL\Utils\Utils;
|
||||
use function get_object_vars;
|
||||
use function is_array;
|
||||
use function is_scalar;
|
||||
use function json_encode;
|
||||
|
||||
/**
|
||||
* type Node = NameNode
|
||||
* | DocumentNode
|
||||
* | OperationDefinitionNode
|
||||
* | VariableDefinitionNode
|
||||
* | VariableNode
|
||||
* | SelectionSetNode
|
||||
* | FieldNode
|
||||
* | ArgumentNode
|
||||
* | FragmentSpreadNode
|
||||
* | InlineFragmentNode
|
||||
* | FragmentDefinitionNode
|
||||
* | IntValueNode
|
||||
* | FloatValueNode
|
||||
* | StringValueNode
|
||||
* | BooleanValueNode
|
||||
* | EnumValueNode
|
||||
* | ListValueNode
|
||||
* | ObjectValueNode
|
||||
* | ObjectFieldNode
|
||||
* | DirectiveNode
|
||||
* | ListTypeNode
|
||||
* | NonNullTypeNode
|
||||
*/
|
||||
abstract class Node
|
||||
{
|
||||
/**
|
||||
type Node = NameNode
|
||||
| DocumentNode
|
||||
| OperationDefinitionNode
|
||||
| VariableDefinitionNode
|
||||
| VariableNode
|
||||
| SelectionSetNode
|
||||
| FieldNode
|
||||
| ArgumentNode
|
||||
| FragmentSpreadNode
|
||||
| InlineFragmentNode
|
||||
| FragmentDefinitionNode
|
||||
| IntValueNode
|
||||
| FloatValueNode
|
||||
| StringValueNode
|
||||
| BooleanValueNode
|
||||
| EnumValueNode
|
||||
| ListValueNode
|
||||
| ObjectValueNode
|
||||
| ObjectFieldNode
|
||||
| DirectiveNode
|
||||
| ListTypeNode
|
||||
| NonNullTypeNode
|
||||
*/
|
||||
|
||||
public $kind;
|
||||
|
||||
/**
|
||||
* @var Location
|
||||
*/
|
||||
/** @var Location */
|
||||
public $loc;
|
||||
|
||||
/**
|
||||
* @param array $vars
|
||||
* @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars
|
||||
*/
|
||||
public function __construct(array $vars)
|
||||
{
|
||||
if (!empty($vars)) {
|
||||
Utils::assign($this, $vars);
|
||||
if (empty($vars)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Utils::assign($this, $vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @return self
|
||||
*/
|
||||
public function cloneDeep()
|
||||
{
|
||||
|
@ -56,8 +60,9 @@ abstract class Node
|
|||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return array|Node
|
||||
* @param string|NodeList|Location|Node|(Node|NodeList|Location)[] $value
|
||||
*
|
||||
* @return string|NodeList|Location|Node
|
||||
*/
|
||||
private function cloneValue($value)
|
||||
{
|
||||
|
@ -66,7 +71,7 @@ abstract class Node
|
|||
foreach ($value as $key => $arrValue) {
|
||||
$cloned[$key] = $this->cloneValue($arrValue);
|
||||
}
|
||||
} else if ($value instanceof Node) {
|
||||
} elseif ($value instanceof self) {
|
||||
$cloned = clone $value;
|
||||
foreach (get_object_vars($cloned) as $prop => $propValue) {
|
||||
$cloned->{$prop} = $this->cloneValue($propValue);
|
||||
|
@ -84,34 +89,35 @@ abstract class Node
|
|||
public function __toString()
|
||||
{
|
||||
$tmp = $this->toArray(true);
|
||||
return json_encode($tmp);
|
||||
|
||||
return (string) json_encode($tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $recursive
|
||||
* @return array
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function toArray($recursive = false)
|
||||
{
|
||||
if ($recursive) {
|
||||
return $this->recursiveToArray($this);
|
||||
} else {
|
||||
$tmp = (array) $this;
|
||||
|
||||
if ($this->loc) {
|
||||
$tmp['loc'] = [
|
||||
'start' => $this->loc->start,
|
||||
'end' => $this->loc->end
|
||||
];
|
||||
}
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
$tmp = (array) $this;
|
||||
|
||||
if ($this->loc !== null) {
|
||||
$tmp['loc'] = [
|
||||
'start' => $this->loc->start,
|
||||
'end' => $this->loc->end,
|
||||
];
|
||||
}
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return array
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function recursiveToArray(Node $node)
|
||||
{
|
||||
|
@ -119,28 +125,30 @@ abstract class Node
|
|||
'kind' => $node->kind,
|
||||
];
|
||||
|
||||
if ($node->loc) {
|
||||
if ($node->loc !== null) {
|
||||
$result['loc'] = [
|
||||
'start' => $node->loc->start,
|
||||
'end' => $node->loc->end
|
||||
'end' => $node->loc->end,
|
||||
];
|
||||
}
|
||||
|
||||
foreach (get_object_vars($node) as $prop => $propValue) {
|
||||
if (isset($result[$prop]))
|
||||
if (isset($result[$prop])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($propValue === null)
|
||||
if ($propValue === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($propValue) || $propValue instanceof NodeList) {
|
||||
$tmp = [];
|
||||
foreach ($propValue as $tmp1) {
|
||||
$tmp[] = $tmp1 instanceof Node ? $this->recursiveToArray($tmp1) : (array) $tmp1;
|
||||
}
|
||||
} else if ($propValue instanceof Node) {
|
||||
} elseif ($propValue instanceof Node) {
|
||||
$tmp = $this->recursiveToArray($propValue);
|
||||
} else if (is_scalar($propValue) || null === $propValue) {
|
||||
} elseif (is_scalar($propValue) || $propValue === null) {
|
||||
$tmp = $propValue;
|
||||
} else {
|
||||
$tmp = null;
|
||||
|
@ -148,6 +156,7 @@ abstract class Node
|
|||
|
||||
$result[$prop] = $tmp;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class NodeKind
|
||||
|
@ -7,139 +9,130 @@ class NodeKind
|
|||
// constants from language/kinds.js:
|
||||
|
||||
const NAME = 'Name';
|
||||
|
||||
// Document
|
||||
|
||||
const DOCUMENT = 'Document';
|
||||
const DOCUMENT = 'Document';
|
||||
const OPERATION_DEFINITION = 'OperationDefinition';
|
||||
const VARIABLE_DEFINITION = 'VariableDefinition';
|
||||
const VARIABLE = 'Variable';
|
||||
const SELECTION_SET = 'SelectionSet';
|
||||
const FIELD = 'Field';
|
||||
const ARGUMENT = 'Argument';
|
||||
|
||||
const VARIABLE_DEFINITION = 'VariableDefinition';
|
||||
const VARIABLE = 'Variable';
|
||||
const SELECTION_SET = 'SelectionSet';
|
||||
const FIELD = 'Field';
|
||||
const ARGUMENT = 'Argument';
|
||||
// Fragments
|
||||
|
||||
const FRAGMENT_SPREAD = 'FragmentSpread';
|
||||
const INLINE_FRAGMENT = 'InlineFragment';
|
||||
const FRAGMENT_SPREAD = 'FragmentSpread';
|
||||
const INLINE_FRAGMENT = 'InlineFragment';
|
||||
const FRAGMENT_DEFINITION = 'FragmentDefinition';
|
||||
|
||||
// Values
|
||||
|
||||
const INT = 'IntValue';
|
||||
const FLOAT = 'FloatValue';
|
||||
const STRING = 'StringValue';
|
||||
const BOOLEAN = 'BooleanValue';
|
||||
const ENUM = 'EnumValue';
|
||||
const NULL = 'NullValue';
|
||||
const LST = 'ListValue';
|
||||
const OBJECT = 'ObjectValue';
|
||||
const INT = 'IntValue';
|
||||
const FLOAT = 'FloatValue';
|
||||
const STRING = 'StringValue';
|
||||
const BOOLEAN = 'BooleanValue';
|
||||
const ENUM = 'EnumValue';
|
||||
const NULL = 'NullValue';
|
||||
const LST = 'ListValue';
|
||||
const OBJECT = 'ObjectValue';
|
||||
const OBJECT_FIELD = 'ObjectField';
|
||||
|
||||
// Directives
|
||||
|
||||
const DIRECTIVE = 'Directive';
|
||||
|
||||
// Types
|
||||
|
||||
const NAMED_TYPE = 'NamedType';
|
||||
const LIST_TYPE = 'ListType';
|
||||
const NAMED_TYPE = 'NamedType';
|
||||
const LIST_TYPE = 'ListType';
|
||||
const NON_NULL_TYPE = 'NonNullType';
|
||||
|
||||
// Type System Definitions
|
||||
|
||||
const SCHEMA_DEFINITION = 'SchemaDefinition';
|
||||
const SCHEMA_DEFINITION = 'SchemaDefinition';
|
||||
const OPERATION_TYPE_DEFINITION = 'OperationTypeDefinition';
|
||||
|
||||
// Type Definitions
|
||||
|
||||
const SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition';
|
||||
const OBJECT_TYPE_DEFINITION = 'ObjectTypeDefinition';
|
||||
const FIELD_DEFINITION = 'FieldDefinition';
|
||||
const INPUT_VALUE_DEFINITION = 'InputValueDefinition';
|
||||
const INTERFACE_TYPE_DEFINITION = 'InterfaceTypeDefinition';
|
||||
const UNION_TYPE_DEFINITION = 'UnionTypeDefinition';
|
||||
const ENUM_TYPE_DEFINITION = 'EnumTypeDefinition';
|
||||
const ENUM_VALUE_DEFINITION = 'EnumValueDefinition';
|
||||
const SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition';
|
||||
const OBJECT_TYPE_DEFINITION = 'ObjectTypeDefinition';
|
||||
const FIELD_DEFINITION = 'FieldDefinition';
|
||||
const INPUT_VALUE_DEFINITION = 'InputValueDefinition';
|
||||
const INTERFACE_TYPE_DEFINITION = 'InterfaceTypeDefinition';
|
||||
const UNION_TYPE_DEFINITION = 'UnionTypeDefinition';
|
||||
const ENUM_TYPE_DEFINITION = 'EnumTypeDefinition';
|
||||
const ENUM_VALUE_DEFINITION = 'EnumValueDefinition';
|
||||
const INPUT_OBJECT_TYPE_DEFINITION = 'InputObjectTypeDefinition';
|
||||
|
||||
// Type Extensions
|
||||
|
||||
const SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension';
|
||||
const OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension';
|
||||
const INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension';
|
||||
const UNION_TYPE_EXTENSION = 'UnionTypeExtension';
|
||||
const ENUM_TYPE_EXTENSION = 'EnumTypeExtension';
|
||||
const SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension';
|
||||
const OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension';
|
||||
const INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension';
|
||||
const UNION_TYPE_EXTENSION = 'UnionTypeExtension';
|
||||
const ENUM_TYPE_EXTENSION = 'EnumTypeExtension';
|
||||
const INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension';
|
||||
|
||||
// Directive Definitions
|
||||
|
||||
const DIRECTIVE_DEFINITION = 'DirectiveDefinition';
|
||||
|
||||
/**
|
||||
* @todo conver to const array when moving to PHP5.6
|
||||
* @var array
|
||||
*/
|
||||
// Type System Extensions
|
||||
const SCHEMA_EXTENSION = 'SchemaExtension';
|
||||
|
||||
/** @var string[] */
|
||||
public static $classMap = [
|
||||
NodeKind::NAME => NameNode::class,
|
||||
self::NAME => NameNode::class,
|
||||
|
||||
// Document
|
||||
NodeKind::DOCUMENT => DocumentNode::class,
|
||||
NodeKind::OPERATION_DEFINITION => OperationDefinitionNode::class,
|
||||
NodeKind::VARIABLE_DEFINITION => VariableDefinitionNode::class,
|
||||
NodeKind::VARIABLE => VariableNode::class,
|
||||
NodeKind::SELECTION_SET => SelectionSetNode::class,
|
||||
NodeKind::FIELD => FieldNode::class,
|
||||
NodeKind::ARGUMENT => ArgumentNode::class,
|
||||
self::DOCUMENT => DocumentNode::class,
|
||||
self::OPERATION_DEFINITION => OperationDefinitionNode::class,
|
||||
self::VARIABLE_DEFINITION => VariableDefinitionNode::class,
|
||||
self::VARIABLE => VariableNode::class,
|
||||
self::SELECTION_SET => SelectionSetNode::class,
|
||||
self::FIELD => FieldNode::class,
|
||||
self::ARGUMENT => ArgumentNode::class,
|
||||
|
||||
// Fragments
|
||||
NodeKind::FRAGMENT_SPREAD => FragmentSpreadNode::class,
|
||||
NodeKind::INLINE_FRAGMENT => InlineFragmentNode::class,
|
||||
NodeKind::FRAGMENT_DEFINITION => FragmentDefinitionNode::class,
|
||||
self::FRAGMENT_SPREAD => FragmentSpreadNode::class,
|
||||
self::INLINE_FRAGMENT => InlineFragmentNode::class,
|
||||
self::FRAGMENT_DEFINITION => FragmentDefinitionNode::class,
|
||||
|
||||
// Values
|
||||
NodeKind::INT => IntValueNode::class,
|
||||
NodeKind::FLOAT => FloatValueNode::class,
|
||||
NodeKind::STRING => StringValueNode::class,
|
||||
NodeKind::BOOLEAN => BooleanValueNode::class,
|
||||
NodeKind::ENUM => EnumValueNode::class,
|
||||
NodeKind::NULL => NullValueNode::class,
|
||||
NodeKind::LST => ListValueNode::class,
|
||||
NodeKind::OBJECT => ObjectValueNode::class,
|
||||
NodeKind::OBJECT_FIELD => ObjectFieldNode::class,
|
||||
self::INT => IntValueNode::class,
|
||||
self::FLOAT => FloatValueNode::class,
|
||||
self::STRING => StringValueNode::class,
|
||||
self::BOOLEAN => BooleanValueNode::class,
|
||||
self::ENUM => EnumValueNode::class,
|
||||
self::NULL => NullValueNode::class,
|
||||
self::LST => ListValueNode::class,
|
||||
self::OBJECT => ObjectValueNode::class,
|
||||
self::OBJECT_FIELD => ObjectFieldNode::class,
|
||||
|
||||
// Directives
|
||||
NodeKind::DIRECTIVE => DirectiveNode::class,
|
||||
self::DIRECTIVE => DirectiveNode::class,
|
||||
|
||||
// Types
|
||||
NodeKind::NAMED_TYPE => NamedTypeNode::class,
|
||||
NodeKind::LIST_TYPE => ListTypeNode::class,
|
||||
NodeKind::NON_NULL_TYPE => NonNullTypeNode::class,
|
||||
self::NAMED_TYPE => NamedTypeNode::class,
|
||||
self::LIST_TYPE => ListTypeNode::class,
|
||||
self::NON_NULL_TYPE => NonNullTypeNode::class,
|
||||
|
||||
// Type System Definitions
|
||||
NodeKind::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
|
||||
NodeKind::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class,
|
||||
self::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
|
||||
self::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class,
|
||||
|
||||
// Type Definitions
|
||||
NodeKind::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class,
|
||||
NodeKind::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class,
|
||||
NodeKind::FIELD_DEFINITION => FieldDefinitionNode::class,
|
||||
NodeKind::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class,
|
||||
NodeKind::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class,
|
||||
NodeKind::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class,
|
||||
NodeKind::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class,
|
||||
NodeKind::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class,
|
||||
NodeKind::INPUT_OBJECT_TYPE_DEFINITION =>InputObjectTypeDefinitionNode::class,
|
||||
self::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class,
|
||||
self::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class,
|
||||
self::FIELD_DEFINITION => FieldDefinitionNode::class,
|
||||
self::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class,
|
||||
self::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class,
|
||||
self::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class,
|
||||
self::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class,
|
||||
self::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class,
|
||||
self::INPUT_OBJECT_TYPE_DEFINITION => InputObjectTypeDefinitionNode::class,
|
||||
|
||||
// Type Extensions
|
||||
NodeKind::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class,
|
||||
NodeKind::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class,
|
||||
NodeKind::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class,
|
||||
NodeKind::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class,
|
||||
NodeKind::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class,
|
||||
NodeKind::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class,
|
||||
self::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class,
|
||||
self::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class,
|
||||
self::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class,
|
||||
self::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class,
|
||||
self::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class,
|
||||
self::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class,
|
||||
|
||||
// Directive Definitions
|
||||
NodeKind::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class
|
||||
self::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use Generator;
|
||||
use GraphQL\Utils\AST;
|
||||
use IteratorAggregate;
|
||||
use function array_merge;
|
||||
use function array_splice;
|
||||
use function count;
|
||||
use function is_array;
|
||||
|
||||
/**
|
||||
* Class NodeList
|
||||
*
|
||||
* @package GraphQL\Utils
|
||||
*/
|
||||
class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
|
||||
class NodeList implements ArrayAccess, IteratorAggregate, Countable
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var Node[]|mixed[] */
|
||||
private $nodes;
|
||||
|
||||
/**
|
||||
* @param array $nodes
|
||||
* @param Node[]|mixed[] $nodes
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function create(array $nodes)
|
||||
|
@ -25,8 +30,7 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||
}
|
||||
|
||||
/**
|
||||
* NodeList constructor.
|
||||
* @param array $nodes
|
||||
* @param Node[]|mixed[] $nodes
|
||||
*/
|
||||
public function __construct(array $nodes)
|
||||
{
|
||||
|
@ -35,6 +39,7 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
|
@ -44,6 +49,7 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
|
@ -78,9 +84,10 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @param mixed $replacement
|
||||
*
|
||||
* @return NodeList
|
||||
*/
|
||||
public function splice($offset, $length, $replacement = null)
|
||||
|
@ -89,25 +96,26 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||
}
|
||||
|
||||
/**
|
||||
* @param $list
|
||||
* @param NodeList|Node[] $list
|
||||
*
|
||||
* @return NodeList
|
||||
*/
|
||||
public function merge($list)
|
||||
{
|
||||
if ($list instanceof NodeList) {
|
||||
if ($list instanceof self) {
|
||||
$list = $list->nodes;
|
||||
}
|
||||
|
||||
return new NodeList(array_merge($this->nodes, $list));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator
|
||||
* @return Generator
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
$count = count($this->nodes);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
yield $this->offsetGet($i);
|
||||
foreach ($this->nodes as $key => $_) {
|
||||
yield $this->offsetGet($key);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class NonNullTypeNode extends Node implements TypeNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::NON_NULL_TYPE;
|
||||
|
||||
/**
|
||||
* @var NameNode | ListTypeNode
|
||||
*/
|
||||
/** @var NamedTypeNode | ListTypeNode */
|
||||
public $type;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class NullValueNode extends Node implements ValueNode
|
||||
{
|
||||
/** @var string */
|
||||
public $kind = NodeKind::NULL;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue