mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-04-07 19:22:02 +00:00
Compare commits
No commits in common. "master" and "v0.10.2" have entirely different histories.
403 changed files with 30699 additions and 50686 deletions
11
.gitattributes
vendored
11
.gitattributes
vendored
|
@ -1,13 +1,2 @@
|
||||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||||
* text eol=lf
|
* text eol=lf
|
||||||
/benchmarks export-ignore
|
|
||||||
/tests export-ignore
|
|
||||||
/examples export-ignore
|
|
||||||
/tools export-ignore
|
|
||||||
.gitattributes export-ignore
|
|
||||||
.gitignore export-ignore
|
|
||||||
.travis.yml export-ignore
|
|
||||||
CONTRIBUTING.md export-ignore
|
|
||||||
mkdocs.yml export-ignore
|
|
||||||
phpbench.json export-ignore
|
|
||||||
phpunit.xml.dist export-ignore
|
|
||||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,7 +1,5 @@
|
||||||
.phpcs-cache
|
.idea/
|
||||||
composer.lock
|
|
||||||
composer.phar
|
composer.phar
|
||||||
phpcs.xml
|
composer.lock
|
||||||
phpstan.neon
|
|
||||||
vendor/
|
vendor/
|
||||||
/.idea
|
bin/
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
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
|
|
66
.travis.yml
66
.travis.yml
|
@ -1,66 +1,34 @@
|
||||||
dist: trusty
|
|
||||||
language: php
|
language: php
|
||||||
|
|
||||||
|
# Required for HHVM, see https://github.com/travis-ci/travis-ci/issues/7712
|
||||||
|
dist: trusty
|
||||||
|
|
||||||
php:
|
php:
|
||||||
|
- 5.5
|
||||||
|
- 5.6
|
||||||
|
- 7.0
|
||||||
- 7.1
|
- 7.1
|
||||||
- 7.2
|
- hhvm
|
||||||
- 7.3
|
|
||||||
- 7.4snapshot
|
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
env:
|
|
||||||
matrix:
|
matrix:
|
||||||
- EXECUTOR= DEPENDENCIES=--prefer-lowest
|
allow_failures:
|
||||||
- EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest
|
- php: nightly
|
||||||
- EXECUTOR=
|
|
||||||
- EXECUTOR=coroutine
|
|
||||||
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.composer/cache
|
- $HOME/.composer/cache
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
|
- if [[ "$TRAVIS_PHP_VERSION" != "5.6" && "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini || true; fi
|
||||||
- travis_retry composer self-update
|
- composer selfupdate
|
||||||
|
|
||||||
install: travis_retry composer update --prefer-dist
|
|
||||||
|
|
||||||
script: ./vendor/bin/phpunit --group default,ReactPromise
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
allow_failures:
|
|
||||||
- php: 7.4snapshot
|
|
||||||
- php: nightly
|
|
||||||
|
|
||||||
include:
|
|
||||||
- stage: Test
|
|
||||||
install:
|
install:
|
||||||
- travis_retry composer update --prefer-dist {$DEPENDENCIES}
|
- composer install --dev --prefer-dist
|
||||||
|
- composer require react/promise:2.*
|
||||||
|
- composer require psr/http-message:1.*
|
||||||
|
|
||||||
- stage: Test
|
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
|
||||||
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
|
||||||
|
|
105
CHANGELOG.md
105
CHANGELOG.md
|
@ -1,108 +1,5 @@
|
||||||
# Changelog
|
# 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)
|
|
||||||
- Changed minimum required PHP version to 5.6
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Allow extending GraphQL errors with additional properties
|
|
||||||
- Fixed parsing of default values in Schema SDL
|
|
||||||
- Handling several more cases in findBreakingChanges
|
|
||||||
- StandardServer: expect `operationName` (instead of `operation`) in input
|
|
||||||
|
|
||||||
|
|
||||||
#### v0.11.5
|
|
||||||
- Allow objects with __toString in IDType
|
|
||||||
|
|
||||||
#### v0.11.4
|
|
||||||
- findBreakingChanges utility (see #199)
|
|
||||||
|
|
||||||
#### v0.11.3
|
|
||||||
- StandardServer: Support non pre-parsed PSR-7 request body (see #202)
|
|
||||||
|
|
||||||
#### v0.11.2
|
|
||||||
- Bugfix: provide descriptions to custom scalars (see #181)
|
|
||||||
|
|
||||||
#### v0.11.1
|
|
||||||
- Ability to override internal types via `types` option of the schema (see #174).
|
|
||||||
|
|
||||||
## v0.11.0
|
|
||||||
This release brings little changes but there are two reasons why it is released as major version:
|
|
||||||
|
|
||||||
1. To follow reference implementation versions (it matches 0.11.x series of graphql-js)
|
|
||||||
2. It may break existing applications because scalar input coercion rules are stricter now:<br>
|
|
||||||
In previous versions sloppy client input could leak through with unexpected results.
|
|
||||||
For example string `"false"` accidentally sent in variables was converted to boolean `true`
|
|
||||||
and passed to field arguments. In the new version, such input will produce an error
|
|
||||||
(which is a spec-compliant behavior).
|
|
||||||
|
|
||||||
Improvements:
|
|
||||||
- Stricter input coercion (see #171)
|
|
||||||
- Types built with `BuildSchema` now have reference to AST node with corresponding AST definition (in $astNode property)
|
|
||||||
- Account for query offset for error locations (e.g. when query is stored in `.graphql` file)
|
|
||||||
|
|
||||||
#### v0.10.2
|
#### v0.10.2
|
||||||
- StandardServer improvement: do not raise an error when variables are passed as empty string (see #156)
|
- StandardServer improvement: do not raise an error when variables are passed as empty string (see #156)
|
||||||
|
|
||||||
|
@ -197,4 +94,4 @@ Improvements:
|
||||||
- New docs and examples
|
- New docs and examples
|
||||||
|
|
||||||
## Older versions
|
## Older versions
|
||||||
Look at [GitHub Releases Page](https://github.com/webonyx/graphql-php/releases).
|
Look at [Github Releases Page](https://github.com/webonyx/graphql-php/releases).
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# Contributing to GraphQL PHP
|
# Contributing to GraphQL PHP
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
If your contribution requires significant or breaking changes, or if you plan to propose a major new feature,
|
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
|
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.
|
a brief proposal and discuss it with us first.
|
||||||
|
|
||||||
For smaller contributions just use this workflow:
|
For smaller contributions just use this workflow:
|
||||||
|
@ -10,46 +11,17 @@ For smaller contributions just use this workflow:
|
||||||
* Fork the project.
|
* Fork the project.
|
||||||
* Add your features and or bug fixes.
|
* Add your features and or bug fixes.
|
||||||
* Add tests. Tests are important for us.
|
* Add tests. Tests are important for us.
|
||||||
* Check your changes using `composer check-all`.
|
* Send a pull request
|
||||||
* Add an entry to the [Changelog's Unreleases section](CHANGELOG.md#unreleased).
|
|
||||||
* Send a pull request.
|
|
||||||
|
|
||||||
## Setup the Development Environment
|
## Using GraphQL PHP from a Git checkout
|
||||||
First, copy the URL of your fork and `git clone` it to your local machine.
|
```
|
||||||
|
$ git clone https://github.com/webonyx/graphql-php.git
|
||||||
```sh
|
$ cd graphql-php
|
||||||
cd graphql-php
|
$ composer install
|
||||||
composer install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running tests
|
## Running tests
|
||||||
```sh
|
From the project root:
|
||||||
./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 .
|
|
||||||
```
|
```
|
||||||
|
|
41
LICENSE
41
LICENSE
|
@ -1,21 +1,28 @@
|
||||||
MIT License
|
Copyright (c) 2015, webonyx
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
Copyright (c) 2015-present, Webonyx, LLC.
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
list of conditions and the following disclaimer.
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
copies or substantial portions of the Software.
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of graphql-php nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
34
README.md
34
README.md
|
@ -1,6 +1,6 @@
|
||||||
# graphql-php
|
# graphql-php
|
||||||
[](https://travis-ci.org/webonyx/graphql-php)
|
[](https://travis-ci.org/webonyx/graphql-php)
|
||||||
[](https://scrutinizer-ci.com/g/webonyx/graphql-php)
|
[](https://coveralls.io/github/webonyx/graphql-php)
|
||||||
[](https://packagist.org/packages/webonyx/graphql-php)
|
[](https://packagist.org/packages/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
|
## Documentation
|
||||||
Full documentation is available on the [Documentation site](https://webonyx.github.io/graphql-php/) as well
|
Full documentation is available on the [Documentation site](http://webonyx.github.io/graphql-php/) as well
|
||||||
as in the [docs](docs/) folder of the distribution.
|
as in the [docs](docs/) folder of the distribution.
|
||||||
|
|
||||||
If you don't know what GraphQL is, visit this [official website](http://graphql.org)
|
If you don't know what GraphQL is, visit this [official website](http://graphql.org)
|
||||||
|
@ -24,29 +24,11 @@ by the Facebook engineering team.
|
||||||
There are several ready examples in the [examples](examples/) folder of the distribution with specific
|
There are several ready examples in the [examples](examples/) folder of the distribution with specific
|
||||||
README file per example.
|
README file per example.
|
||||||
|
|
||||||
## Contributors
|
## Contribute
|
||||||
|
Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute.
|
||||||
|
|
||||||
This project exists thanks to [all the people](https://github.com/webonyx/graphql-php/graphs/contributors) who contribute. [[Contribute](CONTRIBUTING.md)].
|
## Old README.md
|
||||||
|
Here is a [link to the old README.md](https://github.com/webonyx/graphql-php/blob/v0.9.14/README.md).
|
||||||
|
|
||||||
## Backers
|
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).
|
||||||
<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).
|
|
||||||
|
|
219
UPGRADE.md
219
UPGRADE.md
|
@ -1,222 +1,3 @@
|
||||||
## Master
|
|
||||||
|
|
||||||
### Breaking (major): dropped deprecations
|
|
||||||
- dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`.
|
|
||||||
|
|
||||||
## Upgrade v0.12.x > v0.13.x
|
|
||||||
|
|
||||||
### Breaking (major): minimum supported version of PHP
|
|
||||||
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
|
|
||||||
Dropped support for PHP 5.5. This release still supports PHP 5.6 and PHP 7.0
|
|
||||||
**But the next major release will require PHP7.1+**
|
|
||||||
|
|
||||||
### Breaking: Custom scalar types need to throw on invalid value
|
|
||||||
As null might be a valid value custom types need to throw an
|
|
||||||
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`
|
|
||||||
to BuildSchema::buildAST(), BuildSchema::build() or Printer::doPrint().
|
|
||||||
|
|
||||||
Here is the official way now to define descriptions in the graphQL language:
|
|
||||||
|
|
||||||
Old:
|
|
||||||
|
|
||||||
```graphql
|
|
||||||
# Description
|
|
||||||
type Dog {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
New:
|
|
||||||
|
|
||||||
```graphql
|
|
||||||
"Description"
|
|
||||||
type Dog {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
"""
|
|
||||||
Long Description
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
|
|
||||||
### Breaking: Standard server expects `operationName` vs `operation` for multi-op queries
|
|
||||||
Before the change:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"queryId": "persisted-query-id",
|
|
||||||
"operation": "QueryFromPersistedDocument",
|
|
||||||
"variables": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
After the change:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"queryId": "persisted-query-id",
|
|
||||||
"operationName": "QueryFromPersistedDocument",
|
|
||||||
"variables": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
This naming is aligned with graphql-express version.
|
|
||||||
|
|
||||||
### Possibly Breaking: AST to array serialization excludes nulls
|
|
||||||
Most users won't be affected. It *may* affect you only if you do your own manipulations
|
|
||||||
with exported AST.
|
|
||||||
|
|
||||||
Example of json-serialized AST before the change:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": "Field",
|
|
||||||
"loc": null,
|
|
||||||
"name": {
|
|
||||||
"kind": "Name",
|
|
||||||
"loc": null,
|
|
||||||
"value": "id"
|
|
||||||
},
|
|
||||||
"alias": null,
|
|
||||||
"arguments": [],
|
|
||||||
"directives": [],
|
|
||||||
"selectionSet": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
After the change:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": "Field",
|
|
||||||
"name": {
|
|
||||||
"kind": "Name",
|
|
||||||
"value": "id"
|
|
||||||
},
|
|
||||||
"arguments": [],
|
|
||||||
"directives": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Upgrade v0.8.x, v0.9.x > v0.10.x
|
## Upgrade v0.8.x, v0.9.x > v0.10.x
|
||||||
|
|
||||||
### Breaking: changed minimum PHP version from 5.4 to 5.5
|
### Breaking: changed minimum PHP version from 5.4 to 5.5
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Benchmarks;
|
namespace GraphQL\Benchmarks;
|
||||||
|
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
use GraphQL\Schema;
|
||||||
use GraphQL\Benchmarks\Utils\QueryGenerator;
|
use GraphQL\Benchmarks\Utils\QueryGenerator;
|
||||||
use GraphQL\Benchmarks\Utils\SchemaGenerator;
|
use GraphQL\Benchmarks\Utils\SchemaGenerator;
|
||||||
use GraphQL\GraphQL;
|
use GraphQL\Type\LazyResolution;
|
||||||
use GraphQL\Type\Schema;
|
|
||||||
use GraphQL\Type\SchemaConfig;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @BeforeMethods({"setUp"})
|
* @BeforeMethods({"setUp"})
|
||||||
|
@ -16,12 +16,18 @@ use GraphQL\Type\SchemaConfig;
|
||||||
*/
|
*/
|
||||||
class HugeSchemaBench
|
class HugeSchemaBench
|
||||||
{
|
{
|
||||||
/** @var SchemaGenerator */
|
/**
|
||||||
|
* @var SchemaGenerator
|
||||||
|
*/
|
||||||
private $schemaBuilder;
|
private $schemaBuilder;
|
||||||
|
|
||||||
private $schema;
|
private $schema;
|
||||||
|
|
||||||
/** @var string */
|
private $lazySchema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $smallQuery;
|
private $smallQuery;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
|
@ -30,7 +36,7 @@ class HugeSchemaBench
|
||||||
'totalTypes' => 600,
|
'totalTypes' => 600,
|
||||||
'fieldsPerType' => 8,
|
'fieldsPerType' => 8,
|
||||||
'listFieldsPerType' => 2,
|
'listFieldsPerType' => 2,
|
||||||
'nestingLevel' => 10,
|
'nestingLevel' => 10
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->schema = $this->schemaBuilder->buildSchema();
|
$this->schema = $this->schemaBuilder->buildSchema();
|
||||||
|
@ -43,7 +49,8 @@ class HugeSchemaBench
|
||||||
{
|
{
|
||||||
$this->schemaBuilder
|
$this->schemaBuilder
|
||||||
->buildSchema()
|
->buildSchema()
|
||||||
->getTypeMap();
|
->getTypeMap()
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function benchSchemaLazy()
|
public function benchSchemaLazy()
|
||||||
|
@ -53,19 +60,19 @@ class HugeSchemaBench
|
||||||
|
|
||||||
public function benchSmallQuery()
|
public function benchSmallQuery()
|
||||||
{
|
{
|
||||||
$result = GraphQL::executeQuery($this->schema, $this->smallQuery);
|
$result = GraphQL::execute($this->schema, $this->smallQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function benchSmallQueryLazy()
|
public function benchSmallQueryLazy()
|
||||||
{
|
{
|
||||||
$schema = $this->createLazySchema();
|
$schema = $this->createLazySchema();
|
||||||
$result = GraphQL::executeQuery($schema, $this->smallQuery);
|
$result = GraphQL::execute($schema, $this->smallQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createLazySchema()
|
private function createLazySchema()
|
||||||
{
|
{
|
||||||
return new Schema(
|
return new Schema(
|
||||||
SchemaConfig::create()
|
\GraphQL\Type\SchemaConfig::create()
|
||||||
->setQuery($this->schemaBuilder->buildQueryType())
|
->setQuery($this->schemaBuilder->buildQueryType())
|
||||||
->setTypeLoader(function($name) {
|
->setTypeLoader(function($name) {
|
||||||
return $this->schemaBuilder->loadType($name);
|
return $this->schemaBuilder->loadType($name);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Benchmarks;
|
namespace GraphQL\Benchmarks;
|
||||||
|
|
||||||
use GraphQL\GraphQL;
|
use GraphQL\GraphQL;
|
||||||
use GraphQL\Tests\StarWarsSchema;
|
use GraphQL\Tests\StarWarsSchema;
|
||||||
use GraphQL\Type\Introspection;
|
use GraphQL\Type\Introspection;
|
||||||
|
@ -36,7 +35,7 @@ class StarWarsBench
|
||||||
}
|
}
|
||||||
';
|
';
|
||||||
|
|
||||||
GraphQL::executeQuery(
|
GraphQL::execute(
|
||||||
StarWarsSchema::build(),
|
StarWarsSchema::build(),
|
||||||
$q
|
$q
|
||||||
);
|
);
|
||||||
|
@ -58,7 +57,7 @@ class StarWarsBench
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
';
|
';
|
||||||
GraphQL::executeQuery(
|
GraphQL::execute(
|
||||||
StarWarsSchema::build(),
|
StarWarsSchema::build(),
|
||||||
$q
|
$q
|
||||||
);
|
);
|
||||||
|
@ -82,7 +81,7 @@ class StarWarsBench
|
||||||
}
|
}
|
||||||
';
|
';
|
||||||
|
|
||||||
GraphQL::executeQuery(
|
GraphQL::execute(
|
||||||
StarWarsSchema::build(),
|
StarWarsSchema::build(),
|
||||||
$q
|
$q
|
||||||
);
|
);
|
||||||
|
@ -90,7 +89,7 @@ class StarWarsBench
|
||||||
|
|
||||||
public function benchStarWarsIntrospectionQuery()
|
public function benchStarWarsIntrospectionQuery()
|
||||||
{
|
{
|
||||||
GraphQL::executeQuery(
|
GraphQL::execute(
|
||||||
StarWarsSchema::build(),
|
StarWarsSchema::build(),
|
||||||
$this->introQuery
|
$this->introQuery
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,15 +7,12 @@ use GraphQL\Language\AST\NameNode;
|
||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Language\AST\SelectionSetNode;
|
use GraphQL\Language\AST\SelectionSetNode;
|
||||||
use GraphQL\Language\Printer;
|
use GraphQL\Language\Printer;
|
||||||
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\FieldDefinition;
|
use GraphQL\Type\Definition\FieldDefinition;
|
||||||
use GraphQL\Type\Definition\InterfaceType;
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\WrappingType;
|
use GraphQL\Type\Definition\WrappingType;
|
||||||
use GraphQL\Type\Schema;
|
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use function count;
|
|
||||||
use function max;
|
|
||||||
use function round;
|
|
||||||
|
|
||||||
class QueryGenerator
|
class QueryGenerator
|
||||||
{
|
{
|
||||||
|
@ -33,12 +30,10 @@ class QueryGenerator
|
||||||
|
|
||||||
$totalFields = 0;
|
$totalFields = 0;
|
||||||
foreach ($schema->getTypeMap() as $type) {
|
foreach ($schema->getTypeMap() as $type) {
|
||||||
if (! ($type instanceof ObjectType)) {
|
if ($type instanceof ObjectType) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$totalFields += count($type->getFields());
|
$totalFields += count($type->getFields());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->maxLeafFields = max(1, round($totalFields * $percentOfLeafFields));
|
$this->maxLeafFields = max(1, round($totalFields * $percentOfLeafFields));
|
||||||
$this->currentLeafFields = 0;
|
$this->currentLeafFields = 0;
|
||||||
|
@ -49,12 +44,13 @@ class QueryGenerator
|
||||||
$qtype = $this->schema->getQueryType();
|
$qtype = $this->schema->getQueryType();
|
||||||
|
|
||||||
$ast = new DocumentNode([
|
$ast = new DocumentNode([
|
||||||
'definitions' => [new OperationDefinitionNode([
|
'definitions' => [
|
||||||
|
new OperationDefinitionNode([
|
||||||
'name' => new NameNode(['value' => 'TestQuery']),
|
'name' => new NameNode(['value' => 'TestQuery']),
|
||||||
'operation' => 'query',
|
'operation' => 'query',
|
||||||
'selectionSet' => $this->buildSelectionSet($qtype->getFields()),
|
'selectionSet' => $this->buildSelectionSet($qtype->getFields())
|
||||||
]),
|
])
|
||||||
],
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return Printer::doPrint($ast);
|
return Printer::doPrint($ast);
|
||||||
|
@ -62,13 +58,12 @@ class QueryGenerator
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param FieldDefinition[] $fields
|
* @param FieldDefinition[] $fields
|
||||||
*
|
|
||||||
* @return SelectionSetNode
|
* @return SelectionSetNode
|
||||||
*/
|
*/
|
||||||
public function buildSelectionSet($fields)
|
public function buildSelectionSet($fields)
|
||||||
{
|
{
|
||||||
$selections[] = new FieldNode([
|
$selections[] = new FieldNode([
|
||||||
'name' => new NameNode(['value' => '__typename']),
|
'name' => new NameNode(['value' => '__typename'])
|
||||||
]);
|
]);
|
||||||
$this->currentLeafFields++;
|
$this->currentLeafFields++;
|
||||||
|
|
||||||
|
@ -92,12 +87,12 @@ class QueryGenerator
|
||||||
|
|
||||||
$selections[] = new FieldNode([
|
$selections[] = new FieldNode([
|
||||||
'name' => new NameNode(['value' => $field->name]),
|
'name' => new NameNode(['value' => $field->name]),
|
||||||
'selectionSet' => $selectionSet,
|
'selectionSet' => $selectionSet
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$selectionSet = new SelectionSetNode([
|
$selectionSet = new SelectionSetNode([
|
||||||
'selections' => $selections,
|
'selections' => $selections
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $selectionSet;
|
return $selectionSet;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Benchmarks\Utils;
|
namespace GraphQL\Benchmarks\Utils;
|
||||||
|
|
||||||
|
use GraphQL\Schema;
|
||||||
use GraphQL\Type\Definition\EnumType;
|
use GraphQL\Type\Definition\EnumType;
|
||||||
use GraphQL\Type\Definition\InputObjectType;
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Schema;
|
|
||||||
|
|
||||||
class SchemaGenerator
|
class SchemaGenerator
|
||||||
{
|
{
|
||||||
|
@ -152,7 +152,7 @@ class SchemaGenerator
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resolveField($objectValue, $args, $context, $resolveInfo)
|
public function resolveField($value, $args, $context, $resolveInfo)
|
||||||
{
|
{
|
||||||
return $resolveInfo->fieldName . '-value';
|
return $resolveInfo->fieldName . '-value';
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,33 +2,25 @@
|
||||||
"name": "webonyx/graphql-php",
|
"name": "webonyx/graphql-php",
|
||||||
"description": "A PHP port of GraphQL reference implementation",
|
"description": "A PHP port of GraphQL reference implementation",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"license": "MIT",
|
"license": "BSD",
|
||||||
"homepage": "https://github.com/webonyx/graphql-php",
|
"homepage": "https://github.com/webonyx/graphql-php",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"graphql",
|
"graphql",
|
||||||
"API"
|
"API"
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1||^8.0",
|
"php": ">=5.5,<8.0-DEV",
|
||||||
"ext-json": "*",
|
|
||||||
"ext-mbstring": "*"
|
"ext-mbstring": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/coding-standard": "^6.0",
|
"phpunit/phpunit": "^4.8",
|
||||||
"phpbench/phpbench": "^0.14.0",
|
"psr/http-message": "^1.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": {
|
"config": {
|
||||||
"preferred-install": "dist",
|
"bin-dir": "bin"
|
||||||
"sort-packages": true
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
"files": ["src/deprecated.php"],
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"GraphQL\\": "src/"
|
"GraphQL\\": "src/"
|
||||||
}
|
}
|
||||||
|
@ -43,14 +35,5 @@
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"react/promise": "To leverage async resolving on React PHP platform",
|
"react/promise": "To leverage async resolving on React PHP platform",
|
||||||
"psr/http-message": "To use standard GraphQL server"
|
"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,26 +1,14 @@
|
||||||
# Integrations
|
# Integrations
|
||||||
|
|
||||||
* [Standard Server](executing-queries.md/#using-server) – Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)).
|
- [Integration with Relay](https://github.com/ivome/graphql-relay-php)
|
||||||
* [Relay Library for graphql-php](https://github.com/ivome/graphql-relay-php) – Helps construct Relay related schema definitions.
|
- [Integration with Laravel 5](https://github.com/Folkloreatelier/laravel-graphql) + [Relay Helpers for Laravel](https://github.com/nuwave/laravel-graphql-relay)
|
||||||
* [Lighthouse](https://github.com/nuwave/lighthouse) – Laravel based, uses Schema Definition Language
|
- [Symfony Bundle](https://github.com/overblog/GraphQLBundle) by Overblog
|
||||||
* [Laravel GraphQL](https://github.com/rebing/graphql-laravel) - Laravel wrapper for Facebook's GraphQL
|
- 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))
|
||||||
* [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) – Bundle for Symfony
|
- 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)
|
||||||
* [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress
|
|
||||||
|
|
||||||
# GraphQL PHP Tools
|
# Tools
|
||||||
|
- [GraphiQL](https://github.com/graphql/graphiql) - An in-browser IDE for exploring GraphQL
|
||||||
* [GraphQLite](https://graphqlite.thecodingmachine.io) – Define your complete schema with annotations
|
- [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
|
||||||
* [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) – Define types with Doctrine ORM annotations
|
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) -
|
||||||
* [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
|
|
||||||
|
|
||||||
* [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) –
|
|
||||||
GraphiQL as Google Chrome extension
|
GraphiQL as Google Chrome extension
|
||||||
|
- [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
|
|
@ -103,24 +103,22 @@ for a field you simply override this default resolver.
|
||||||
**graphql-php** provides following default field resolver:
|
**graphql-php** provides following default field resolver:
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
function defaultFieldResolver($objectValue, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
|
function defaultFieldResolver($source, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
|
||||||
{
|
{
|
||||||
$fieldName = $info->fieldName;
|
$fieldName = $info->fieldName;
|
||||||
$property = null;
|
$property = null;
|
||||||
|
|
||||||
if (is_array($objectValue) || $objectValue instanceof \ArrayAccess) {
|
if (is_array($source) || $source instanceof \ArrayAccess) {
|
||||||
if (isset($objectValue[$fieldName])) {
|
if (isset($source[$fieldName])) {
|
||||||
$property = $objectValue[$fieldName];
|
$property = $source[$fieldName];
|
||||||
}
|
}
|
||||||
} elseif (is_object($objectValue)) {
|
} else if (is_object($source)) {
|
||||||
if (isset($objectValue->{$fieldName})) {
|
if (isset($source->{$fieldName})) {
|
||||||
$property = $objectValue->{$fieldName};
|
$property = $source->{$fieldName};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $property instanceof Closure
|
return $property instanceof \Closure ? $property($source, $args, $context) : $property;
|
||||||
? $property($objectValue, $args, $context, $info)
|
|
||||||
: $property;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -163,6 +161,7 @@ $userType = new ObjectType([
|
||||||
Keep in mind that **field resolver** has precedence over **default field resolver per type** which in turn
|
Keep in mind that **field resolver** has precedence over **default field resolver per type** which in turn
|
||||||
has precedence over **default field resolver**.
|
has precedence over **default field resolver**.
|
||||||
|
|
||||||
|
|
||||||
# Solving N+1 Problem
|
# Solving N+1 Problem
|
||||||
Since: 0.9.0
|
Since: 0.9.0
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ This will make each error entry to look like this:
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
If you prefer the first resolver exception to be re-thrown, use following flags:
|
If you prefer first resolver exception to be re-thrown, use following flags:
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
use GraphQL\GraphQL;
|
use GraphQL\GraphQL;
|
||||||
|
@ -127,9 +127,6 @@ $debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::RETHROW_INTERNAL_EXCEPTIONS;
|
||||||
$result = GraphQL::executeQuery(/*args*/)->toArray($debug);
|
$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
|
# Custom Error Handling and Formatting
|
||||||
It is possible to define custom **formatter** and **handler** for result errors.
|
It is possible to define custom **formatter** and **handler** for result errors.
|
||||||
|
|
||||||
|
@ -154,7 +151,7 @@ $myErrorHandler = function(array $errors, callable $formatter) {
|
||||||
|
|
||||||
$result = GraphQL::executeQuery(/* $args */)
|
$result = GraphQL::executeQuery(/* $args */)
|
||||||
->setErrorFormatter($myErrorFormatter)
|
->setErrorFormatter($myErrorFormatter)
|
||||||
->setErrorsHandler($myErrorHandler)
|
->setErrorHandler($myErrorHandler)
|
||||||
->toArray();
|
->toArray();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ $psrResponse = new SomePsr7ResponseImplementation(json_encode($result));
|
||||||
PSR-7 is useful when you want to integrate the server into existing framework:
|
PSR-7 is useful when you want to integrate the server into existing framework:
|
||||||
|
|
||||||
- [PSR-7 for Laravel](https://laravel.com/docs/5.1/requests#psr7-requests)
|
- [PSR-7 for Laravel](https://laravel.com/docs/5.1/requests#psr7-requests)
|
||||||
- [Symfony PSR-7 Bridge](https://symfony.com/doc/current/components/psr7.html)
|
- [Symfony PSR-7 Bridge](https://symfony.com/doc/current/request/psr7.html)
|
||||||
- [Slim](https://www.slimframework.com/docs/concepts/value-objects.html)
|
- [Slim](https://www.slimframework.com/docs/concepts/value-objects.html)
|
||||||
- [Zend Expressive](http://zendframework.github.io/zend-expressive/)
|
- [Zend Expressive](http://zendframework.github.io/zend-expressive/)
|
||||||
|
|
||||||
|
@ -136,13 +136,13 @@ So for example following batch will require single DB request (if user field is
|
||||||
```json
|
```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
|
# Installation
|
||||||
|
|
||||||
Using [composer](https://getcomposer.org/doc/00-intro.md), run:
|
Using [composer](https://getcomposer.org/doc/00-intro.md), simply run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
composer require webonyx/graphql-php
|
composer require webonyx/graphql-php
|
||||||
|
@ -54,8 +54,8 @@ $queryType = new ObjectType([
|
||||||
'args' => [
|
'args' => [
|
||||||
'message' => Type::nonNull(Type::string()),
|
'message' => Type::nonNull(Type::string()),
|
||||||
],
|
],
|
||||||
'resolve' => function ($rootValue, $args) {
|
'resolve' => function ($root, $args) {
|
||||||
return $rootValue['prefix'] . $args['message'];
|
return $root['prefix'] . $args['message'];
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -106,7 +106,7 @@ echo json_encode($output);
|
||||||
|
|
||||||
Our example is finished. Try it by running:
|
Our example is finished. Try it by running:
|
||||||
```sh
|
```sh
|
||||||
php -S localhost:8080 graphql.php
|
php -S localhost:8000 graphql.php
|
||||||
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
|
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
# About GraphQL
|
# About GraphQL
|
||||||
|
|
||||||
GraphQL is a modern way to build HTTP APIs consumed by the web and mobile clients.
|
GraphQL is a modern way to build HTTP APIs consumed by the web and mobile clients.
|
||||||
It is intended to be an alternative to REST and SOAP APIs (even for **existing applications**).
|
It is intended to be a replacement for REST and SOAP APIs (even for **existing applications**).
|
||||||
|
|
||||||
GraphQL itself is a [specification](https://github.com/facebook/graphql) designed by Facebook
|
GraphQL itself is a [specification](https://github.com/facebook/graphql) designed by Facebook
|
||||||
engineers. Various implementations of this specification were written
|
engineers. Various implementations of this specification were written
|
||||||
|
@ -34,9 +34,9 @@ Library features include:
|
||||||
- Parsing, validating and [executing GraphQL queries](executing-queries.md) against this Type System
|
- Parsing, validating and [executing GraphQL queries](executing-queries.md) against this Type System
|
||||||
- Rich [error reporting](error-handling.md), including query validation and execution errors
|
- Rich [error reporting](error-handling.md), including query validation and execution errors
|
||||||
- Optional tools for [parsing GraphQL Type language](type-system/type-language.md)
|
- Optional tools for [parsing GraphQL Type language](type-system/type-language.md)
|
||||||
- Tools for [batching requests](data-fetching.md#solving-n1-problem) to backend storage
|
- Tools for [batching requests](data-fetching.md/#solving-n1-problem) to backend storage
|
||||||
- [Async PHP platforms support](data-fetching.md#async-php) via promises
|
- [Async PHP platforms support](data-fetching.md/#async-php) via promises
|
||||||
- [Standard HTTP server](executing-queries.md#using-server)
|
- [Standard HTTP server](executing-queries.md/#using-server)
|
||||||
|
|
||||||
Also, several [complementary tools](complementary-tools.md) are available which provide integrations with
|
Also, several [complementary tools](complementary-tools.md) are available which provide integrations with
|
||||||
existing PHP frameworks, add support for Relay, etc.
|
existing PHP frameworks, add support for Relay, etc.
|
||||||
|
@ -44,12 +44,12 @@ existing PHP frameworks, add support for Relay, etc.
|
||||||
## Current Status
|
## Current Status
|
||||||
The first version of this library (v0.1) was released on August 10th 2015.
|
The first version of this library (v0.1) was released on August 10th 2015.
|
||||||
|
|
||||||
The current version supports all features described by GraphQL specification
|
The current version (v0.10) supports all features described by GraphQL specification
|
||||||
as well as some experimental features like
|
(including April 2016 add-ons) as well as some experimental features like
|
||||||
[Schema Language parser](type-system/type-language.md) and
|
[Schema Language parser](type-system/type-language.md) and
|
||||||
[Schema printer](reference.md#graphqlutilsschemaprinter).
|
[Schema printer](reference.md#graphqlutilsschemaprinter).
|
||||||
|
|
||||||
Ready for real-world usage.
|
Ready for real-world usage.
|
||||||
|
|
||||||
## GitHub
|
## Github
|
||||||
Project source code is [hosted on GitHub](https://github.com/webonyx/graphql-php).
|
Project source code is [hosted on GitHub](https://github.com/webonyx/graphql-php).
|
||||||
|
|
1004
docs/reference.md
1004
docs/reference.md
File diff suppressed because it is too large
Load diff
|
@ -35,9 +35,9 @@ In **graphql-php** custom directive is an instance of `GraphQL\Type\Definition\D
|
||||||
|
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
use GraphQL\Language\DirectiveLocation;
|
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Definition\Directive;
|
use GraphQL\Type\Definition\Directive;
|
||||||
|
use GraphQL\Type\Definition\DirectiveLocation;
|
||||||
use GraphQL\Type\Definition\FieldArgument;
|
use GraphQL\Type\Definition\FieldArgument;
|
||||||
|
|
||||||
$trackDirective = new Directive([
|
$trackDirective = new Directive([
|
||||||
|
@ -58,4 +58,4 @@ $trackDirective = new Directive([
|
||||||
```
|
```
|
||||||
|
|
||||||
See possible directive locations in
|
See possible directive locations in
|
||||||
[`GraphQL\Language\DirectiveLocation`](../reference.md#graphqllanguagedirectivelocation).
|
[`GraphQL\Type\Definition\DirectiveLocation`](../reference.md#graphqltypedefinitiondirectivelocation).
|
||||||
|
|
|
@ -158,7 +158,7 @@ $heroType = new ObjectType([
|
||||||
'args' => [
|
'args' => [
|
||||||
'episode' => Type::nonNull($enumType)
|
'episode' => Type::nonNull($enumType)
|
||||||
],
|
],
|
||||||
'resolve' => function($hero, $args) {
|
'resolve' => function($_value, $args) {
|
||||||
return $args['episode'] === 5 ? true : false;
|
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
|
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)
|
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)
|
description | `string` | Plain-text description of this input field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
|
||||||
defaultValue | `scalar` | Default value of this input field. Use the internal value if specifying a default for an **enum** type
|
defaultValue | `scalar` | Default value of this input field
|
||||||
|
|
||||||
# Using Input Object Type
|
# Using Input Object Type
|
||||||
In the example above we defined our InputObjectType. Now let's use it in one of field arguments:
|
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),
|
'type' => Type::listOf($storyType),
|
||||||
'args' => [
|
'args' => [
|
||||||
'filters' => [
|
'filters' => [
|
||||||
'type' => $filters,
|
'type' => Type::nonNull($filters),
|
||||||
'defaultValue' => [
|
'defaultValue' => [
|
||||||
'popular' => true
|
'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)
|
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))
|
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.
|
args | `array` | An array of possible type arguments. Each entry is expected to be an array with keys: **name**, **type**, **description**, **defaultValue**. See [Field Arguments](#field-arguments) section below.
|
||||||
resolve | `callable` | **function($objectValue, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$objectValue** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
|
resolve | `callable` | **function($value, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$value** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
|
||||||
complexity | `callable` | **function($childrenComplexity, $args)**<br> Used to restrict query complexity. The feature is disabled by default, read about [Security](../security.md#query-complexity-analysis) to use it.
|
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)
|
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)
|
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
|
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)
|
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)
|
description | `string` | Plain-text description of this argument for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
|
||||||
defaultValue | `scalar` | Default value for this argument. Use the internal value if specifying a default for an **enum** type
|
defaultValue | `scalar` | Default value for this argument
|
||||||
|
|
||||||
# Shorthand field definitions
|
# Shorthand field definitions
|
||||||
Fields can be also defined in **shorthand** notation (with only **name** and **type** options):
|
Fields can be also defined in **shorthand** notation (with only **name** and **type** options):
|
||||||
|
|
|
@ -96,11 +96,10 @@ class EmailType extends ScalarType
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param \GraphQL\Language\AST\Node $valueNode
|
* @param \GraphQL\Language\AST\Node $valueNode
|
||||||
* @param array|null $variables
|
|
||||||
* @return string
|
* @return string
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
public function parseLiteral($valueNode, array $variables = null)
|
public function parseLiteral($valueNode)
|
||||||
{
|
{
|
||||||
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
|
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
|
||||||
// error location in query:
|
// error location in query:
|
||||||
|
@ -125,6 +124,6 @@ $emailType = new CustomScalarType([
|
||||||
'name' => 'Email',
|
'name' => 'Email',
|
||||||
'serialize' => function($value) {/* See function body above */},
|
'serialize' => function($value) {/* See function body above */},
|
||||||
'parseValue' => function($value) {/* See function body above */},
|
'parseValue' => function($value) {/* See function body above */},
|
||||||
'parseLiteral' => function($valueNode, array $variables = null) {/* See function body above */},
|
'parseLiteral' => function($valueNode) {/* See function body above */},
|
||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
|
|
|
@ -62,7 +62,7 @@ $mutationType = new ObjectType([
|
||||||
'episode' => $episodeEnum,
|
'episode' => $episodeEnum,
|
||||||
'review' => $reviewInputObject
|
'review' => $reviewInputObject
|
||||||
],
|
],
|
||||||
'resolve' => function($rootValue, $args) {
|
'resolve' => function($val, $args) {
|
||||||
// TODOC
|
// TODOC
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# Defining your schema
|
# Defining your schema
|
||||||
Since 0.9.0
|
|
||||||
|
|
||||||
[Type language](http://graphql.org/learn/schema/#type-language) is a convenient way to define your schema,
|
[Type language](http://graphql.org/learn/schema/#type-language) is a convenient way to define your schema,
|
||||||
especially with IDE autocompletion and syntax validation.
|
especially with IDE autocompletion and syntax validation.
|
||||||
|
@ -33,14 +32,13 @@ $contents = file_get_contents('schema.graphql');
|
||||||
$schema = BuildSchema::build($contents);
|
$schema = BuildSchema::build($contents);
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, such schema is created without any resolvers.
|
By default, such schema is created without any resolvers. As a result, it doesn't support **Interfaces** and **Unions**
|
||||||
|
because it is impossible to resolve actual implementations during execution.
|
||||||
|
|
||||||
We have to rely on [default field resolver](../data-fetching.md#default-field-resolver) and **root value** in
|
Also, 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.
|
order to execute a query against this schema.
|
||||||
|
|
||||||
# Defining resolvers
|
# Defining resolvers
|
||||||
Since 0.10.0
|
|
||||||
|
|
||||||
In order to enable **Interfaces**, **Unions** and custom field resolvers you can pass the second argument:
|
In order to enable **Interfaces**, **Unions** and custom field resolvers you can pass the second argument:
|
||||||
**type config decorator** to schema builder.
|
**type config decorator** to schema builder.
|
||||||
|
|
||||||
|
@ -63,8 +61,6 @@ $schema = BuildSchema::build($contents, $typeConfigDecorator);
|
||||||
```
|
```
|
||||||
|
|
||||||
# Performance considerations
|
# Performance considerations
|
||||||
Since 0.10.0
|
|
||||||
|
|
||||||
Method **build()** produces a [lazy schema](schema.md#lazy-loading-of-types)
|
Method **build()** produces a [lazy schema](schema.md#lazy-loading-of-types)
|
||||||
automatically, so it works efficiently even with very large schemas.
|
automatically, so it works efficiently even with very large schemas.
|
||||||
|
|
||||||
|
@ -81,7 +77,7 @@ $cacheFilename = 'cached_schema.php';
|
||||||
|
|
||||||
if (!file_exists($cacheFilename)) {
|
if (!file_exists($cacheFilename)) {
|
||||||
$document = Parser::parse(file_get_contents('./schema.graphql'));
|
$document = Parser::parse(file_get_contents('./schema.graphql'));
|
||||||
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true) . ";\n");
|
file_put_contents($cacheFilename, "<?php\nreturn " . var_export($document->toArray(), true));
|
||||||
} else {
|
} else {
|
||||||
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
|
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@ try {
|
||||||
'args' => [
|
'args' => [
|
||||||
'message' => ['type' => Type::string()],
|
'message' => ['type' => Type::string()],
|
||||||
],
|
],
|
||||||
'resolve' => function ($rootValue, $args) {
|
'resolve' => function ($root, $args) {
|
||||||
return $rootValue['prefix'] . $args['message'];
|
return $root['prefix'] . $args['message'];
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -35,7 +35,7 @@ try {
|
||||||
'x' => ['type' => Type::int()],
|
'x' => ['type' => Type::int()],
|
||||||
'y' => ['type' => Type::int()],
|
'y' => ['type' => Type::int()],
|
||||||
],
|
],
|
||||||
'resolve' => function ($calc, $args) {
|
'resolve' => function ($root, $args) {
|
||||||
return $args['x'] + $args['y'];
|
return $args['x'] + $args['y'];
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -35,12 +35,12 @@ class CommentType extends ObjectType
|
||||||
Types::htmlField('body')
|
Types::htmlField('body')
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
'resolveField' => function($comment, $args, $context, ResolveInfo $info) {
|
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||||
$method = 'resolve' . ucfirst($info->fieldName);
|
$method = 'resolve' . ucfirst($info->fieldName);
|
||||||
if (method_exists($this, $method)) {
|
if (method_exists($this, $method)) {
|
||||||
return $this->{$method}($comment, $args, $context, $info);
|
return $this->{$method}($value, $args, $context, $info);
|
||||||
} else {
|
} else {
|
||||||
return $comment->{$info->fieldName};
|
return $value->{$info->fieldName};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
@ -57,8 +57,8 @@ class QueryType extends ObjectType
|
||||||
],
|
],
|
||||||
'hello' => Type::string()
|
'hello' => Type::string()
|
||||||
],
|
],
|
||||||
'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) {
|
'resolveField' => function($val, $args, $context, ResolveInfo $info) {
|
||||||
return $this->{$info->fieldName}($rootValue, $args, $context, $info);
|
return $this->{$info->fieldName}($val, $args, $context, $info);
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
parent::__construct($config);
|
parent::__construct($config);
|
||||||
|
|
|
@ -30,12 +30,11 @@ class UrlType extends ScalarType
|
||||||
*
|
*
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @return mixed
|
* @return mixed
|
||||||
* @throws Error
|
|
||||||
*/
|
*/
|
||||||
public function parseValue($value)
|
public function parseValue($value)
|
||||||
{
|
{
|
||||||
if (!is_string($value) || !filter_var($value, FILTER_VALIDATE_URL)) { // quite naive, but after all this is example
|
if (!is_string($value) || !filter_var($value, FILTER_VALIDATE_URL)) { // quite naive, but after all this is example
|
||||||
throw new Error("Cannot represent value as URL: " . Utils::printSafe($value));
|
throw new \UnexpectedValueException("Cannot represent value as URL: " . Utils::printSafe($value));
|
||||||
}
|
}
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
@ -43,21 +42,20 @@ class UrlType extends ScalarType
|
||||||
/**
|
/**
|
||||||
* Parses an externally provided literal value to use as an input (e.g. in Query AST)
|
* Parses an externally provided literal value to use as an input (e.g. in Query AST)
|
||||||
*
|
*
|
||||||
* @param Node $valueNode
|
* @param $ast Node
|
||||||
* @param array|null $variables
|
|
||||||
* @return null|string
|
* @return null|string
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
public function parseLiteral($valueNode, array $variables = null)
|
public function parseLiteral($ast)
|
||||||
{
|
{
|
||||||
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
|
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
|
||||||
// error location in query:
|
// error location in query:
|
||||||
if (!($valueNode instanceof StringValueNode)) {
|
if (!($ast instanceof StringValueNode)) {
|
||||||
throw new Error('Query error: Can only parse strings got: ' . $valueNode->kind, [$valueNode]);
|
throw new Error('Query error: Can only parse strings got: ' . $ast->kind, [$ast]);
|
||||||
}
|
}
|
||||||
if (!is_string($valueNode->value) || !filter_var($valueNode->value, FILTER_VALIDATE_URL)) {
|
if (!is_string($ast->value) || !filter_var($ast->value, FILTER_VALIDATE_URL)) {
|
||||||
throw new Error('Query error: Not a valid URL', [$valueNode]);
|
throw new Error('Query error: Not a valid URL', [$ast]);
|
||||||
}
|
}
|
||||||
return $valueNode->value;
|
return $ast->value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,12 +75,12 @@ class StoryType extends ObjectType
|
||||||
'interfaces' => [
|
'interfaces' => [
|
||||||
Types::node()
|
Types::node()
|
||||||
],
|
],
|
||||||
'resolveField' => function($story, $args, $context, ResolveInfo $info) {
|
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||||
$method = 'resolve' . ucfirst($info->fieldName);
|
$method = 'resolve' . ucfirst($info->fieldName);
|
||||||
if (method_exists($this, $method)) {
|
if (method_exists($this, $method)) {
|
||||||
return $this->{$method}($story, $args, $context, $info);
|
return $this->{$method}($value, $args, $context, $info);
|
||||||
} else {
|
} else {
|
||||||
return $story->{$info->fieldName};
|
return $value->{$info->fieldName};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
@ -44,12 +44,12 @@ class UserType extends ObjectType
|
||||||
'interfaces' => [
|
'interfaces' => [
|
||||||
Types::node()
|
Types::node()
|
||||||
],
|
],
|
||||||
'resolveField' => function($user, $args, $context, ResolveInfo $info) {
|
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
|
||||||
$method = 'resolve' . ucfirst($info->fieldName);
|
$method = 'resolve' . ucfirst($info->fieldName);
|
||||||
if (method_exists($this, $method)) {
|
if (method_exists($this, $method)) {
|
||||||
return $this->{$method}($user, $args, $context, $info);
|
return $this->{$method}($value, $args, $context, $info);
|
||||||
} else {
|
} else {
|
||||||
return $user->{$info->fieldName};
|
return $value->{$info->fieldName};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
@ -35,11 +35,10 @@ try {
|
||||||
// Parse incoming query and variables
|
// Parse incoming query and variables
|
||||||
if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) {
|
if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) {
|
||||||
$raw = file_get_contents('php://input') ?: '';
|
$raw = file_get_contents('php://input') ?: '';
|
||||||
$data = json_decode($raw, true) ?: [];
|
$data = json_decode($raw, true);
|
||||||
} else {
|
} else {
|
||||||
$data = $_REQUEST;
|
$data = $_REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data += ['query' => null, 'variables' => null];
|
$data += ['query' => null, 'variables' => null];
|
||||||
|
|
||||||
if (null === $data['query']) {
|
if (null === $data['query']) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ try {
|
||||||
$query = $input['query'];
|
$query = $input['query'];
|
||||||
$variableValues = isset($input['variables']) ? $input['variables'] : null;
|
$variableValues = isset($input['variables']) ? $input['variables'] : null;
|
||||||
|
|
||||||
$result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variableValues);
|
$result = GraphQL::execute($schema, $query, $rootValue, null, $variableValues);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$result = [
|
$result = [
|
||||||
'error' => [
|
'error' => [
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
interface Resolver {
|
interface Resolver {
|
||||||
public function resolve($rootValue, $args, $context);
|
public function resolve($root, $args, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Addition implements Resolver
|
class Addition implements Resolver
|
||||||
{
|
{
|
||||||
public function resolve($rootValue, $args, $context)
|
public function resolve($root, $args, $context)
|
||||||
{
|
{
|
||||||
return $args['x'] + $args['y'];
|
return $args['x'] + $args['y'];
|
||||||
}
|
}
|
||||||
|
@ -14,22 +14,22 @@ class Addition implements Resolver
|
||||||
|
|
||||||
class Echoer implements Resolver
|
class Echoer implements Resolver
|
||||||
{
|
{
|
||||||
public function resolve($rootValue, $args, $context)
|
public function resolve($root, $args, $context)
|
||||||
{
|
{
|
||||||
return $rootValue['prefix'].$args['message'];
|
return $root['prefix'].$args['message'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'sum' => function($rootValue, $args, $context) {
|
'sum' => function($root, $args, $context) {
|
||||||
$sum = new Addition();
|
$sum = new Addition();
|
||||||
|
|
||||||
return $sum->resolve($rootValue, $args, $context);
|
return $sum->resolve($root, $args, $context);
|
||||||
},
|
},
|
||||||
'echo' => function($rootValue, $args, $context) {
|
'echo' => function($root, $args, $context) {
|
||||||
$echo = new Echoer();
|
$echo = new Echoer();
|
||||||
|
|
||||||
return $echo->resolve($rootValue, $args, $context);
|
return $echo->resolve($root, $args, $context);
|
||||||
},
|
},
|
||||||
'prefix' => 'You said: ',
|
'prefix' => 'You said: ',
|
||||||
];
|
];
|
||||||
|
|
|
@ -10,10 +10,10 @@ php -S localhost:8080 ./graphql.php
|
||||||
|
|
||||||
### Try query
|
### Try query
|
||||||
```
|
```
|
||||||
curl -d '{"query": "query { echo(message: \"Hello World\") }" }' -H "Content-Type: application/json" http://localhost:8080
|
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Try mutation
|
### Try mutation
|
||||||
```
|
```
|
||||||
curl -d '{"query": "mutation { sum(x: 2, y: 2) }" }' -H "Content-Type: application/json" http://localhost:8080
|
curl http://localhost:8080 -d '{"query": "mutation { sum(x: 2, y: 2) }" }'
|
||||||
```
|
```
|
||||||
|
|
|
@ -19,8 +19,8 @@ try {
|
||||||
'args' => [
|
'args' => [
|
||||||
'message' => ['type' => Type::string()],
|
'message' => ['type' => Type::string()],
|
||||||
],
|
],
|
||||||
'resolve' => function ($rootValue, $args) {
|
'resolve' => function ($root, $args) {
|
||||||
return $rootValue['prefix'] . $args['message'];
|
return $root['prefix'] . $args['message'];
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -35,7 +35,7 @@ try {
|
||||||
'x' => ['type' => Type::int()],
|
'x' => ['type' => Type::int()],
|
||||||
'y' => ['type' => Type::int()],
|
'y' => ['type' => Type::int()],
|
||||||
],
|
],
|
||||||
'resolve' => function ($calc, $args) {
|
'resolve' => function ($root, $args) {
|
||||||
return $args['x'] + $args['y'];
|
return $args['x'] + $args['y'];
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
102
phpcs.xml.dist
102
phpcs.xml.dist
|
@ -1,102 +0,0 @@
|
||||||
<?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>
|
|
|
@ -1,17 +0,0 @@
|
||||||
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,13 +1,16 @@
|
||||||
<?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>
|
<testsuites>
|
||||||
<testsuite name="webonyx/graphql-php Test Suite">
|
<testsuite name="webonyx/graphql-php Test Suite">
|
||||||
<directory>./tests/</directory>
|
<directory>./tests/</directory>
|
||||||
|
@ -23,6 +26,21 @@
|
||||||
<filter>
|
<filter>
|
||||||
<whitelist>
|
<whitelist>
|
||||||
<directory suffix=".php">./src</directory>
|
<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>
|
</whitelist>
|
||||||
</filter>
|
</filter>
|
||||||
|
|
||||||
|
<php>
|
||||||
|
<ini name="error_reporting" value="E_ALL"/>
|
||||||
|
</php>
|
||||||
|
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|
|
@ -1,25 +1,40 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL;
|
namespace GraphQL;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use GraphQL\Executor\Promise\Adapter\SyncPromise;
|
use GraphQL\Executor\Promise\Adapter\SyncPromise;
|
||||||
use SplQueue;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
class Deferred
|
class Deferred
|
||||||
{
|
{
|
||||||
/** @var SplQueue|null */
|
/**
|
||||||
|
* @var \SplQueue
|
||||||
|
*/
|
||||||
private static $queue;
|
private static $queue;
|
||||||
|
|
||||||
/** @var callable */
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
private $callback;
|
private $callback;
|
||||||
|
|
||||||
/** @var SyncPromise */
|
/**
|
||||||
|
* @var SyncPromise
|
||||||
|
*/
|
||||||
public $promise;
|
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)
|
public function __construct(callable $callback)
|
||||||
{
|
{
|
||||||
$this->callback = $callback;
|
$this->callback = $callback;
|
||||||
|
@ -27,38 +42,19 @@ class Deferred
|
||||||
self::getQueue()->enqueue($this);
|
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)
|
public function then($onFulfilled = null, $onRejected = null)
|
||||||
{
|
{
|
||||||
return $this->promise->then($onFulfilled, $onRejected);
|
return $this->promise->then($onFulfilled, $onRejected);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run() : void
|
private function run()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$cb = $this->callback;
|
$cb = $this->callback;
|
||||||
$this->promise->resolve($cb());
|
$this->promise->resolve($cb());
|
||||||
} catch (Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->promise->reject($e);
|
$this->promise->reject($e);
|
||||||
} catch (Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->promise->reject($e);
|
$this->promise->reject($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
src/Error.php
Normal file
18
src/Error.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
namespace GraphQL;
|
||||||
|
|
||||||
|
trigger_error(
|
||||||
|
'GraphQL\Error was moved to GraphQL\Error\Error and will be deleted on next release',
|
||||||
|
E_USER_DEPRECATED
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Error
|
||||||
|
*
|
||||||
|
* @deprecated as of 8.0 in favor of GraphQL\Error\Error
|
||||||
|
* @package GraphQL
|
||||||
|
*/
|
||||||
|
class Error extends \GraphQL\Error\Error
|
||||||
|
{
|
||||||
|
}
|
|
@ -1,7 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Error;
|
namespace GraphQL\Error;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,9 +14,8 @@ interface ClientAware
|
||||||
/**
|
/**
|
||||||
* Returns true when exception message is safe to be displayed to a client.
|
* Returns true when exception message is safe to be displayed to a client.
|
||||||
*
|
*
|
||||||
* @return bool
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isClientSafe();
|
public function isClientSafe();
|
||||||
|
|
||||||
|
@ -28,9 +24,8 @@ interface ClientAware
|
||||||
*
|
*
|
||||||
* Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.
|
* Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.
|
||||||
*
|
*
|
||||||
* @return string
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getCategory();
|
public function getCategory();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Error;
|
namespace GraphQL\Error;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,5 +9,4 @@ class Debug
|
||||||
const INCLUDE_DEBUG_MESSAGE = 1;
|
const INCLUDE_DEBUG_MESSAGE = 1;
|
||||||
const INCLUDE_TRACE = 2;
|
const INCLUDE_TRACE = 2;
|
||||||
const RETHROW_INTERNAL_EXCEPTIONS = 4;
|
const RETHROW_INTERNAL_EXCEPTIONS = 4;
|
||||||
const RETHROW_UNSAFE_EXCEPTIONS = 8;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Error;
|
namespace GraphQL\Error;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use GraphQL\Language\AST\Node;
|
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
use GraphQL\Utils\Utils;
|
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
|
* Describes an Error found during the parse, validate, or
|
||||||
|
@ -32,7 +19,7 @@ use function iterator_to_array;
|
||||||
* Class extends standard PHP `\Exception`, so all standard methods of base `\Exception` class
|
* Class extends standard PHP `\Exception`, so all standard methods of base `\Exception` class
|
||||||
* are available in addition to those listed below.
|
* 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';
|
const CATEGORY_INTERNAL = 'internal';
|
||||||
|
@ -44,103 +31,56 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
||||||
*/
|
*/
|
||||||
public $message;
|
public $message;
|
||||||
|
|
||||||
/** @var SourceLocation[] */
|
/**
|
||||||
|
* @var SourceLocation[]
|
||||||
|
*/
|
||||||
private $locations;
|
private $locations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array describing the JSON-path into the execution response which
|
* An array describing the JSON-path into the execution response which
|
||||||
* corresponds to this error. Only included for errors during execution.
|
* corresponds to this error. Only included for errors during execution.
|
||||||
*
|
*
|
||||||
* @var mixed[]|null
|
* @var array
|
||||||
*/
|
*/
|
||||||
public $path;
|
public $path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of GraphQL AST Nodes corresponding to this error.
|
* An array of GraphQL AST Nodes corresponding to this error.
|
||||||
*
|
*
|
||||||
* @var Node[]|null
|
* @var array
|
||||||
*/
|
*/
|
||||||
public $nodes;
|
public $nodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The source GraphQL document for the first location of this error.
|
* The source GraphQL document corresponding to this error.
|
||||||
*
|
|
||||||
* Note that if this Error represents more than one node, the source may not
|
|
||||||
* represent nodes after the first node.
|
|
||||||
*
|
*
|
||||||
* @var Source|null
|
* @var Source|null
|
||||||
*/
|
*/
|
||||||
private $source;
|
private $source;
|
||||||
|
|
||||||
/** @var int[]|null */
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
private $positions;
|
private $positions;
|
||||||
|
|
||||||
/** @var bool */
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
private $isClientSafe;
|
private $isClientSafe;
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
protected $category;
|
|
||||||
|
|
||||||
/** @var mixed[]|null */
|
|
||||||
protected $extensions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $message
|
* @var string
|
||||||
* @param Node|Node[]|Traversable|null $nodes
|
|
||||||
* @param mixed[]|null $positions
|
|
||||||
* @param mixed[]|null $path
|
|
||||||
* @param Throwable $previous
|
|
||||||
* @param mixed[] $extensions
|
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
protected $category;
|
||||||
$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
|
* Given an arbitrary Error, presumably thrown while attempting to execute a
|
||||||
* GraphQL operation, produce a new GraphQLError aware of the location in the
|
* GraphQL operation, produce a new GraphQLError aware of the location in the
|
||||||
* document responsible for the original Error.
|
* document responsible for the original Error.
|
||||||
*
|
*
|
||||||
* @param mixed $error
|
* @param $error
|
||||||
* @param Node[]|null $nodes
|
* @param array|null $nodes
|
||||||
* @param mixed[]|null $path
|
* @param array|null $path
|
||||||
*
|
|
||||||
* @return Error
|
* @return Error
|
||||||
*/
|
*/
|
||||||
public static function createLocatedError($error, $nodes = null, $path = null)
|
public static function createLocatedError($error, $nodes = null, $path = null)
|
||||||
|
@ -148,14 +88,13 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
||||||
if ($error instanceof self) {
|
if ($error instanceof self) {
|
||||||
if ($error->path && $error->nodes) {
|
if ($error->path && $error->nodes) {
|
||||||
return $error;
|
return $error;
|
||||||
}
|
} else {
|
||||||
|
|
||||||
$nodes = $nodes ?: $error->nodes;
|
$nodes = $nodes ?: $error->nodes;
|
||||||
$path = $path ?: $error->path;
|
$path = $path ?: $error->path;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$source = $positions = $originalError = null;
|
$source = $positions = $originalError = null;
|
||||||
$extensions = [];
|
|
||||||
|
|
||||||
if ($error instanceof self) {
|
if ($error instanceof self) {
|
||||||
$message = $error->getMessage();
|
$message = $error->getMessage();
|
||||||
|
@ -163,8 +102,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
||||||
$nodes = $error->nodes ?: $nodes;
|
$nodes = $error->nodes ?: $nodes;
|
||||||
$source = $error->source;
|
$source = $error->source;
|
||||||
$positions = $error->positions;
|
$positions = $error->positions;
|
||||||
$extensions = $error->extensions;
|
} else if ($error instanceof \Exception || $error instanceof \Throwable) {
|
||||||
} elseif ($error instanceof Exception || $error instanceof Throwable) {
|
|
||||||
$message = $error->getMessage();
|
$message = $error->getMessage();
|
||||||
$originalError = $error;
|
$originalError = $error;
|
||||||
} else {
|
} else {
|
||||||
|
@ -177,19 +115,60 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
||||||
$source,
|
$source,
|
||||||
$positions,
|
$positions,
|
||||||
$path,
|
$path,
|
||||||
$originalError,
|
$originalError
|
||||||
$extensions
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed[]
|
* @param Error $error
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function formatError(Error $error)
|
public static function formatError(Error $error)
|
||||||
{
|
{
|
||||||
return $error->toSerializableArray();
|
return $error->toSerializableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $message
|
||||||
|
* @param array|null $nodes
|
||||||
|
* @param Source $source
|
||||||
|
* @param array|null $positions
|
||||||
|
* @param array|null $path
|
||||||
|
* @param \Throwable $previous
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
$message,
|
||||||
|
$nodes = null,
|
||||||
|
Source $source = null,
|
||||||
|
$positions = null,
|
||||||
|
$path = null,
|
||||||
|
$previous = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
parent::__construct($message, 0, $previous);
|
||||||
|
|
||||||
|
if ($nodes instanceof \Traversable) {
|
||||||
|
$nodes = iterator_to_array($nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->nodes = $nodes;
|
||||||
|
$this->source = $source;
|
||||||
|
$this->positions = $positions;
|
||||||
|
$this->path = $path;
|
||||||
|
|
||||||
|
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
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -211,38 +190,29 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
||||||
*/
|
*/
|
||||||
public function getSource()
|
public function getSource()
|
||||||
{
|
{
|
||||||
if ($this->source === null) {
|
if (null === $this->source) {
|
||||||
if (!empty($this->nodes[0]) && !empty($this->nodes[0]->loc)) {
|
if (!empty($this->nodes[0]) && !empty($this->nodes[0]->loc)) {
|
||||||
$this->source = $this->nodes[0]->loc->source;
|
$this->source = $this->nodes[0]->loc->source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->source;
|
return $this->source;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int[]
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getPositions()
|
public function getPositions()
|
||||||
{
|
{
|
||||||
if ($this->positions === null && ! empty($this->nodes)) {
|
if (null === $this->positions) {
|
||||||
$positions = array_map(
|
if (!empty($this->nodes)) {
|
||||||
static function ($node) {
|
$positions = array_map(function($node) {
|
||||||
return isset($node->loc) ? $node->loc->start : null;
|
return isset($node->loc) ? $node->loc->start : null;
|
||||||
},
|
}, $this->nodes);
|
||||||
$this->nodes
|
$this->positions = array_filter($positions, function($p) {
|
||||||
);
|
|
||||||
|
|
||||||
$positions = array_filter(
|
|
||||||
$positions,
|
|
||||||
static function ($p) {
|
|
||||||
return $p !== null;
|
return $p !== null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
$this->positions = array_values($positions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->positions;
|
return $this->positions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,36 +227,19 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
||||||
* point out to field mentioned in multiple fragments. Errors during execution include a
|
* point out to field mentioned in multiple fragments. Errors during execution include a
|
||||||
* single location, the field which produced the error.
|
* single location, the field which produced the error.
|
||||||
*
|
*
|
||||||
* @return SourceLocation[]
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @return SourceLocation[]
|
||||||
*/
|
*/
|
||||||
public function getLocations()
|
public function getLocations()
|
||||||
{
|
{
|
||||||
if ($this->locations === null) {
|
if (null === $this->locations) {
|
||||||
$positions = $this->getPositions();
|
$positions = $this->getPositions();
|
||||||
$source = $this->getSource();
|
$source = $this->getSource();
|
||||||
$nodes = $this->nodes;
|
|
||||||
|
|
||||||
if ($positions && $source) {
|
if ($positions && $source) {
|
||||||
$this->locations = array_map(
|
$this->locations = array_map(function ($pos) use ($source) {
|
||||||
static function ($pos) use ($source) {
|
|
||||||
return $source->getLocation($pos);
|
return $source->getLocation($pos);
|
||||||
},
|
}, $positions);
|
||||||
$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 {
|
} else {
|
||||||
$this->locations = [];
|
$this->locations = [];
|
||||||
}
|
}
|
||||||
|
@ -295,54 +248,33 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
||||||
return $this->locations;
|
return $this->locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Node[]|null
|
|
||||||
*/
|
|
||||||
public function getNodes()
|
|
||||||
{
|
|
||||||
return $this->nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array describing the path from the root value to the field which produced this error.
|
* Returns an array describing the path from the root value to the field which produced this error.
|
||||||
* Only included for execution errors.
|
* Only included for execution errors.
|
||||||
*
|
*
|
||||||
* @return mixed[]|null
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @return array|null
|
||||||
*/
|
*/
|
||||||
public function getPath()
|
public function getPath()
|
||||||
{
|
{
|
||||||
return $this->path;
|
return $this->path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed[]
|
|
||||||
*/
|
|
||||||
public function getExtensions()
|
|
||||||
{
|
|
||||||
return $this->extensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns array representation of error suitable for serialization
|
* Returns array representation of error suitable for serialization
|
||||||
*
|
*
|
||||||
* @deprecated Use FormattedError::createFromException() instead
|
* @deprecated Use FormattedError::createFromException() instead
|
||||||
*
|
* @return array
|
||||||
* @return mixed[]
|
|
||||||
*/
|
*/
|
||||||
public function toSerializableArray()
|
public function toSerializableArray()
|
||||||
{
|
{
|
||||||
$arr = [
|
$arr = [
|
||||||
'message' => $this->getMessage(),
|
'message' => $this->getMessage()
|
||||||
];
|
];
|
||||||
|
|
||||||
$locations = Utils::map(
|
$locations = Utils::map($this->getLocations(), function(SourceLocation $loc) {
|
||||||
$this->getLocations(),
|
|
||||||
static function (SourceLocation $loc) {
|
|
||||||
return $loc->toSerializableArray();
|
return $loc->toSerializableArray();
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (!empty($locations)) {
|
if (!empty($locations)) {
|
||||||
$arr['locations'] = $locations;
|
$arr['locations'] = $locations;
|
||||||
|
@ -350,31 +282,19 @@ class Error extends Exception implements JsonSerializable, ClientAware
|
||||||
if (!empty($this->path)) {
|
if (!empty($this->path)) {
|
||||||
$arr['path'] = $this->path;
|
$arr['path'] = $this->path;
|
||||||
}
|
}
|
||||||
if (! empty($this->extensions)) {
|
|
||||||
$arr['extensions'] = $this->extensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $arr;
|
return $arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify data which should be serialized to JSON
|
* Specify data which should be serialized to JSON
|
||||||
*
|
|
||||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||||
*
|
|
||||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||||
* which is a value of any type other than a resource.
|
* which is a value of any type other than a resource.
|
||||||
|
* @since 5.4.0
|
||||||
*/
|
*/
|
||||||
public function jsonSerialize()
|
function jsonSerialize()
|
||||||
{
|
{
|
||||||
return $this->toSerializableArray();
|
return $this->toSerializableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return FormattedError::printError($this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Error;
|
namespace GraphQL\Error;
|
||||||
|
|
||||||
use Countable;
|
|
||||||
use ErrorException;
|
|
||||||
use Exception;
|
|
||||||
use GraphQL\Language\AST\Node;
|
|
||||||
use GraphQL\Language\Source;
|
|
||||||
use GraphQL\Language\SourceLocation;
|
use GraphQL\Language\SourceLocation;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Definition\WrappingType;
|
use GraphQL\Type\Definition\WrappingType;
|
||||||
use GraphQL\Utils\Utils;
|
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).
|
* This class is used for [default error formatting](error-handling.md).
|
||||||
|
@ -42,119 +13,20 @@ use function strlen;
|
||||||
*/
|
*/
|
||||||
class FormattedError
|
class FormattedError
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
private static $internalErrorMessage = 'Internal server error';
|
private static $internalErrorMessage = 'Internal server error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set default error message for internal errors formatted using createFormattedError().
|
* Set default error message for internal errors formatted using createFormattedError().
|
||||||
* This value can be overridden by passing 3rd argument to `createFormattedError()`.
|
* This value can be overridden by passing 3rd argument to `createFormattedError()`.
|
||||||
*
|
*
|
||||||
* @param string $msg
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param string $msg
|
||||||
*/
|
*/
|
||||||
public static function setInternalErrorMessage($msg)
|
public static function setInternalErrorMessage($msg)
|
||||||
{
|
{
|
||||||
self::$internalErrorMessage = $msg;
|
self::$internalErrorMessage = $msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prints a GraphQLError to a string, representing useful location information
|
|
||||||
* about the error's position in the source.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function printError(Error $error)
|
|
||||||
{
|
|
||||||
$printedLocations = [];
|
|
||||||
if ($error->nodes) {
|
|
||||||
/** @var Node $node */
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} elseif ($error->getSource() && $error->getLocations()) {
|
|
||||||
$source = $error->getSource();
|
|
||||||
foreach ($error->getLocations() as $location) {
|
|
||||||
$printedLocations[] = self::highlightSourceAtLocation($source, $location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ! $printedLocations
|
|
||||||
? $error->getMessage()
|
|
||||||
: implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render a helpful description of the location of the error in the GraphQL
|
|
||||||
* Source document.
|
|
||||||
*
|
|
||||||
* @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;
|
|
||||||
$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);
|
|
||||||
|
|
||||||
$lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0];
|
|
||||||
|
|
||||||
$outputLines = [
|
|
||||||
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,
|
|
||||||
];
|
|
||||||
|
|
||||||
return implode("\n", array_filter($outputLines));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
private static function getColumnOffset(Source $source, SourceLocation $location)
|
|
||||||
{
|
|
||||||
return $location->line === 1 ? $source->locationOffset->column - 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $len
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private static function whitespace($len)
|
|
||||||
{
|
|
||||||
return str_repeat(' ', $len);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $len
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private static function lpad($len, $str)
|
|
||||||
{
|
|
||||||
return self::whitespace($len - mb_strlen($str)) . $str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard GraphQL error formatter. Converts any exception to array
|
* Standard GraphQL error formatter. Converts any exception to array
|
||||||
* conforming to GraphQL spec.
|
* conforming to GraphQL spec.
|
||||||
|
@ -164,21 +36,18 @@ class FormattedError
|
||||||
*
|
*
|
||||||
* For a list of available debug flags see GraphQL\Error\Debug constants.
|
* For a list of available debug flags see GraphQL\Error\Debug constants.
|
||||||
*
|
*
|
||||||
* @param Throwable $e
|
* @api
|
||||||
|
* @param \Throwable $e
|
||||||
* @param bool|int $debug
|
* @param bool|int $debug
|
||||||
* @param string $internalErrorMessage
|
* @param string $internalErrorMessage
|
||||||
*
|
* @return array
|
||||||
* @return mixed[]
|
* @throws \Throwable
|
||||||
*
|
|
||||||
* @throws Throwable
|
|
||||||
*
|
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
public static function createFromException($e, $debug = false, $internalErrorMessage = null)
|
public static function createFromException($e, $debug = false, $internalErrorMessage = null)
|
||||||
{
|
{
|
||||||
Utils::invariant(
|
Utils::invariant(
|
||||||
$e instanceof Exception || $e instanceof Throwable,
|
$e instanceof \Exception || $e instanceof \Throwable,
|
||||||
'Expected exception, got %s',
|
"Expected exception, got %s",
|
||||||
Utils::getVariableType($e)
|
Utils::getVariableType($e)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -187,35 +56,26 @@ class FormattedError
|
||||||
if ($e instanceof ClientAware) {
|
if ($e instanceof ClientAware) {
|
||||||
$formattedError = [
|
$formattedError = [
|
||||||
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
|
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
|
||||||
'extensions' => [
|
'category' => $e->getCategory()
|
||||||
'category' => $e->getCategory(),
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$formattedError = [
|
$formattedError = [
|
||||||
'message' => $internalErrorMessage,
|
'message' => $internalErrorMessage,
|
||||||
'extensions' => [
|
'category' => Error::CATEGORY_INTERNAL
|
||||||
'category' => Error::CATEGORY_INTERNAL,
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($e instanceof Error) {
|
if ($e instanceof Error) {
|
||||||
$locations = Utils::map(
|
$locations = Utils::map($e->getLocations(), function(SourceLocation $loc) {
|
||||||
$e->getLocations(),
|
|
||||||
static function (SourceLocation $loc) {
|
|
||||||
return $loc->toSerializableArray();
|
return $loc->toSerializableArray();
|
||||||
}
|
});
|
||||||
);
|
|
||||||
if (!empty($locations)) {
|
if (!empty($locations)) {
|
||||||
$formattedError['locations'] = $locations;
|
$formattedError['locations'] = $locations;
|
||||||
}
|
}
|
||||||
if (!empty($e->path)) {
|
if (!empty($e->path)) {
|
||||||
$formattedError['path'] = $e->path;
|
$formattedError['path'] = $e->path;
|
||||||
}
|
}
|
||||||
if (! empty($e->getExtensions())) {
|
|
||||||
$formattedError['extensions'] = $e->getExtensions() + $formattedError['extensions'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($debug) {
|
if ($debug) {
|
||||||
|
@ -229,13 +89,11 @@ class FormattedError
|
||||||
* Decorates spec-compliant $formattedError with debug entries according to $debug flags
|
* Decorates spec-compliant $formattedError with debug entries according to $debug flags
|
||||||
* (see GraphQL\Error\Debug for available flags)
|
* (see GraphQL\Error\Debug for available flags)
|
||||||
*
|
*
|
||||||
* @param mixed[] $formattedError
|
* @param array $formattedError
|
||||||
* @param Throwable $e
|
* @param \Throwable $e
|
||||||
* @param bool|int $debug
|
* @param bool $debug
|
||||||
*
|
* @return array
|
||||||
* @return mixed[]
|
* @throws \Throwable
|
||||||
*
|
|
||||||
* @throws Throwable
|
|
||||||
*/
|
*/
|
||||||
public static function addDebugEntries(array $formattedError, $e, $debug)
|
public static function addDebugEntries(array $formattedError, $e, $debug)
|
||||||
{
|
{
|
||||||
|
@ -244,8 +102,8 @@ class FormattedError
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::invariant(
|
Utils::invariant(
|
||||||
$e instanceof Exception || $e instanceof Throwable,
|
$e instanceof \Exception || $e instanceof \Throwable,
|
||||||
'Expected exception, got %s',
|
"Expected exception, got %s",
|
||||||
Utils::getVariableType($e)
|
Utils::getVariableType($e)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -254,28 +112,20 @@ class FormattedError
|
||||||
if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
|
if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
|
||||||
if (!$e instanceof Error) {
|
if (!$e instanceof Error) {
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
} else if ($e->getPrevious()) {
|
||||||
|
|
||||||
if ($e->getPrevious()) {
|
|
||||||
throw $e->getPrevious();
|
throw $e->getPrevious();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
|
$isInternal = !$e instanceof ClientAware || !$e->isClientSafe();
|
||||||
|
|
||||||
if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) && $isUnsafe) {
|
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isInternal) {
|
||||||
if ($e->getPrevious()) {
|
|
||||||
throw $e->getPrevious();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isUnsafe) {
|
|
||||||
// Displaying debugMessage as a first entry:
|
// Displaying debugMessage as a first entry:
|
||||||
$formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
|
$formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($debug & Debug::INCLUDE_TRACE) {
|
if ($debug & Debug::INCLUDE_TRACE) {
|
||||||
if ($e instanceof ErrorException || $e instanceof \Error) {
|
if ($e instanceof \ErrorException || $e instanceof \Error) {
|
||||||
$formattedError += [
|
$formattedError += [
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
|
@ -289,7 +139,6 @@ class FormattedError
|
||||||
$formattedError['trace'] = static::toSafeTrace($debugging);
|
$formattedError['trace'] = static::toSafeTrace($debugging);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $formattedError;
|
return $formattedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,54 +146,53 @@ class FormattedError
|
||||||
* Prepares final error formatter taking in account $debug flags.
|
* Prepares final error formatter taking in account $debug flags.
|
||||||
* If initial formatter is not set, FormattedError::createFromException is used
|
* If initial formatter is not set, FormattedError::createFromException is used
|
||||||
*
|
*
|
||||||
* @param bool|int $debug
|
* @param callable|null $formatter
|
||||||
*
|
* @param $debug
|
||||||
* @return callable|callable
|
* @return callable|\Closure
|
||||||
*/
|
*/
|
||||||
public static function prepareFormatter(?callable $formatter = null, $debug)
|
public static function prepareFormatter(callable $formatter = null, $debug)
|
||||||
{
|
{
|
||||||
$formatter = $formatter ?: static function ($e) {
|
$formatter = $formatter ?: function($e) {
|
||||||
return FormattedError::createFromException($e);
|
return FormattedError::createFromException($e);
|
||||||
};
|
};
|
||||||
if ($debug) {
|
if ($debug) {
|
||||||
$formatter = static function ($e) use ($formatter, $debug) {
|
$formatter = function($e) use ($formatter, $debug) {
|
||||||
return FormattedError::addDebugEntries($formatter($e), $e, $debug);
|
return FormattedError::addDebugEntries($formatter($e), $e, $debug);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return $formatter;
|
return $formatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns error trace as serializable array
|
* Returns error trace as serializable array
|
||||||
*
|
*
|
||||||
* @param Throwable $error
|
|
||||||
*
|
|
||||||
* @return mixed[]
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param \Throwable $error
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function toSafeTrace($error)
|
public static function toSafeTrace($error)
|
||||||
{
|
{
|
||||||
$trace = $error->getTrace();
|
$trace = $error->getTrace();
|
||||||
|
|
||||||
if (isset($trace[0]['function']) && isset($trace[0]['class']) &&
|
|
||||||
// Remove invariant entries as they don't provide much value:
|
// Remove invariant entries as they don't provide much value:
|
||||||
($trace[0]['class'] . '::' . $trace[0]['function'] === 'GraphQL\Utils\Utils::invariant')) {
|
if (
|
||||||
array_shift($trace);
|
isset($trace[0]['function']) && isset($trace[0]['class']) &&
|
||||||
} elseif (! isset($trace[0]['file'])) {
|
('GraphQL\Utils\Utils::invariant' === $trace[0]['class'].'::'.$trace[0]['function'])) {
|
||||||
// Remove root call as it's likely error handler trace:
|
|
||||||
array_shift($trace);
|
array_shift($trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_map(
|
// Remove root call as it's likely error handler trace:
|
||||||
static function ($err) {
|
else if (!isset($trace[0]['file'])) {
|
||||||
|
array_shift($trace);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_map(function($err) {
|
||||||
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
|
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
|
||||||
|
|
||||||
if (isset($err['function'])) {
|
if (isset($err['function'])) {
|
||||||
$func = $err['function'];
|
$func = $err['function'];
|
||||||
$args = ! empty($err['args']) ? array_map([self::class, 'printVar'], $err['args']) : [];
|
$args = !empty($err['args']) ? array_map([__CLASS__, 'printVar'], $err['args']) : [];
|
||||||
$funcStr = $func . '(' . implode(', ', $args) . ')';
|
$funcStr = $func . '(' . implode(", ", $args) . ')';
|
||||||
|
|
||||||
if (isset($err['class'])) {
|
if (isset($err['class'])) {
|
||||||
$safeErr['call'] = $err['class'] . '::' . $funcStr;
|
$safeErr['call'] = $err['class'] . '::' . $funcStr;
|
||||||
|
@ -354,14 +202,11 @@ class FormattedError
|
||||||
}
|
}
|
||||||
|
|
||||||
return $safeErr;
|
return $safeErr;
|
||||||
},
|
}, $trace);
|
||||||
$trace
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $var
|
* @param $var
|
||||||
*
|
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function printVar($var)
|
public static function printVar($var)
|
||||||
|
@ -371,17 +216,16 @@ class FormattedError
|
||||||
if ($var instanceof WrappingType) {
|
if ($var instanceof WrappingType) {
|
||||||
$var = $var->getWrappedType(true);
|
$var = $var->getWrappedType(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'GraphQLType: ' . $var->name;
|
return 'GraphQLType: ' . $var->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_object($var)) {
|
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)) {
|
if (is_array($var)) {
|
||||||
return 'array(' . count($var) . ')';
|
return 'array(' . count($var) . ')';
|
||||||
}
|
}
|
||||||
if ($var === '') {
|
if ('' === $var) {
|
||||||
return '(empty string)';
|
return '(empty string)';
|
||||||
}
|
}
|
||||||
if (is_string($var)) {
|
if (is_string($var)) {
|
||||||
|
@ -393,48 +237,42 @@ class FormattedError
|
||||||
if (is_scalar($var)) {
|
if (is_scalar($var)) {
|
||||||
return $var;
|
return $var;
|
||||||
}
|
}
|
||||||
if ($var === null) {
|
if (null === $var) {
|
||||||
return 'null';
|
return 'null';
|
||||||
}
|
}
|
||||||
|
|
||||||
return gettype($var);
|
return gettype($var);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated as of v0.8.0
|
* @deprecated as of v0.8.0
|
||||||
*
|
* @param $error
|
||||||
* @param string $error
|
|
||||||
* @param SourceLocation[] $locations
|
* @param SourceLocation[] $locations
|
||||||
*
|
* @return array
|
||||||
* @return mixed[]
|
|
||||||
*/
|
*/
|
||||||
public static function create($error, array $locations = [])
|
public static function create($error, array $locations = [])
|
||||||
{
|
{
|
||||||
$formatted = ['message' => $error];
|
$formatted = [
|
||||||
|
'message' => $error
|
||||||
|
];
|
||||||
|
|
||||||
if (!empty($locations)) {
|
if (!empty($locations)) {
|
||||||
$formatted['locations'] = array_map(
|
$formatted['locations'] = array_map(function($loc) { return $loc->toArray();}, $locations);
|
||||||
static function ($loc) {
|
|
||||||
return $loc->toArray();
|
|
||||||
},
|
|
||||||
$locations
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $formatted;
|
return $formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param \ErrorException $e
|
||||||
* @deprecated as of v0.10.0, use general purpose method createFromException() instead
|
* @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 [
|
return [
|
||||||
'message' => $e->getMessage(),
|
'message' => $e->getMessage(),
|
||||||
'severity' => $e->getSeverity(),
|
'severity' => $e->getSeverity(),
|
||||||
'trace' => self::toSafeTrace($e),
|
'trace' => self::toSafeTrace($e)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Error;
|
namespace GraphQL\Error;
|
||||||
|
|
||||||
use LogicException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Class InvariantVoilation
|
||||||
|
*
|
||||||
* Note:
|
* Note:
|
||||||
* This exception should not inherit base Error exception as it is raised when there is an error somewhere in
|
* This exception should not inherit base Error exception as it is raised when there is an error somewhere in
|
||||||
* user-land code
|
* user-land code
|
||||||
|
*
|
||||||
|
* @package GraphQL\Error
|
||||||
*/
|
*/
|
||||||
class InvariantViolation extends LogicException
|
class InvariantViolation extends \LogicException
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,50 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Error;
|
namespace GraphQL\Error;
|
||||||
|
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
use function sprintf;
|
use GraphQL\Language\SourceLocation;
|
||||||
|
|
||||||
class SyntaxError extends Error
|
class SyntaxError extends Error
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* @param Source $source
|
||||||
* @param int $position
|
* @param int $position
|
||||||
* @param string $description
|
* @param string $description
|
||||||
*/
|
*/
|
||||||
public function __construct(Source $source, $position, $description)
|
public function __construct(Source $source, $position, $description)
|
||||||
{
|
{
|
||||||
parent::__construct(
|
$location = $source->getLocation($position);
|
||||||
sprintf('Syntax Error: %s', $description),
|
$syntaxError =
|
||||||
null,
|
"Syntax Error {$source->name} ({$location->line}:{$location->column}) $description\n\n" .
|
||||||
$source,
|
self::highlightSourceAtLocation($source, $location);
|
||||||
[$position]
|
|
||||||
);
|
parent::__construct($syntaxError, null, $source, [$position]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Source $source
|
||||||
|
* @param SourceLocation $location
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function highlightSourceAtLocation(Source $source, SourceLocation $location)
|
||||||
|
{
|
||||||
|
$line = $location->line;
|
||||||
|
$prevLineNum = (string) ($line - 1);
|
||||||
|
$lineNum = (string) $line;
|
||||||
|
$nextLineNum = (string) ($line + 1);
|
||||||
|
$padLen = mb_strlen($nextLineNum, 'UTF-8');
|
||||||
|
|
||||||
|
$unicodeChars = json_decode('"\u2028\u2029"'); // Quick hack to get js-compatible representation of these chars
|
||||||
|
$lines = preg_split('/\r\n|[\n\r' . $unicodeChars . ']/su', $source->body);
|
||||||
|
|
||||||
|
$lpad = function($len, $str) {
|
||||||
|
return str_pad($str, $len - mb_strlen($str, 'UTF-8') + 1, ' ', STR_PAD_LEFT);
|
||||||
|
};
|
||||||
|
|
||||||
|
return
|
||||||
|
($line >= 2 ? $lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2] . "\n" : '') .
|
||||||
|
($lpad($padLen, $lineNum) . ': ' . $lines[$line - 1] . "\n") .
|
||||||
|
(str_repeat(' ', 1 + $padLen + $location->column) . "^\n") .
|
||||||
|
($line < count($lines) ? $lpad($padLen, $nextLineNum) . ': ' . $lines[$line] . "\n" : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Error;
|
namespace GraphQL\Error;
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Class UserError
|
||||||
|
*
|
||||||
* Error caused by actions of GraphQL clients. Can be safely displayed to a client...
|
* 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
|
* @return bool
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Error;
|
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.
|
* Encapsulates warnings produced by the library.
|
||||||
*
|
*
|
||||||
|
@ -17,29 +9,28 @@ use const E_USER_WARNING;
|
||||||
*/
|
*/
|
||||||
final class Warning
|
final class Warning
|
||||||
{
|
{
|
||||||
public const WARNING_ASSIGN = 2;
|
const WARNING_NAME = 1;
|
||||||
public const WARNING_CONFIG = 4;
|
const WARNING_ASSIGN = 2;
|
||||||
public const WARNING_FULL_SCHEMA_SCAN = 8;
|
const WARNING_CONFIG = 4;
|
||||||
public const WARNING_CONFIG_DEPRECATION = 16;
|
const WARNING_FULL_SCHEMA_SCAN = 8;
|
||||||
public const WARNING_NOT_A_TYPE = 32;
|
const WARNING_CONFIG_DEPRECATION = 16;
|
||||||
public const ALL = 63;
|
const WARNING_NOT_A_TYPE = 32;
|
||||||
|
const ALL = 63;
|
||||||
|
|
||||||
/** @var int */
|
static $enableWarnings = self::ALL;
|
||||||
private static $enableWarnings = self::ALL;
|
|
||||||
|
|
||||||
/** @var mixed[] */
|
static $warned = [];
|
||||||
private static $warned = [];
|
|
||||||
|
|
||||||
/** @var callable|null */
|
static private $warningHandler;
|
||||||
private static $warningHandler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets warning handler which can intercept all system warnings.
|
* Sets warning handler which can intercept all system warnings.
|
||||||
* When not set, trigger_error() is used to notify about warnings.
|
* When not set, trigger_error() is used to notify about warnings.
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
|
* @param callable|null $warningHandler
|
||||||
*/
|
*/
|
||||||
public static function setWarningHandler(?callable $warningHandler = null) : void
|
public static function setWarningHandler(callable $warningHandler = null)
|
||||||
{
|
{
|
||||||
self::$warningHandler = $warningHandler;
|
self::$warningHandler = $warningHandler;
|
||||||
}
|
}
|
||||||
|
@ -52,20 +43,18 @@ final class Warning
|
||||||
*
|
*
|
||||||
* When passing true - suppresses all warnings.
|
* When passing true - suppresses all warnings.
|
||||||
*
|
*
|
||||||
* @param bool|int $suppress
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param bool|int $suppress
|
||||||
*/
|
*/
|
||||||
public static function suppress($suppress = true) : void
|
static function suppress($suppress = true)
|
||||||
{
|
{
|
||||||
if ($suppress === true) {
|
if (true === $suppress) {
|
||||||
self::$enableWarnings = 0;
|
self::$enableWarnings = 0;
|
||||||
} elseif ($suppress === false) {
|
} else if (false === $suppress) {
|
||||||
self::$enableWarnings = self::ALL;
|
self::$enableWarnings = self::ALL;
|
||||||
} elseif (is_int($suppress)) {
|
|
||||||
self::$enableWarnings &= ~$suppress;
|
|
||||||
} else {
|
} else {
|
||||||
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress);
|
$suppress = (int) $suppress;
|
||||||
|
self::$enableWarnings &= ~$suppress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,26 +66,24 @@ final class Warning
|
||||||
*
|
*
|
||||||
* When passing true - re-enables all warnings.
|
* When passing true - re-enables all warnings.
|
||||||
*
|
*
|
||||||
* @param bool|int $enable
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param bool|int $enable
|
||||||
*/
|
*/
|
||||||
public static function enable($enable = true) : void
|
public static function enable($enable = true)
|
||||||
{
|
{
|
||||||
if ($enable === true) {
|
if (true === $enable) {
|
||||||
self::$enableWarnings = self::ALL;
|
self::$enableWarnings = self::ALL;
|
||||||
} elseif ($enable === false) {
|
} else if (false === $enable) {
|
||||||
self::$enableWarnings = 0;
|
self::$enableWarnings = 0;
|
||||||
} elseif (is_int($enable)) {
|
|
||||||
self::$enableWarnings |= $enable;
|
|
||||||
} else {
|
} else {
|
||||||
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable);
|
$enable = (int) $enable;
|
||||||
|
self::$enableWarnings |= $enable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
|
static function warnOnce($errorMessage, $warningId, $messageLevel = null)
|
||||||
{
|
{
|
||||||
if (self::$warningHandler !== null) {
|
if (self::$warningHandler) {
|
||||||
$fn = self::$warningHandler;
|
$fn = self::$warningHandler;
|
||||||
$fn($errorMessage, $warningId);
|
$fn($errorMessage, $warningId);
|
||||||
} else if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
|
} else if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
|
||||||
|
@ -105,9 +92,9 @@ final class Warning
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
|
static function warn($errorMessage, $warningId, $messageLevel = null)
|
||||||
{
|
{
|
||||||
if (self::$warningHandler !== null) {
|
if (self::$warningHandler) {
|
||||||
$fn = self::$warningHandler;
|
$fn = self::$warningHandler;
|
||||||
$fn($errorMessage, $warningId);
|
$fn($errorMessage, $warningId);
|
||||||
} else if ((self::$enableWarnings & $warningId) > 0) {
|
} else if ((self::$enableWarnings & $warningId) > 0) {
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Exception;
|
|
||||||
|
|
||||||
use InvalidArgumentException;
|
|
||||||
use function gettype;
|
|
||||||
use function sprintf;
|
|
||||||
|
|
||||||
final class InvalidArgument extends InvalidArgumentException
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param mixed $argument
|
|
||||||
*/
|
|
||||||
public static function fromExpectedTypeAndArgument(string $expectedType, $argument) : self
|
|
||||||
{
|
|
||||||
return new self(sprintf('Expected type "%s", got "%s"', $expectedType, gettype($argument)));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Executor;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
|
||||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||||
use GraphQL\Language\AST\OperationDefinitionNode;
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
use GraphQL\Type\Schema;
|
use GraphQL\Type\Schema;
|
||||||
|
@ -14,65 +10,78 @@ use GraphQL\Type\Schema;
|
||||||
* Data that must be available at all points during query execution.
|
* Data that must be available at all points during query execution.
|
||||||
*
|
*
|
||||||
* Namely, schema of the type system that is currently executing,
|
* 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
|
* @internal
|
||||||
*/
|
*/
|
||||||
class ExecutionContext
|
class ExecutionContext
|
||||||
{
|
{
|
||||||
/** @var Schema */
|
/**
|
||||||
|
* @var Schema
|
||||||
|
*/
|
||||||
public $schema;
|
public $schema;
|
||||||
|
|
||||||
/** @var FragmentDefinitionNode[] */
|
/**
|
||||||
|
* @var FragmentDefinitionNode[]
|
||||||
|
*/
|
||||||
public $fragments;
|
public $fragments;
|
||||||
|
|
||||||
/** @var mixed */
|
/**
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
public $rootValue;
|
public $rootValue;
|
||||||
|
|
||||||
/** @var mixed */
|
/**
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
public $contextValue;
|
public $contextValue;
|
||||||
|
|
||||||
/** @var OperationDefinitionNode */
|
/**
|
||||||
|
* @var OperationDefinitionNode
|
||||||
|
*/
|
||||||
public $operation;
|
public $operation;
|
||||||
|
|
||||||
/** @var mixed[] */
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
public $variableValues;
|
public $variableValues;
|
||||||
|
|
||||||
/** @var callable */
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
public $fieldResolver;
|
public $fieldResolver;
|
||||||
|
|
||||||
/** @var Error[] */
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
public $errors;
|
public $errors;
|
||||||
|
|
||||||
/** @var PromiseAdapter */
|
|
||||||
public $promiseAdapter;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
$schema,
|
$schema,
|
||||||
$fragments,
|
$fragments,
|
||||||
$rootValue,
|
$root,
|
||||||
$contextValue,
|
$contextValue,
|
||||||
$operation,
|
$operation,
|
||||||
$variableValues,
|
$variables,
|
||||||
$errors,
|
$errors,
|
||||||
$fieldResolver,
|
$fieldResolver,
|
||||||
$promiseAdapter
|
$promiseAdapter
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
$this->schema = $schema;
|
$this->schema = $schema;
|
||||||
$this->fragments = $fragments;
|
$this->fragments = $fragments;
|
||||||
$this->rootValue = $rootValue;
|
$this->rootValue = $root;
|
||||||
$this->contextValue = $contextValue;
|
$this->contextValue = $contextValue;
|
||||||
$this->operation = $operation;
|
$this->operation = $operation;
|
||||||
$this->variableValues = $variableValues;
|
$this->variableValues = $variables;
|
||||||
$this->errors = $errors ?: [];
|
$this->errors = $errors ?: [];
|
||||||
$this->fieldResolver = $fieldResolver;
|
$this->fieldResolver = $fieldResolver;
|
||||||
$this->promiseAdapter = $promiseAdapter;
|
$this->promises = $promiseAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addError(Error $error)
|
public function addError(Error $error)
|
||||||
{
|
{
|
||||||
$this->errors[] = $error;
|
$this->errors[] = $error;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Executor;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Error\FormattedError;
|
use GraphQL\Error\FormattedError;
|
||||||
use JsonSerializable;
|
|
||||||
use function array_map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returned after [query execution](executing-queries.md).
|
* Returned after [query execution](executing-queries.md).
|
||||||
|
@ -17,13 +12,13 @@ use function array_map;
|
||||||
* Could be converted to [spec-compliant](https://facebook.github.io/graphql/#sec-Response-Format)
|
* Could be converted to [spec-compliant](https://facebook.github.io/graphql/#sec-Response-Format)
|
||||||
* serializable array using `toArray()`
|
* serializable array using `toArray()`
|
||||||
*/
|
*/
|
||||||
class ExecutionResult implements JsonSerializable
|
class ExecutionResult implements \JsonSerializable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Data collected from resolvers during query execution
|
* Data collected from resolvers during query execution
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @var mixed[]
|
* @var array
|
||||||
*/
|
*/
|
||||||
public $data;
|
public $data;
|
||||||
|
|
||||||
|
@ -34,7 +29,7 @@ class ExecutionResult implements JsonSerializable
|
||||||
* contain original exception.
|
* contain original exception.
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @var Error[]
|
* @var \GraphQL\Error\Error[]
|
||||||
*/
|
*/
|
||||||
public $errors;
|
public $errors;
|
||||||
|
|
||||||
|
@ -43,22 +38,26 @@ class ExecutionResult implements JsonSerializable
|
||||||
* Conforms to
|
* Conforms to
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @var mixed[]
|
* @var array
|
||||||
*/
|
*/
|
||||||
public $extensions;
|
public $extensions;
|
||||||
|
|
||||||
/** @var callable */
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
private $errorFormatter;
|
private $errorFormatter;
|
||||||
|
|
||||||
/** @var callable */
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
private $errorsHandler;
|
private $errorsHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed[] $data
|
* @param array $data
|
||||||
* @param Error[] $errors
|
* @param array $errors
|
||||||
* @param mixed[] $extensions
|
* @param array $extensions
|
||||||
*/
|
*/
|
||||||
public function __construct($data = null, array $errors = [], array $extensions = [])
|
public function __construct(array $data = null, array $errors = [], array $extensions = [])
|
||||||
{
|
{
|
||||||
$this->data = $data;
|
$this->data = $data;
|
||||||
$this->errors = $errors;
|
$this->errors = $errors;
|
||||||
|
@ -78,14 +77,13 @@ class ExecutionResult implements JsonSerializable
|
||||||
* // ... other keys
|
* // ... other keys
|
||||||
* );
|
* );
|
||||||
*
|
*
|
||||||
* @return self
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param callable $errorFormatter
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setErrorFormatter(callable $errorFormatter)
|
public function setErrorFormatter(callable $errorFormatter)
|
||||||
{
|
{
|
||||||
$this->errorFormatter = $errorFormatter;
|
$this->errorFormatter = $errorFormatter;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,25 +97,16 @@ class ExecutionResult implements JsonSerializable
|
||||||
* return array_map($formatter, $errors);
|
* return array_map($formatter, $errors);
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @return self
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param callable $handler
|
||||||
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setErrorsHandler(callable $handler)
|
public function setErrorsHandler(callable $handler)
|
||||||
{
|
{
|
||||||
$this->errorsHandler = $handler;
|
$this->errorsHandler = $handler;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed[]
|
|
||||||
*/
|
|
||||||
public function jsonSerialize()
|
|
||||||
{
|
|
||||||
return $this->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts GraphQL query result to spec-compliant serializable array using provided
|
* Converts GraphQL query result to spec-compliant serializable array using provided
|
||||||
* errors handler and formatter.
|
* errors handler and formatter.
|
||||||
|
@ -128,35 +117,42 @@ class ExecutionResult implements JsonSerializable
|
||||||
* $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from
|
* $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from
|
||||||
* GraphQL\Error\Debug
|
* GraphQL\Error\Debug
|
||||||
*
|
*
|
||||||
* @param bool|int $debug
|
|
||||||
*
|
|
||||||
* @return mixed[]
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param bool|int $debug
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function toArray($debug = false)
|
public function toArray($debug = false)
|
||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
||||||
if (!empty($this->errors)) {
|
if (!empty($this->errors)) {
|
||||||
$errorsHandler = $this->errorsHandler ?: static function (array $errors, callable $formatter) {
|
$errorsHandler = $this->errorsHandler ?: function(array $errors, callable $formatter) {
|
||||||
return array_map($formatter, $errors);
|
return array_map($formatter, $errors);
|
||||||
};
|
};
|
||||||
|
|
||||||
$result['errors'] = $errorsHandler(
|
$result['errors'] = $errorsHandler(
|
||||||
$this->errors,
|
$this->errors,
|
||||||
FormattedError::prepareFormatter($this->errorFormatter, $debug)
|
FormattedError::prepareFormatter($this->errorFormatter, $debug)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->data !== null) {
|
if (null !== $this->data) {
|
||||||
$result['data'] = $this->data;
|
$result['data'] = $this->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($this->extensions)) {
|
if (!empty($this->extensions)) {
|
||||||
$result['extensions'] = $this->extensions;
|
$result['extensions'] = (array) $this->extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of \JsonSerializable interface
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function jsonSerialize()
|
||||||
|
{
|
||||||
|
return $this->toArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,15 +0,0 @@
|
||||||
<?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,7 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Executor\Promise\Adapter;
|
namespace GraphQL\Executor\Promise\Adapter;
|
||||||
|
|
||||||
use GraphQL\Executor\Promise\Promise;
|
use GraphQL\Executor\Promise\Promise;
|
||||||
|
@ -9,9 +6,6 @@ use GraphQL\Executor\Promise\PromiseAdapter;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use React\Promise\Promise as ReactPromise;
|
use React\Promise\Promise as ReactPromise;
|
||||||
use React\Promise\PromiseInterface as ReactPromiseInterface;
|
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
|
class ReactPromiseAdapter implements PromiseAdapter
|
||||||
{
|
{
|
||||||
|
@ -34,11 +28,10 @@ class ReactPromiseAdapter implements PromiseAdapter
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null)
|
public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null)
|
||||||
{
|
{
|
||||||
/** @var ReactPromiseInterface $adoptedPromise */
|
/** @var $adoptedPromise ReactPromiseInterface */
|
||||||
$adoptedPromise = $promise->adoptedPromise;
|
$adoptedPromise = $promise->adoptedPromise;
|
||||||
|
|
||||||
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
|
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +41,6 @@ class ReactPromiseAdapter implements PromiseAdapter
|
||||||
public function create(callable $resolver)
|
public function create(callable $resolver)
|
||||||
{
|
{
|
||||||
$promise = new ReactPromise($resolver);
|
$promise = new ReactPromise($resolver);
|
||||||
|
|
||||||
return new Promise($promise, $this);
|
return new Promise($promise, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,8 +49,7 @@ class ReactPromiseAdapter implements PromiseAdapter
|
||||||
*/
|
*/
|
||||||
public function createFulfilled($value = null)
|
public function createFulfilled($value = null)
|
||||||
{
|
{
|
||||||
$promise = resolve($value);
|
$promise = \React\Promise\resolve($value);
|
||||||
|
|
||||||
return new Promise($promise, $this);
|
return new Promise($promise, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,8 +58,7 @@ class ReactPromiseAdapter implements PromiseAdapter
|
||||||
*/
|
*/
|
||||||
public function createRejected($reason)
|
public function createRejected($reason)
|
||||||
{
|
{
|
||||||
$promise = reject($reason);
|
$promise = \React\Promise\reject($reason);
|
||||||
|
|
||||||
return new Promise($promise, $this);
|
return new Promise($promise, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,14 +68,11 @@ class ReactPromiseAdapter implements PromiseAdapter
|
||||||
public function all(array $promisesOrValues)
|
public function all(array $promisesOrValues)
|
||||||
{
|
{
|
||||||
// TODO: rework with generators when PHP minimum required version is changed to 5.5+
|
// TODO: rework with generators when PHP minimum required version is changed to 5.5+
|
||||||
$promisesOrValues = Utils::map(
|
$promisesOrValues = Utils::map($promisesOrValues, function ($item) {
|
||||||
$promisesOrValues,
|
|
||||||
static function ($item) {
|
|
||||||
return $item instanceof Promise ? $item->adoptedPromise : $item;
|
return $item instanceof Promise ? $item->adoptedPromise : $item;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
$promise = all($promisesOrValues)->then(static function ($values) use ($promisesOrValues) {
|
$promise = \React\Promise\all($promisesOrValues)->then(function($values) use ($promisesOrValues) {
|
||||||
$orderedResults = [];
|
$orderedResults = [];
|
||||||
|
|
||||||
foreach ($promisesOrValues as $key => $value) {
|
foreach ($promisesOrValues as $key => $value) {
|
||||||
|
@ -94,7 +81,6 @@ class ReactPromiseAdapter implements PromiseAdapter
|
||||||
|
|
||||||
return $orderedResults;
|
return $orderedResults;
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise($promise, $this);
|
return new Promise($promise, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Executor\Promise\Adapter;
|
namespace GraphQL\Executor\Promise\Adapter;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use GraphQL\Executor\ExecutionResult;
|
|
||||||
use GraphQL\Utils\Utils;
|
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
|
* Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
|
||||||
* (using queue to defer promises execution)
|
* (using queue to defer promises execution)
|
||||||
|
*
|
||||||
|
* @package GraphQL\Executor\Promise\Adapter
|
||||||
*/
|
*/
|
||||||
class SyncPromise
|
class SyncPromise
|
||||||
{
|
{
|
||||||
|
@ -22,71 +17,39 @@ class SyncPromise
|
||||||
const FULFILLED = 'fulfilled';
|
const FULFILLED = 'fulfilled';
|
||||||
const REJECTED = 'rejected';
|
const REJECTED = 'rejected';
|
||||||
|
|
||||||
/** @var SplQueue */
|
/**
|
||||||
|
* @var \SplQueue
|
||||||
|
*/
|
||||||
public static $queue;
|
public static $queue;
|
||||||
|
|
||||||
/** @var string */
|
public static function getQueue()
|
||||||
public $state = self::PENDING;
|
{
|
||||||
|
return self::$queue ?: self::$queue = new \SplQueue();
|
||||||
|
}
|
||||||
|
|
||||||
/** @var ExecutionResult|Throwable */
|
public static function runQueue()
|
||||||
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;
|
$q = self::$queue;
|
||||||
while ($q !== null && ! $q->isEmpty()) {
|
while ($q && !$q->isEmpty()) {
|
||||||
$task = $q->dequeue();
|
$task = $q->dequeue();
|
||||||
$task();
|
$task();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resolve($value) : self
|
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)
|
||||||
{
|
{
|
||||||
switch ($this->state) {
|
if (!$reason instanceof \Exception && !$reason instanceof \Throwable) {
|
||||||
case self::PENDING:
|
throw new \Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
|
||||||
if ($value === $this) {
|
|
||||||
throw new Exception('Cannot resolve promise with self');
|
|
||||||
}
|
|
||||||
if (is_object($value) && method_exists($value, 'then')) {
|
|
||||||
$value->then(
|
|
||||||
function ($resolvedValue) {
|
|
||||||
$this->resolve($resolvedValue);
|
|
||||||
},
|
|
||||||
function ($reason) {
|
|
||||||
$this->reject($reason);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
$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');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case self::REJECTED:
|
|
||||||
throw new Exception('Cannot resolve rejected promise');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function reject($reason) : self
|
|
||||||
{
|
|
||||||
if (! $reason instanceof Exception && ! $reason instanceof Throwable) {
|
|
||||||
throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($this->state) {
|
switch ($this->state) {
|
||||||
|
@ -97,65 +60,55 @@ class SyncPromise
|
||||||
break;
|
break;
|
||||||
case self::REJECTED:
|
case self::REJECTED:
|
||||||
if ($reason !== $this->result) {
|
if ($reason !== $this->result) {
|
||||||
throw new Exception('Cannot change rejection reason');
|
throw new \Exception("Cannot change rejection reason");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case self::FULFILLED:
|
case self::FULFILLED:
|
||||||
throw new Exception('Cannot reject fulfilled promise');
|
throw new \Exception("Cannot reject fulfilled promise");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function enqueueWaitingPromises() : void
|
public function resolve($value)
|
||||||
{
|
{
|
||||||
Utils::invariant(
|
switch ($this->state) {
|
||||||
$this->state !== self::PENDING,
|
case self::PENDING:
|
||||||
'Cannot enqueue derived promises when parent is still pending'
|
if ($value === $this) {
|
||||||
|
throw new \Exception("Cannot resolve promise with self");
|
||||||
|
}
|
||||||
|
if (is_object($value) && method_exists($value, 'then')) {
|
||||||
|
$value->then(
|
||||||
|
function($resolvedValue) {
|
||||||
|
$this->resolve($resolvedValue);
|
||||||
|
},
|
||||||
|
function($reason) {
|
||||||
|
$this->reject($reason);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
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;
|
return $this;
|
||||||
}
|
}
|
||||||
if ($this->state === self::FULFILLED && $onFulfilled === null) {
|
|
||||||
|
$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");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case self::REJECTED:
|
||||||
|
throw new \Exception("Cannot resolve rejected promise");
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function then(callable $onFulfilled = null, callable $onRejected = null)
|
||||||
|
{
|
||||||
|
if ($this->state === self::REJECTED && !$onRejected) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
if ($this->state === self::FULFILLED && !$onFulfilled) {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
$tmp = new self();
|
$tmp = new self();
|
||||||
|
@ -167,4 +120,39 @@ class SyncPromise
|
||||||
|
|
||||||
return $tmp;
|
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,22 +1,19 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Executor\Promise\Adapter;
|
namespace GraphQL\Executor\Promise\Adapter;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use GraphQL\Deferred;
|
use GraphQL\Deferred;
|
||||||
use GraphQL\Error\InvariantViolation;
|
use GraphQL\Error\InvariantViolation;
|
||||||
use GraphQL\Executor\ExecutionResult;
|
|
||||||
use GraphQL\Executor\Promise\Promise;
|
use GraphQL\Executor\Promise\Promise;
|
||||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use Throwable;
|
|
||||||
use function count;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Class SyncPromiseAdapter
|
||||||
|
*
|
||||||
* Allows changing order of field resolution even in sync environments
|
* Allows changing order of field resolution even in sync environments
|
||||||
* (by leveraging queue of deferreds and promises)
|
* (by leveraging queue of deferreds and promises)
|
||||||
|
*
|
||||||
|
* @package GraphQL\Executor\Promise\Adapter
|
||||||
*/
|
*/
|
||||||
class SyncPromiseAdapter implements PromiseAdapter
|
class SyncPromiseAdapter implements PromiseAdapter
|
||||||
{
|
{
|
||||||
|
@ -36,19 +33,17 @@ class SyncPromiseAdapter implements PromiseAdapter
|
||||||
if (!$thenable instanceof Deferred) {
|
if (!$thenable instanceof Deferred) {
|
||||||
throw new InvariantViolation('Expected instance of GraphQL\Deferred, got ' . Utils::printSafe($thenable));
|
throw new InvariantViolation('Expected instance of GraphQL\Deferred, got ' . Utils::printSafe($thenable));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise($thenable->promise, $this);
|
return new Promise($thenable->promise, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @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 $adoptedPromise */
|
/** @var SyncPromise $promise */
|
||||||
$adoptedPromise = $promise->adoptedPromise;
|
$promise = $promise->adoptedPromise;
|
||||||
|
return new Promise($promise->then($onFulfilled, $onRejected), $this);
|
||||||
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,18 +55,12 @@ class SyncPromiseAdapter implements PromiseAdapter
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$resolver(
|
$resolver(
|
||||||
[
|
[$promise, 'resolve'],
|
||||||
$promise,
|
[$promise, 'reject']
|
||||||
'resolve',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
$promise,
|
|
||||||
'reject',
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
} catch (Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$promise->reject($e);
|
$promise->reject($e);
|
||||||
} catch (Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$promise->reject($e);
|
$promise->reject($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +73,6 @@ class SyncPromiseAdapter implements PromiseAdapter
|
||||||
public function createFulfilled($value = null)
|
public function createFulfilled($value = null)
|
||||||
{
|
{
|
||||||
$promise = new SyncPromise();
|
$promise = new SyncPromise();
|
||||||
|
|
||||||
return new Promise($promise->resolve($value), $this);
|
return new Promise($promise->resolve($value), $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +82,6 @@ class SyncPromiseAdapter implements PromiseAdapter
|
||||||
public function createRejected($reason)
|
public function createRejected($reason)
|
||||||
{
|
{
|
||||||
$promise = new SyncPromise();
|
$promise = new SyncPromise();
|
||||||
|
|
||||||
return new Promise($promise->reject($reason), $this);
|
return new Promise($promise->reject($reason), $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,14 +100,12 @@ class SyncPromiseAdapter implements PromiseAdapter
|
||||||
if ($promiseOrValue instanceof Promise) {
|
if ($promiseOrValue instanceof Promise) {
|
||||||
$result[$index] = null;
|
$result[$index] = null;
|
||||||
$promiseOrValue->then(
|
$promiseOrValue->then(
|
||||||
static function ($value) use ($index, &$count, $total, &$result, $all) {
|
function($value) use ($index, &$count, $total, &$result, $all) {
|
||||||
$result[$index] = $value;
|
$result[$index] = $value;
|
||||||
$count++;
|
$count++;
|
||||||
if ($count < $total) {
|
if ($count >= $total) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$all->resolve($result);
|
$all->resolve($result);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[$all, 'reject']
|
[$all, 'reject']
|
||||||
);
|
);
|
||||||
|
@ -132,14 +117,14 @@ class SyncPromiseAdapter implements PromiseAdapter
|
||||||
if ($count === $total) {
|
if ($count === $total) {
|
||||||
$all->resolve($result);
|
$all->resolve($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise($all, $this);
|
return new Promise($all, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously wait when promise completes
|
* Synchronously wait when promise completes
|
||||||
*
|
*
|
||||||
* @return ExecutionResult
|
* @param Promise $promise
|
||||||
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function wait(Promise $promise)
|
public function wait(Promise $promise)
|
||||||
{
|
{
|
||||||
|
@ -147,7 +132,8 @@ class SyncPromiseAdapter implements PromiseAdapter
|
||||||
$dfdQueue = Deferred::getQueue();
|
$dfdQueue = Deferred::getQueue();
|
||||||
$promiseQueue = SyncPromise::getQueue();
|
$promiseQueue = SyncPromise::getQueue();
|
||||||
|
|
||||||
while ($promise->adoptedPromise->state === SyncPromise::PENDING &&
|
while (
|
||||||
|
$promise->adoptedPromise->state === SyncPromise::PENDING &&
|
||||||
!($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
|
!($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
|
||||||
) {
|
) {
|
||||||
Deferred::runQueue();
|
Deferred::runQueue();
|
||||||
|
@ -160,17 +146,17 @@ class SyncPromiseAdapter implements PromiseAdapter
|
||||||
|
|
||||||
if ($syncPromise->state === SyncPromise::FULFILLED) {
|
if ($syncPromise->state === SyncPromise::FULFILLED) {
|
||||||
return $syncPromise->result;
|
return $syncPromise->result;
|
||||||
}
|
} else if ($syncPromise->state === SyncPromise::REJECTED) {
|
||||||
|
|
||||||
if ($syncPromise->state === SyncPromise::REJECTED) {
|
|
||||||
throw $syncPromise->result;
|
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
|
* Execute just before starting to run promise completion
|
||||||
|
*
|
||||||
|
* @param Promise $promise
|
||||||
*/
|
*/
|
||||||
protected function beforeWait(Promise $promise)
|
protected function beforeWait(Promise $promise)
|
||||||
{
|
{
|
||||||
|
@ -178,6 +164,8 @@ class SyncPromiseAdapter implements PromiseAdapter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute while running promise completion
|
* Execute while running promise completion
|
||||||
|
*
|
||||||
|
* @param Promise $promise
|
||||||
*/
|
*/
|
||||||
protected function onWait(Promise $promise)
|
protected function onWait(Promise $promise)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,39 +1,38 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Executor\Promise;
|
namespace GraphQL\Executor\Promise;
|
||||||
|
|
||||||
use GraphQL\Executor\Promise\Adapter\SyncPromise;
|
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use React\Promise\Promise as ReactPromise;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience wrapper for promises represented by Promise Adapter
|
* Convenience wrapper for promises represented by Promise Adapter
|
||||||
*/
|
*/
|
||||||
class Promise
|
class Promise
|
||||||
{
|
{
|
||||||
/** @var SyncPromise|ReactPromise */
|
|
||||||
public $adoptedPromise;
|
|
||||||
|
|
||||||
/** @var PromiseAdapter */
|
|
||||||
private $adapter;
|
private $adapter;
|
||||||
|
|
||||||
|
public $adoptedPromise;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Promise constructor.
|
||||||
|
*
|
||||||
* @param mixed $adoptedPromise
|
* @param mixed $adoptedPromise
|
||||||
|
* @param PromiseAdapter $adapter
|
||||||
*/
|
*/
|
||||||
public function __construct($adoptedPromise, PromiseAdapter $adapter)
|
public function __construct($adoptedPromise, PromiseAdapter $adapter)
|
||||||
{
|
{
|
||||||
Utils::invariant(! $adoptedPromise instanceof self, 'Expecting promise from adapted system, got ' . self::class);
|
Utils::invariant(!$adoptedPromise instanceof self, 'Expecting promise from adapted system, got ' . __CLASS__);
|
||||||
|
|
||||||
$this->adapter = $adapter;
|
$this->adapter = $adapter;
|
||||||
$this->adoptedPromise = $adoptedPromise;
|
$this->adoptedPromise = $adoptedPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param callable|null $onFulfilled
|
||||||
|
* @param callable|null $onRejected
|
||||||
|
*
|
||||||
* @return Promise
|
* @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);
|
return $this->adapter->then($this, $onFulfilled, $onRejected);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Executor\Promise;
|
namespace GraphQL\Executor\Promise;
|
||||||
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a means for integration of async PHP platforms ([related docs](data-fetching.md#async-php))
|
* Provides a means for integration of async PHP platforms ([related docs](data-fetching.md#async-php))
|
||||||
*/
|
*/
|
||||||
|
@ -14,22 +9,18 @@ interface PromiseAdapter
|
||||||
/**
|
/**
|
||||||
* Return true if the value is a promise or a deferred of the underlying platform
|
* Return true if the value is a promise or a deferred of the underlying platform
|
||||||
*
|
*
|
||||||
* @param mixed $value
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param mixed $value
|
||||||
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isThenable($value);
|
public function isThenable($value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts thenable of the underlying platform into GraphQL\Executor\Promise\Promise instance
|
* Converts thenable of the underlying platform into GraphQL\Executor\Promise\Promise instance
|
||||||
*
|
*
|
||||||
* @param object $thenable
|
|
||||||
*
|
|
||||||
* @return Promise
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param object $thenable
|
||||||
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public function convertThenable($thenable);
|
public function convertThenable($thenable);
|
||||||
|
|
||||||
|
@ -37,11 +28,14 @@ interface PromiseAdapter
|
||||||
* Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described
|
* 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.
|
* in Promises/A+ specs. Then returns new wrapped instance of GraphQL\Executor\Promise\Promise.
|
||||||
*
|
*
|
||||||
* @return Promise
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param Promise $promise
|
||||||
|
* @param callable|null $onFulfilled
|
||||||
|
* @param callable|null $onRejected
|
||||||
|
*
|
||||||
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
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
|
* Creates a Promise
|
||||||
|
@ -49,20 +43,18 @@ interface PromiseAdapter
|
||||||
* Expected resolver signature:
|
* Expected resolver signature:
|
||||||
* function(callable $resolve, callable $reject)
|
* function(callable $resolve, callable $reject)
|
||||||
*
|
*
|
||||||
* @return Promise
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param callable $resolver
|
||||||
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public function create(callable $resolver);
|
public function create(callable $resolver);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a fulfilled Promise for a value if the value is not a promise.
|
* Creates a fulfilled Promise for a value if the value is not a promise.
|
||||||
*
|
*
|
||||||
* @param mixed $value
|
|
||||||
*
|
|
||||||
* @return Promise
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param mixed $value
|
||||||
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public function createFulfilled($value = null);
|
public function createFulfilled($value = null);
|
||||||
|
|
||||||
|
@ -70,11 +62,9 @@ interface PromiseAdapter
|
||||||
* Creates a rejected promise for a reason if the reason is not a promise. If
|
* 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.
|
* the provided reason is a promise, then it is returned as-is.
|
||||||
*
|
*
|
||||||
* @param Throwable $reason
|
|
||||||
*
|
|
||||||
* @return Promise
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param \Throwable $reason
|
||||||
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public function createRejected($reason);
|
public function createRejected($reason);
|
||||||
|
|
||||||
|
@ -82,11 +72,9 @@ interface PromiseAdapter
|
||||||
* Given an array of promises (or values), returns a promise that is fulfilled when all the
|
* Given an array of promises (or values), returns a promise that is fulfilled when all the
|
||||||
* items in the array are fulfilled.
|
* items in the array are fulfilled.
|
||||||
*
|
*
|
||||||
* @param Promise[]|mixed[] $promisesOrValues Promises or values.
|
|
||||||
*
|
|
||||||
* @return Promise
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @param array $promisesOrValues Promises or values.
|
||||||
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public function all(array $promisesOrValues);
|
public function all(array $promisesOrValues);
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Executor;
|
namespace GraphQL\Executor;
|
||||||
|
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Error\InvariantViolation;
|
||||||
use GraphQL\Language\AST\ArgumentNode;
|
use GraphQL\Language\AST\ArgumentNode;
|
||||||
use GraphQL\Language\AST\DirectiveNode;
|
use GraphQL\Language\AST\DirectiveNode;
|
||||||
use GraphQL\Language\AST\EnumValueDefinitionNode;
|
use GraphQL\Language\AST\EnumValueDefinitionNode;
|
||||||
|
@ -12,27 +11,23 @@ use GraphQL\Language\AST\FieldDefinitionNode;
|
||||||
use GraphQL\Language\AST\FieldNode;
|
use GraphQL\Language\AST\FieldNode;
|
||||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||||
use GraphQL\Language\AST\InlineFragmentNode;
|
use GraphQL\Language\AST\InlineFragmentNode;
|
||||||
use GraphQL\Language\AST\Node;
|
|
||||||
use GraphQL\Language\AST\NodeList;
|
use GraphQL\Language\AST\NodeList;
|
||||||
use GraphQL\Language\AST\ValueNode;
|
|
||||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
|
||||||
use GraphQL\Language\AST\VariableNode;
|
use GraphQL\Language\AST\VariableNode;
|
||||||
|
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||||
use GraphQL\Language\Printer;
|
use GraphQL\Language\Printer;
|
||||||
|
use GraphQL\Type\Schema;
|
||||||
use GraphQL\Type\Definition\Directive;
|
use GraphQL\Type\Definition\Directive;
|
||||||
use GraphQL\Type\Definition\FieldDefinition;
|
use GraphQL\Type\Definition\FieldDefinition;
|
||||||
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
use GraphQL\Type\Definition\InputType;
|
use GraphQL\Type\Definition\InputType;
|
||||||
|
use GraphQL\Type\Definition\LeafType;
|
||||||
|
use GraphQL\Type\Definition\ListOfType;
|
||||||
use GraphQL\Type\Definition\NonNull;
|
use GraphQL\Type\Definition\NonNull;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Schema;
|
|
||||||
use GraphQL\Utils\AST;
|
use GraphQL\Utils\AST;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use GraphQL\Utils\Value;
|
use GraphQL\Validator\DocumentValidator;
|
||||||
use stdClass;
|
|
||||||
use Throwable;
|
|
||||||
use function array_key_exists;
|
|
||||||
use function array_map;
|
|
||||||
use function sprintf;
|
|
||||||
|
|
||||||
class Values
|
class Values
|
||||||
{
|
{
|
||||||
|
@ -41,78 +36,134 @@ class Values
|
||||||
* variable definitions and arbitrary input. If the input cannot be coerced
|
* variable definitions and arbitrary input. If the input cannot be coerced
|
||||||
* to match the variable definitions, a Error will be thrown.
|
* to match the variable definitions, a Error will be thrown.
|
||||||
*
|
*
|
||||||
* @param VariableDefinitionNode[] $varDefNodes
|
* @param Schema $schema
|
||||||
* @param mixed[] $inputs
|
* @param VariableDefinitionNode[] $definitionNodes
|
||||||
*
|
* @param array $inputs
|
||||||
* @return mixed[]
|
* @return array
|
||||||
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
public static function getVariableValues(Schema $schema, $varDefNodes, array $inputs)
|
public static function getVariableValues(Schema $schema, $definitionNodes, array $inputs)
|
||||||
{
|
{
|
||||||
$errors = [];
|
|
||||||
$coercedValues = [];
|
$coercedValues = [];
|
||||||
foreach ($varDefNodes as $varDefNode) {
|
foreach ($definitionNodes as $definitionNode) {
|
||||||
$varName = $varDefNode->variable->name->value;
|
$varName = $definitionNode->variable->name->value;
|
||||||
/** @var InputType|Type $varType */
|
$varType = TypeInfo::typeFromAST($schema, $definitionNode->type);
|
||||||
$varType = TypeInfo::typeFromAST($schema, $varDefNode->type);
|
|
||||||
|
|
||||||
if (Type::isInputType($varType)) {
|
if (!Type::isInputType($varType)) {
|
||||||
if (array_key_exists($varName, $inputs)) {
|
throw new Error(
|
||||||
$value = $inputs[$varName];
|
'Variable "$'.$varName.'" expected value of type ' .
|
||||||
$coerced = Value::coerceValue($value, $varType, $varDefNode);
|
'"' . Printer::doPrint($definitionNode->type) . '" which cannot be used as an input type.',
|
||||||
/** @var Error[] $coercionErrors */
|
[$definitionNode->type]
|
||||||
$coercionErrors = $coerced['errors'];
|
|
||||||
if (empty($coercionErrors)) {
|
|
||||||
$coercedValues[$varName] = $coerced['value'];
|
|
||||||
} else {
|
|
||||||
$messagePrelude = sprintf(
|
|
||||||
'Variable "$%s" got invalid value %s; ',
|
|
||||||
$varName,
|
|
||||||
Utils::printSafeJson($value)
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($coercionErrors as $error) {
|
|
||||||
$errors[] = new Error(
|
|
||||||
$messagePrelude . $error->getMessage(),
|
|
||||||
$error->getNodes(),
|
|
||||||
$error->getSource(),
|
|
||||||
$error->getPositions(),
|
|
||||||
$error->getPath(),
|
|
||||||
$error,
|
|
||||||
$error->getExtensions()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!array_key_exists($varName, $inputs)) {
|
||||||
|
$defaultValue = $definitionNode->defaultValue;
|
||||||
|
if ($defaultValue) {
|
||||||
|
$coercedValues[$varName] = AST::valueFromAST($defaultValue, $varType);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if ($varType instanceof NonNull) {
|
if ($varType instanceof NonNull) {
|
||||||
$errors[] = new Error(
|
throw new Error(
|
||||||
sprintf(
|
'Variable "$'.$varName .'" of required type ' .
|
||||||
'Variable "$%s" of required type "%s" was not provided.',
|
'"'. Utils::printSafe($varType) . '" was not provided.',
|
||||||
$varName,
|
[$definitionNode]
|
||||||
$varType
|
|
||||||
),
|
|
||||||
[$varDefNode]
|
|
||||||
);
|
);
|
||||||
} elseif ($varDefNode->defaultValue !== null) {
|
|
||||||
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$errors[] = new Error(
|
$value = $inputs[$varName];
|
||||||
sprintf(
|
$errors = self::isValidPHPValue($value, $varType);
|
||||||
'Variable "$%s" expected value of type "%s" which cannot be used as an input type.',
|
if (!empty($errors)) {
|
||||||
$varName,
|
$message = "\n" . implode("\n", $errors);
|
||||||
Printer::doPrint($varDefNode->type)
|
throw new Error(
|
||||||
),
|
'Variable "$' . $varName . '" got invalid value ' .
|
||||||
[$varDefNode->type]
|
json_encode($value) . '.' . $message,
|
||||||
|
[$definitionNode]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$coercedValue = self::coerceValue($varType, $value);
|
||||||
|
Utils::invariant($coercedValue !== Utils::undefined(), 'Should have reported error.');
|
||||||
|
$coercedValues[$varName] = $coercedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $coercedValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($errors)) {
|
/**
|
||||||
return [$errors, 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|\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 [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [null, $coercedValues];
|
$coercedValues = [];
|
||||||
|
$undefined = Utils::undefined();
|
||||||
|
|
||||||
|
/** @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 ($coercedValue === $undefined) {
|
||||||
|
$errors = DocumentValidator::isValidLiteralValue($argType, $valueNode);
|
||||||
|
$message = !empty($errors) ? ("\n" . implode("\n", $errors)) : '';
|
||||||
|
throw new Error(
|
||||||
|
'Argument "' . $name . '" got invalid value ' . Printer::doPrint($valueNode) . '.' . $message,
|
||||||
|
[ $argumentNode->value ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$coercedValues[$name] = $coercedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $coercedValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -122,158 +173,213 @@ class Values
|
||||||
*
|
*
|
||||||
* If the directive does not exist on the node, returns undefined.
|
* If the directive does not exist on the node, returns undefined.
|
||||||
*
|
*
|
||||||
|
* @param Directive $directiveDef
|
||||||
* @param FragmentSpreadNode | FieldNode | InlineFragmentNode | EnumValueDefinitionNode | FieldDefinitionNode $node
|
* @param FragmentSpreadNode | FieldNode | InlineFragmentNode | EnumValueDefinitionNode | FieldDefinitionNode $node
|
||||||
* @param mixed[]|null $variableValues
|
* @param array|null $variableValues
|
||||||
*
|
*
|
||||||
* @return mixed[]|null
|
* @return array|null
|
||||||
*/
|
*/
|
||||||
public static function getDirectiveValues(Directive $directiveDef, $node, $variableValues = null)
|
public static function getDirectiveValues(Directive $directiveDef, $node, $variableValues = null)
|
||||||
{
|
{
|
||||||
if (isset($node->directives) && $node->directives instanceof NodeList) {
|
if (isset($node->directives) && $node->directives instanceof NodeList) {
|
||||||
$directiveNode = Utils::find(
|
$directiveNode = Utils::find($node->directives, function(DirectiveNode $directive) use ($directiveDef) {
|
||||||
$node->directives,
|
|
||||||
static function (DirectiveNode $directive) use ($directiveDef) {
|
|
||||||
return $directive->name->value === $directiveDef->name;
|
return $directive->name->value === $directiveDef->name;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if ($directiveNode !== null) {
|
if ($directiveNode) {
|
||||||
return self::getArgumentValues($directiveDef, $directiveNode, $variableValues);
|
return self::getArgumentValues($directiveDef, $directiveNode, $variableValues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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)
|
* @deprecated as of 8.0 (Moved to \GraphQL\Utils\AST::valueFromAST)
|
||||||
*
|
*
|
||||||
* @param ValueNode $valueNode
|
* @param $valueNode
|
||||||
* @param mixed[]|null $variables
|
* @param InputType $type
|
||||||
*
|
* @param null $variables
|
||||||
* @return mixed[]|stdClass|null
|
* @return array|null|\stdClass
|
||||||
*/
|
*/
|
||||||
public static function valueFromAST($valueNode, InputType $type, ?array $variables = null)
|
public static function valueFromAST($valueNode, InputType $type, $variables = null)
|
||||||
{
|
{
|
||||||
return AST::valueFromAST($valueNode, $type, $variables);
|
return AST::valueFromAST($valueNode, $type, $variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated as of 0.12 (Use coerceValue() directly for richer information)
|
* Given a PHP value and a GraphQL type, determine if the value will be
|
||||||
|
* accepted for that type. This is primarily useful for validating the
|
||||||
|
* runtime values of query variables.
|
||||||
*
|
*
|
||||||
* @param mixed[] $value
|
* @param $value
|
||||||
*
|
* @param InputType $type
|
||||||
* @return string[]
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function isValidPHPValue($value, InputType $type)
|
public static function isValidPHPValue($value, InputType $type)
|
||||||
{
|
{
|
||||||
$errors = Value::coerceValue($value, $type)['errors'];
|
// A value must be provided if the type is non-null.
|
||||||
|
if ($type instanceof NonNull) {
|
||||||
|
if (null === $value) {
|
||||||
|
return ['Expected "' . Utils::printSafe($type) . '", found null.'];
|
||||||
|
}
|
||||||
|
return self::isValidPHPValue($value, $type->getWrappedType());
|
||||||
|
}
|
||||||
|
|
||||||
return $errors
|
if (null === $value) {
|
||||||
? array_map(
|
return [];
|
||||||
static function (Throwable $error) {
|
}
|
||||||
return $error->getMessage();
|
|
||||||
},
|
// Lists accept a non-list value as a list of one.
|
||||||
$errors
|
if ($type instanceof ListOfType) {
|
||||||
)
|
$itemType = $type->getWrappedType();
|
||||||
: [];
|
if (is_array($value)) {
|
||||||
|
$tmp = [];
|
||||||
|
foreach ($value as $index => $item) {
|
||||||
|
$errors = self::isValidPHPValue($item, $itemType);
|
||||||
|
$tmp = array_merge($tmp, Utils::map($errors, function ($error) use ($index) {
|
||||||
|
return "In element #$index: $error";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return $tmp;
|
||||||
|
}
|
||||||
|
return self::isValidPHPValue($value, $itemType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input objects check each defined field.
|
||||||
|
if ($type instanceof InputObjectType) {
|
||||||
|
if (!is_object($value) && !is_array($value)) {
|
||||||
|
return ["Expected \"{$type->name}\", found not an object."];
|
||||||
|
}
|
||||||
|
$fields = $type->getFields();
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
// Ensure every provided field is defined.
|
||||||
|
$props = is_object($value) ? get_object_vars($value) : $value;
|
||||||
|
foreach ($props as $providedField => $tmp) {
|
||||||
|
if (!isset($fields[$providedField])) {
|
||||||
|
$errors[] = "In field \"{$providedField}\": Unknown field.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure every defined field is valid.
|
||||||
|
foreach ($fields as $fieldName => $tmp) {
|
||||||
|
$newErrors = self::isValidPHPValue(isset($value[$fieldName]) ? $value[$fieldName] : null, $fields[$fieldName]->getType());
|
||||||
|
$errors = array_merge(
|
||||||
|
$errors,
|
||||||
|
Utils::map($newErrors, function ($error) use ($fieldName) {
|
||||||
|
return "In field \"{$fieldName}\": {$error}";
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type instanceof LeafType) {
|
||||||
|
try {
|
||||||
|
// Scalar/Enum input checks to ensure the type can parse the value to
|
||||||
|
// a non-null value.
|
||||||
|
$parseResult = $type->parseValue($value);
|
||||||
|
if (null === $parseResult && !$type->isValidValue($value)) {
|
||||||
|
$v = Utils::printSafe($value);
|
||||||
|
return [
|
||||||
|
"Expected type \"{$type->name}\", found $v."
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
"Expected type \"{$type->name}\", found " . Utils::printSafe($value) . ': ' .
|
||||||
|
$e->getMessage()
|
||||||
|
];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return [
|
||||||
|
"Expected type \"{$type->name}\", found " . Utils::printSafe($value) . ': ' .
|
||||||
|
$e->getMessage()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvariantViolation('Must be input type');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a type and any value, return a runtime value coerced to match the type.
|
||||||
|
*/
|
||||||
|
private static function coerceValue(Type $type, $value)
|
||||||
|
{
|
||||||
|
$undefined = Utils::undefined();
|
||||||
|
if ($value === $undefined) {
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type instanceof NonNull) {
|
||||||
|
if ($value === null) {
|
||||||
|
// Intentionally return no value.
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
return self::coerceValue($type->getWrappedType(), $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type instanceof ListOfType) {
|
||||||
|
$itemType = $type->getWrappedType();
|
||||||
|
if (is_array($value) || $value instanceof \Traversable) {
|
||||||
|
$coercedValues = [];
|
||||||
|
foreach ($value as $item) {
|
||||||
|
$itemValue = self::coerceValue($itemType, $item);
|
||||||
|
if ($undefined === $itemValue) {
|
||||||
|
// Intentionally return no value.
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
$coercedValues[] = $itemValue;
|
||||||
|
}
|
||||||
|
return $coercedValues;
|
||||||
|
} else {
|
||||||
|
$coercedValue = self::coerceValue($itemType, $value);
|
||||||
|
if ($coercedValue === $undefined) {
|
||||||
|
// Intentionally return no value.
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
return [$coercedValue];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type instanceof InputObjectType) {
|
||||||
|
$coercedObj = [];
|
||||||
|
$fields = $type->getFields();
|
||||||
|
foreach ($fields as $fieldName => $field) {
|
||||||
|
if (!array_key_exists($fieldName, $value)) {
|
||||||
|
if ($field->defaultValueExists()) {
|
||||||
|
$coercedObj[$fieldName] = $field->defaultValue;
|
||||||
|
} else if ($field->getType() instanceof NonNull) {
|
||||||
|
// Intentionally return no value.
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$fieldValue = self::coerceValue($field->getType(), $value[$fieldName]);
|
||||||
|
if ($fieldValue === $undefined) {
|
||||||
|
// Intentionally return no value.
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
$coercedObj[$fieldName] = $fieldValue;
|
||||||
|
}
|
||||||
|
return $coercedObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type instanceof LeafType) {
|
||||||
|
$parsed = $type->parseValue($value);
|
||||||
|
if (null === $parsed) {
|
||||||
|
// null or invalid values represent a failure to parse correctly,
|
||||||
|
// in which case no value is returned.
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
return $parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvariantViolation('Must be input type');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,283 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
<?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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
<?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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,952 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
<?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;
|
|
||||||
}
|
|
||||||
}
|
|
195
src/GraphQL.php
195
src/GraphQL.php
|
@ -1,7 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL;
|
namespace GraphQL;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
|
@ -9,21 +6,15 @@ use GraphQL\Executor\ExecutionResult;
|
||||||
use GraphQL\Executor\Executor;
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
|
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
|
||||||
use GraphQL\Executor\Promise\Promise;
|
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\AST\DocumentNode;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
|
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||||
use GraphQL\Type\Definition\Directive;
|
use GraphQL\Type\Definition\Directive;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Schema as SchemaType;
|
|
||||||
use GraphQL\Validator\DocumentValidator;
|
use GraphQL\Validator\DocumentValidator;
|
||||||
|
use GraphQL\Validator\Rules\AbstractValidationRule;
|
||||||
use GraphQL\Validator\Rules\QueryComplexity;
|
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.
|
* This is the primary facade for fulfilling GraphQL operations.
|
||||||
|
@ -66,37 +57,33 @@ class GraphQL
|
||||||
* Empty array would allow to skip query validation (may be convenient for persisted
|
* Empty array would allow to skip query validation (may be convenient for persisted
|
||||||
* queries which are validated before persisting and assumed valid during execution)
|
* queries which are validated before persisting and assumed valid during execution)
|
||||||
*
|
*
|
||||||
|
* @api
|
||||||
|
* @param \GraphQL\Type\Schema $schema
|
||||||
* @param string|DocumentNode $source
|
* @param string|DocumentNode $source
|
||||||
* @param mixed $rootValue
|
* @param mixed $rootValue
|
||||||
* @param mixed $context
|
* @param mixed $context
|
||||||
* @param mixed[]|null $variableValues
|
* @param array|null $variableValues
|
||||||
* @param ValidationRule[] $validationRules
|
* @param string|null $operationName
|
||||||
|
* @param callable $fieldResolver
|
||||||
|
* @param array $validationRules
|
||||||
*
|
*
|
||||||
* @api
|
* @return ExecutionResult
|
||||||
*/
|
*/
|
||||||
public static function executeQuery(
|
public static function executeQuery(
|
||||||
SchemaType $schema,
|
\GraphQL\Type\Schema $schema,
|
||||||
$source,
|
$source,
|
||||||
$rootValue = null,
|
$rootValue = null,
|
||||||
$context = null,
|
$context = null,
|
||||||
$variableValues = null,
|
$variableValues = null,
|
||||||
?string $operationName = null,
|
$operationName = null,
|
||||||
?callable $fieldResolver = null,
|
callable $fieldResolver = null,
|
||||||
?array $validationRules = null
|
array $validationRules = null
|
||||||
) : ExecutionResult {
|
)
|
||||||
|
{
|
||||||
$promiseAdapter = new SyncPromiseAdapter();
|
$promiseAdapter = new SyncPromiseAdapter();
|
||||||
|
|
||||||
$promise = self::promiseToExecute(
|
$promise = self::promiseToExecute($promiseAdapter, $schema, $source, $rootValue, $context,
|
||||||
$promiseAdapter,
|
$variableValues, $operationName, $fieldResolver, $validationRules);
|
||||||
$schema,
|
|
||||||
$source,
|
|
||||||
$rootValue,
|
|
||||||
$context,
|
|
||||||
$variableValues,
|
|
||||||
$operationName,
|
|
||||||
$fieldResolver,
|
|
||||||
$validationRules
|
|
||||||
);
|
|
||||||
|
|
||||||
return $promiseAdapter->wait($promise);
|
return $promiseAdapter->wait($promise);
|
||||||
}
|
}
|
||||||
|
@ -105,25 +92,31 @@ class GraphQL
|
||||||
* Same as executeQuery(), but requires PromiseAdapter and always returns a Promise.
|
* Same as executeQuery(), but requires PromiseAdapter and always returns a Promise.
|
||||||
* Useful for Async PHP platforms.
|
* Useful for Async PHP platforms.
|
||||||
*
|
*
|
||||||
|
* @api
|
||||||
|
* @param PromiseAdapter $promiseAdapter
|
||||||
|
* @param \GraphQL\Type\Schema $schema
|
||||||
* @param string|DocumentNode $source
|
* @param string|DocumentNode $source
|
||||||
* @param mixed $rootValue
|
* @param mixed $rootValue
|
||||||
* @param mixed $context
|
* @param mixed $context
|
||||||
* @param mixed[]|null $variableValues
|
* @param array|null $variableValues
|
||||||
* @param ValidationRule[]|null $validationRules
|
* @param string|null $operationName
|
||||||
|
* @param callable $fieldResolver
|
||||||
|
* @param array $validationRules
|
||||||
*
|
*
|
||||||
* @api
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public static function promiseToExecute(
|
public static function promiseToExecute(
|
||||||
PromiseAdapter $promiseAdapter,
|
PromiseAdapter $promiseAdapter,
|
||||||
SchemaType $schema,
|
\GraphQL\Type\Schema $schema,
|
||||||
$source,
|
$source,
|
||||||
$rootValue = null,
|
$rootValue = null,
|
||||||
$context = null,
|
$context = null,
|
||||||
$variableValues = null,
|
$variableValues = null,
|
||||||
?string $operationName = null,
|
$operationName = null,
|
||||||
?callable $fieldResolver = null,
|
callable $fieldResolver = null,
|
||||||
?array $validationRules = null
|
array $validationRules = null
|
||||||
) : Promise {
|
)
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
if ($source instanceof DocumentNode) {
|
if ($source instanceof DocumentNode) {
|
||||||
$documentNode = $source;
|
$documentNode = $source;
|
||||||
|
@ -132,18 +125,16 @@ class GraphQL
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME
|
// FIXME
|
||||||
if (empty($validationRules)) {
|
if (!empty($validationRules)) {
|
||||||
|
foreach ($validationRules as $rule) {
|
||||||
|
if ($rule instanceof QueryComplexity) {
|
||||||
|
$rule->setRawVariableValues($variableValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
/** @var QueryComplexity $queryComplexity */
|
/** @var QueryComplexity $queryComplexity */
|
||||||
$queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
|
$queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
|
||||||
$queryComplexity->setRawVariableValues($variableValues);
|
$queryComplexity->setRawVariableValues($variableValues);
|
||||||
} else {
|
|
||||||
foreach ($validationRules as $rule) {
|
|
||||||
if (! ($rule instanceof QueryComplexity)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rule->setRawVariableValues($variableValues);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);
|
$validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);
|
||||||
|
@ -152,8 +143,7 @@ class GraphQL
|
||||||
return $promiseAdapter->createFulfilled(
|
return $promiseAdapter->createFulfilled(
|
||||||
new ExecutionResult(null, $validationErrors)
|
new ExecutionResult(null, $validationErrors)
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
|
|
||||||
return Executor::promiseToExecute(
|
return Executor::promiseToExecute(
|
||||||
$promiseAdapter,
|
$promiseAdapter,
|
||||||
$schema,
|
$schema,
|
||||||
|
@ -164,6 +154,7 @@ class GraphQL
|
||||||
$operationName,
|
$operationName,
|
||||||
$fieldResolver
|
$fieldResolver
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} catch (Error $e) {
|
} catch (Error $e) {
|
||||||
return $promiseAdapter->createFulfilled(
|
return $promiseAdapter->createFulfilled(
|
||||||
new ExecutionResult(null, [$e])
|
new ExecutionResult(null, [$e])
|
||||||
|
@ -174,29 +165,25 @@ class GraphQL
|
||||||
/**
|
/**
|
||||||
* @deprecated Use executeQuery()->toArray() instead
|
* @deprecated Use executeQuery()->toArray() instead
|
||||||
*
|
*
|
||||||
|
* @param \GraphQL\Type\Schema $schema
|
||||||
* @param string|DocumentNode $source
|
* @param string|DocumentNode $source
|
||||||
* @param mixed $rootValue
|
* @param mixed $rootValue
|
||||||
* @param mixed $contextValue
|
* @param mixed $contextValue
|
||||||
* @param mixed[]|null $variableValues
|
* @param array|null $variableValues
|
||||||
*
|
* @param string|null $operationName
|
||||||
* @return Promise|mixed[]
|
* @return Promise|array
|
||||||
*/
|
*/
|
||||||
public static function execute(
|
public static function execute(
|
||||||
SchemaType $schema,
|
\GraphQL\Type\Schema $schema,
|
||||||
$source,
|
$source,
|
||||||
$rootValue = null,
|
$rootValue = null,
|
||||||
$contextValue = null,
|
$contextValue = null,
|
||||||
$variableValues = null,
|
$variableValues = null,
|
||||||
?string $operationName = null
|
$operationName = null
|
||||||
) {
|
)
|
||||||
trigger_error(
|
{
|
||||||
__METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement',
|
|
||||||
E_USER_DEPRECATED
|
|
||||||
);
|
|
||||||
|
|
||||||
$promiseAdapter = Executor::getPromiseAdapter();
|
|
||||||
$result = self::promiseToExecute(
|
$result = self::promiseToExecute(
|
||||||
$promiseAdapter,
|
$promiseAdapter = Executor::getPromiseAdapter(),
|
||||||
$schema,
|
$schema,
|
||||||
$source,
|
$source,
|
||||||
$rootValue,
|
$rootValue,
|
||||||
|
@ -208,40 +195,36 @@ class GraphQL
|
||||||
if ($promiseAdapter instanceof SyncPromiseAdapter) {
|
if ($promiseAdapter instanceof SyncPromiseAdapter) {
|
||||||
$result = $promiseAdapter->wait($result)->toArray();
|
$result = $promiseAdapter->wait($result)->toArray();
|
||||||
} else {
|
} else {
|
||||||
$result = $result->then(static function (ExecutionResult $r) {
|
$result = $result->then(function(ExecutionResult $r) {
|
||||||
return $r->toArray();
|
return $r->toArray();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated renamed to executeQuery()
|
* @deprecated renamed to executeQuery()
|
||||||
*
|
*
|
||||||
|
* @param \GraphQL\Type\Schema $schema
|
||||||
* @param string|DocumentNode $source
|
* @param string|DocumentNode $source
|
||||||
* @param mixed $rootValue
|
* @param mixed $rootValue
|
||||||
* @param mixed $contextValue
|
* @param mixed $contextValue
|
||||||
* @param mixed[]|null $variableValues
|
* @param array|null $variableValues
|
||||||
|
* @param string|null $operationName
|
||||||
*
|
*
|
||||||
* @return ExecutionResult|Promise
|
* @return ExecutionResult|Promise
|
||||||
*/
|
*/
|
||||||
public static function executeAndReturnResult(
|
public static function executeAndReturnResult(
|
||||||
SchemaType $schema,
|
\GraphQL\Type\Schema $schema,
|
||||||
$source,
|
$source,
|
||||||
$rootValue = null,
|
$rootValue = null,
|
||||||
$contextValue = null,
|
$contextValue = null,
|
||||||
$variableValues = null,
|
$variableValues = null,
|
||||||
?string $operationName = null
|
$operationName = null
|
||||||
) {
|
)
|
||||||
trigger_error(
|
{
|
||||||
__METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement',
|
|
||||||
E_USER_DEPRECATED
|
|
||||||
);
|
|
||||||
|
|
||||||
$promiseAdapter = Executor::getPromiseAdapter();
|
|
||||||
$result = self::promiseToExecute(
|
$result = self::promiseToExecute(
|
||||||
$promiseAdapter,
|
$promiseAdapter = Executor::getPromiseAdapter(),
|
||||||
$schema,
|
$schema,
|
||||||
$source,
|
$source,
|
||||||
$rootValue,
|
$rootValue,
|
||||||
|
@ -249,22 +232,19 @@ class GraphQL
|
||||||
$variableValues,
|
$variableValues,
|
||||||
$operationName
|
$operationName
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($promiseAdapter instanceof SyncPromiseAdapter) {
|
if ($promiseAdapter instanceof SyncPromiseAdapter) {
|
||||||
$result = $promiseAdapter->wait($result);
|
$result = $promiseAdapter->wait($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns directives defined in GraphQL spec
|
* Returns directives defined in GraphQL spec
|
||||||
*
|
*
|
||||||
* @return Directive[]
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @return Directive[]
|
||||||
*/
|
*/
|
||||||
public static function getStandardDirectives() : array
|
public static function getStandardDirectives()
|
||||||
{
|
{
|
||||||
return array_values(Directive::getInternalDirectives());
|
return array_values(Directive::getInternalDirectives());
|
||||||
}
|
}
|
||||||
|
@ -272,79 +252,48 @@ class GraphQL
|
||||||
/**
|
/**
|
||||||
* Returns types defined in GraphQL spec
|
* Returns types defined in GraphQL spec
|
||||||
*
|
*
|
||||||
|
* @api
|
||||||
* @return Type[]
|
* @return Type[]
|
||||||
*
|
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
public static function getStandardTypes() : array
|
public static function getStandardTypes()
|
||||||
{
|
{
|
||||||
return array_values(Type::getStandardTypes());
|
return array_values(Type::getInternalTypes());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Returns standard validation rules implementing GraphQL spec
|
||||||
*
|
*
|
||||||
* @return ValidationRule[]
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
|
* @return AbstractValidationRule[]
|
||||||
*/
|
*/
|
||||||
public static function getStandardValidationRules() : array
|
public static function getStandardValidationRules()
|
||||||
{
|
{
|
||||||
return array_values(DocumentValidator::defaultRules());
|
return array_values(DocumentValidator::defaultRules());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set default resolver implementation
|
* @param callable $fn
|
||||||
*
|
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
public static function setDefaultFieldResolver(callable $fn) : void
|
public static function setDefaultFieldResolver(callable $fn)
|
||||||
{
|
{
|
||||||
Executor::setDefaultFieldResolver($fn);
|
Executor::setDefaultFieldResolver($fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function setPromiseAdapter(?PromiseAdapter $promiseAdapter = null) : void
|
/**
|
||||||
|
* @param PromiseAdapter|null $promiseAdapter
|
||||||
|
*/
|
||||||
|
public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
|
||||||
{
|
{
|
||||||
Executor::setPromiseAdapter($promiseAdapter);
|
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
|
* Returns directives defined in GraphQL spec
|
||||||
*
|
*
|
||||||
* @deprecated Renamed to getStandardDirectives
|
* @deprecated Renamed to getStandardDirectives
|
||||||
*
|
|
||||||
* @return Directive[]
|
* @return Directive[]
|
||||||
*/
|
*/
|
||||||
public static function getInternalDirectives() : array
|
public static function getInternalDirectives()
|
||||||
{
|
{
|
||||||
return self::getStandardDirectives();
|
return self::getStandardDirectives();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class ArgumentNode extends Node
|
class ArgumentNode extends Node
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::ARGUMENT;
|
public $kind = NodeKind::ARGUMENT;
|
||||||
|
|
||||||
/** @var ValueNode */
|
/**
|
||||||
|
* @var ValueNode
|
||||||
|
*/
|
||||||
public $value;
|
public $value;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
|
|
||||||
class BooleanValueNode extends Node implements ValueNode
|
class BooleanValueNode extends Node implements ValueNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::BOOLEAN;
|
public $kind = NodeKind::BOOLEAN;
|
||||||
|
|
||||||
/** @var bool */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $value;
|
public $value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
/**
|
|
||||||
* export type DefinitionNode =
|
|
||||||
* | ExecutableDefinitionNode
|
|
||||||
* | TypeSystemDefinitionNode; // experimental non-spec addition.
|
|
||||||
*/
|
|
||||||
interface DefinitionNode
|
interface DefinitionNode
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* export type DefinitionNode = OperationDefinitionNode
|
||||||
|
* | FragmentDefinitionNode
|
||||||
|
* | TypeSystemDefinitionNode // experimental non-spec addition.
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class DirectiveDefinitionNode extends Node implements TypeSystemDefinitionNode
|
class DirectiveDefinitionNode extends Node implements TypeSystemDefinitionNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $kind = NodeKind::DIRECTIVE_DEFINITION;
|
public $kind = NodeKind::DIRECTIVE_DEFINITION;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/** @var ArgumentNode[] */
|
/**
|
||||||
|
* @var ArgumentNode[]
|
||||||
|
*/
|
||||||
public $arguments;
|
public $arguments;
|
||||||
|
|
||||||
/** @var NameNode[] */
|
/**
|
||||||
|
* @var NameNode[]
|
||||||
|
*/
|
||||||
public $locations;
|
public $locations;
|
||||||
|
|
||||||
/** @var StringValueNode|null */
|
|
||||||
public $description;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class DirectiveNode extends Node
|
class DirectiveNode extends Node
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::DIRECTIVE;
|
public $kind = NodeKind::DIRECTIVE;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/** @var ArgumentNode[] */
|
/**
|
||||||
|
* @var ArgumentNode[]
|
||||||
|
*/
|
||||||
public $arguments;
|
public $arguments;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class DocumentNode extends Node
|
class DocumentNode extends Node
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::DOCUMENT;
|
public $kind = NodeKind::DOCUMENT;
|
||||||
|
|
||||||
/** @var NodeList|DefinitionNode[] */
|
/**
|
||||||
|
* @var DefinitionNode[]
|
||||||
|
*/
|
||||||
public $definitions;
|
public $definitions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class EnumTypeDefinitionNode extends Node implements TypeDefinitionNode
|
class EnumTypeDefinitionNode extends Node implements TypeDefinitionNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $kind = NodeKind::ENUM_TYPE_DEFINITION;
|
public $kind = NodeKind::ENUM_TYPE_DEFINITION;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/** @var DirectiveNode[] */
|
/**
|
||||||
|
* @var DirectiveNode[]
|
||||||
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
/** @var EnumValueDefinitionNode[]|NodeList|null */
|
/**
|
||||||
|
* @var EnumValueDefinitionNode[]
|
||||||
|
*/
|
||||||
public $values;
|
public $values;
|
||||||
|
|
||||||
/** @var StringValueNode|null */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $description;
|
public $description;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
|
||||||
|
|
||||||
class EnumTypeExtensionNode extends Node implements TypeExtensionNode
|
|
||||||
{
|
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::ENUM_TYPE_EXTENSION;
|
|
||||||
|
|
||||||
/** @var NameNode */
|
|
||||||
public $name;
|
|
||||||
|
|
||||||
/** @var DirectiveNode[]|null */
|
|
||||||
public $directives;
|
|
||||||
|
|
||||||
/** @var EnumValueDefinitionNode[]|null */
|
|
||||||
public $values;
|
|
||||||
}
|
|
|
@ -1,20 +1,25 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class EnumValueDefinitionNode extends Node
|
class EnumValueDefinitionNode extends Node
|
||||||
{
|
{
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $kind = NodeKind::ENUM_VALUE_DEFINITION;
|
public $kind = NodeKind::ENUM_VALUE_DEFINITION;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/** @var DirectiveNode[] */
|
/**
|
||||||
|
* @var DirectiveNode[]
|
||||||
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
/** @var StringValueNode|null */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $description;
|
public $description;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class EnumValueNode extends Node implements ValueNode
|
class EnumValueNode extends Node implements ValueNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::ENUM;
|
public $kind = NodeKind::ENUM;
|
||||||
|
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $value;
|
public $value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* export type ExecutableDefinitionNode =
|
|
||||||
* | OperationDefinitionNode
|
|
||||||
* | FragmentDefinitionNode;
|
|
||||||
*/
|
|
||||||
interface ExecutableDefinitionNode extends DefinitionNode
|
|
||||||
{
|
|
||||||
}
|
|
|
@ -1,26 +1,35 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class FieldDefinitionNode extends Node
|
class FieldDefinitionNode extends Node
|
||||||
{
|
{
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $kind = NodeKind::FIELD_DEFINITION;
|
public $kind = NodeKind::FIELD_DEFINITION;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/** @var InputValueDefinitionNode[]|NodeList */
|
/**
|
||||||
|
* @var InputValueDefinitionNode[]
|
||||||
|
*/
|
||||||
public $arguments;
|
public $arguments;
|
||||||
|
|
||||||
/** @var TypeNode */
|
/**
|
||||||
|
* @var TypeNode
|
||||||
|
*/
|
||||||
public $type;
|
public $type;
|
||||||
|
|
||||||
/** @var DirectiveNode[]|NodeList */
|
/**
|
||||||
|
* @var DirectiveNode[]
|
||||||
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
/** @var StringValueNode|null */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $description;
|
public $description;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,32 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class FieldNode extends Node implements SelectionNode
|
class FieldNode extends Node implements SelectionNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::FIELD;
|
public $kind = NodeKind::FIELD;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/** @var NameNode|null */
|
/**
|
||||||
|
* @var NameNode|null
|
||||||
|
*/
|
||||||
public $alias;
|
public $alias;
|
||||||
|
|
||||||
/** @var ArgumentNode[]|null */
|
/**
|
||||||
|
* @var ArgumentNode[]|null
|
||||||
|
*/
|
||||||
public $arguments;
|
public $arguments;
|
||||||
|
|
||||||
/** @var DirectiveNode[]|null */
|
/**
|
||||||
|
* @var DirectiveNode[]|null
|
||||||
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
/** @var SelectionSetNode|null */
|
/**
|
||||||
|
* @var SelectionSetNode|null
|
||||||
|
*/
|
||||||
public $selectionSet;
|
public $selectionSet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class FloatValueNode extends Node implements ValueNode
|
class FloatValueNode extends Node implements ValueNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::FLOAT;
|
public $kind = NodeKind::FLOAT;
|
||||||
|
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $value;
|
public $value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,27 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, HasSelectionSet
|
class FragmentDefinitionNode extends Node implements DefinitionNode, HasSelectionSet
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::FRAGMENT_DEFINITION;
|
public $kind = NodeKind::FRAGMENT_DEFINITION;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note: fragment variable definitions are experimental and may be changed
|
* @var NamedTypeNode
|
||||||
* or removed in the future.
|
|
||||||
*
|
|
||||||
* @var VariableDefinitionNode[]|NodeList
|
|
||||||
*/
|
*/
|
||||||
public $variableDefinitions;
|
|
||||||
|
|
||||||
/** @var NamedTypeNode */
|
|
||||||
public $typeCondition;
|
public $typeCondition;
|
||||||
|
|
||||||
/** @var DirectiveNode[]|NodeList */
|
/**
|
||||||
|
* @var DirectiveNode[]
|
||||||
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
/** @var SelectionSetNode */
|
/**
|
||||||
|
* @var SelectionSetNode
|
||||||
|
*/
|
||||||
public $selectionSet;
|
public $selectionSet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class FragmentSpreadNode extends Node implements SelectionNode
|
class FragmentSpreadNode extends Node implements SelectionNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::FRAGMENT_SPREAD;
|
public $kind = NodeKind::FRAGMENT_SPREAD;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/** @var DirectiveNode[] */
|
/**
|
||||||
|
* @var DirectiveNode[]
|
||||||
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
interface HasSelectionSet
|
interface HasSelectionSet
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class InlineFragmentNode extends Node implements SelectionNode
|
class InlineFragmentNode extends Node implements SelectionNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::INLINE_FRAGMENT;
|
public $kind = NodeKind::INLINE_FRAGMENT;
|
||||||
|
|
||||||
/** @var NamedTypeNode */
|
/**
|
||||||
|
* @var NamedTypeNode
|
||||||
|
*/
|
||||||
public $typeCondition;
|
public $typeCondition;
|
||||||
|
|
||||||
/** @var DirectiveNode[]|null */
|
/**
|
||||||
|
* @var DirectiveNode[]|null
|
||||||
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
/** @var SelectionSetNode */
|
/**
|
||||||
|
* @var SelectionSetNode
|
||||||
|
*/
|
||||||
public $selectionSet;
|
public $selectionSet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class InputObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
|
class InputObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $kind = NodeKind::INPUT_OBJECT_TYPE_DEFINITION;
|
public $kind = NodeKind::INPUT_OBJECT_TYPE_DEFINITION;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/** @var DirectiveNode[]|null */
|
/**
|
||||||
|
* @var DirectiveNode[]
|
||||||
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
/** @var InputValueDefinitionNode[]|null */
|
/**
|
||||||
|
* @var InputValueDefinitionNode[]
|
||||||
|
*/
|
||||||
public $fields;
|
public $fields;
|
||||||
|
|
||||||
/** @var StringValueNode|null */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $description;
|
public $description;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
|
||||||
|
|
||||||
class InputObjectTypeExtensionNode extends Node implements TypeExtensionNode
|
|
||||||
{
|
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::INPUT_OBJECT_TYPE_EXTENSION;
|
|
||||||
|
|
||||||
/** @var NameNode */
|
|
||||||
public $name;
|
|
||||||
|
|
||||||
/** @var DirectiveNode[]|null */
|
|
||||||
public $directives;
|
|
||||||
|
|
||||||
/** @var InputValueDefinitionNode[]|null */
|
|
||||||
public $fields;
|
|
||||||
}
|
|
|
@ -1,26 +1,35 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class InputValueDefinitionNode extends Node
|
class InputValueDefinitionNode extends Node
|
||||||
{
|
{
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $kind = NodeKind::INPUT_VALUE_DEFINITION;
|
public $kind = NodeKind::INPUT_VALUE_DEFINITION;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/** @var TypeNode */
|
/**
|
||||||
|
* @var TypeNode
|
||||||
|
*/
|
||||||
public $type;
|
public $type;
|
||||||
|
|
||||||
/** @var ValueNode */
|
/**
|
||||||
|
* @var ValueNode
|
||||||
|
*/
|
||||||
public $defaultValue;
|
public $defaultValue;
|
||||||
|
|
||||||
/** @var DirectiveNode[] */
|
/**
|
||||||
|
* @var DirectiveNode[]
|
||||||
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
/** @var StringValueNode|null */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $description;
|
public $description;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class IntValueNode extends Node implements ValueNode
|
class IntValueNode extends Node implements ValueNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::INT;
|
public $kind = NodeKind::INT;
|
||||||
|
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $value;
|
public $value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class InterfaceTypeDefinitionNode extends Node implements TypeDefinitionNode
|
class InterfaceTypeDefinitionNode extends Node implements TypeDefinitionNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $kind = NodeKind::INTERFACE_TYPE_DEFINITION;
|
public $kind = NodeKind::INTERFACE_TYPE_DEFINITION;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
|
|
||||||
/** @var DirectiveNode[]|null */
|
/**
|
||||||
|
* @var DirectiveNode[]
|
||||||
|
*/
|
||||||
public $directives;
|
public $directives;
|
||||||
|
|
||||||
/** @var FieldDefinitionNode[]|null */
|
/**
|
||||||
public $fields;
|
* @var FieldDefinitionNode[]
|
||||||
|
*/
|
||||||
|
public $fields = [];
|
||||||
|
|
||||||
/** @var StringValueNode|null */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $description;
|
public $description;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
|
||||||
|
|
||||||
class InterfaceTypeExtensionNode extends Node implements TypeExtensionNode
|
|
||||||
{
|
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::INTERFACE_TYPE_EXTENSION;
|
|
||||||
|
|
||||||
/** @var NameNode */
|
|
||||||
public $name;
|
|
||||||
|
|
||||||
/** @var DirectiveNode[]|null */
|
|
||||||
public $directives;
|
|
||||||
|
|
||||||
/** @var FieldDefinitionNode[]|null */
|
|
||||||
public $fields;
|
|
||||||
}
|
|
|
@ -1,14 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class ListTypeNode extends Node implements TypeNode
|
class ListTypeNode extends Node implements TypeNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::LIST_TYPE;
|
public $kind = NodeKind::LIST_TYPE;
|
||||||
|
|
||||||
/** @var TypeNode */
|
/**
|
||||||
|
* @var Node
|
||||||
|
*/
|
||||||
public $type;
|
public $type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class ListValueNode extends Node implements ValueNode
|
class ListValueNode extends Node implements ValueNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::LST;
|
public $kind = NodeKind::LST;
|
||||||
|
|
||||||
/** @var ValueNode[]|NodeList */
|
/**
|
||||||
|
* @var ValueNode[]
|
||||||
|
*/
|
||||||
public $values;
|
public $values;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
|
@ -49,9 +46,8 @@ class Location
|
||||||
public $source;
|
public $source;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $start
|
* @param $start
|
||||||
* @param int $end
|
* @param $end
|
||||||
*
|
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function create($start, $end)
|
public static function create($start, $end)
|
||||||
|
@ -59,21 +55,18 @@ class Location
|
||||||
$tmp = new static();
|
$tmp = new static();
|
||||||
$tmp->start = $start;
|
$tmp->start = $start;
|
||||||
$tmp->end = $end;
|
$tmp->end = $end;
|
||||||
|
|
||||||
return $tmp;
|
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->startToken = $startToken;
|
||||||
$this->endToken = $endToken;
|
$this->endToken = $endToken;
|
||||||
$this->source = $source;
|
$this->source = $source;
|
||||||
|
|
||||||
if ($startToken === null || $endToken === null) {
|
if ($startToken && $endToken) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->start = $startToken->start;
|
$this->start = $startToken->start;
|
||||||
$this->end = $endToken->end;
|
$this->end = $endToken->end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class NameNode extends Node implements TypeNode
|
class NameNode extends Node implements TypeNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::NAME;
|
public $kind = NodeKind::NAME;
|
||||||
|
|
||||||
/** @var string */
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
public $value;
|
public $value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace GraphQL\Language\AST;
|
namespace GraphQL\Language\AST;
|
||||||
|
|
||||||
class NamedTypeNode extends Node implements TypeNode
|
class NamedTypeNode extends Node implements TypeNode
|
||||||
{
|
{
|
||||||
/** @var string */
|
|
||||||
public $kind = NodeKind::NAMED_TYPE;
|
public $kind = NodeKind::NAMED_TYPE;
|
||||||
|
|
||||||
/** @var NameNode */
|
/**
|
||||||
|
* @var NameNode
|
||||||
|
*/
|
||||||
public $name;
|
public $name;
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue