Compare commits

..

16 commits

Author SHA1 Message Date
Vladimir Razuvaev
b705ee797f v0.12.4 2018-07-08 04:43:07 +07:00
Vladimir Razuvaev
17392e05dd
Merge pull request #303 from theofidry/bugfix/stringeable-object
Allow stringeable objects to be serialized by StringType
2018-07-08 04:40:16 +07:00
Théo FIDRY
c258109844
Allow stringeable objects to be serialized by StringType
Closes #302
2018-07-07 22:55:53 +02:00
Vladimir Razuvaev
50dfd3fac2 v0.12.3 2018-07-07 22:46:58 +07:00
Iain Mckay
c5b78c66e9 Adds support for the multipart/form-data content type
(cherry picked from commit 750ce38)
2018-07-07 22:44:18 +07:00
Vasily Kartashov
47bcabfd7b Update complementary-tools.md
Adding reference to graphql-batch-processing library

(cherry picked from commit ec77f43)
2018-07-07 22:43:49 +07:00
Daniel Tschinder
4ef7920961 fix: Correct namespace and link for DirectiveLocation in docs
(cherry picked from commit 1b6fb4c)
2018-07-07 22:43:28 +07:00
Vladimir Razuvaev
167c3e7354 v0.12.2 2018-06-25 23:56:32 +07:00
Daniel Tschinder
a8cd87acff Use multi-line block for trailing quote
ref: fdc10bb918 (diff-ebaed8492e8d884ee4f2255e39909568)

(cherry picked from commit 6e64983)
2018-06-25 23:50:19 +07:00
Vladimir Razuvaev
e9cd1daedb v0.12.1 2018-06-23 11:38:13 +07:00
Daniel Tschinder
2540436a2c Fix wrong length being used in validator.
(cherry picked from commit 8ba1460)
2018-06-23 11:30:09 +07:00
Daniel Tschinder
d53f7f041e Improve example
(cherry picked from commit 3a4f520)
2018-06-23 11:30:00 +07:00
Daniel Tschinder
91daa23c5f Add one more breaking change in 0.12
(cherry picked from commit 300b580)
2018-06-23 11:29:52 +07:00
Ben Zhu
57c77623ee fix port bug
(cherry picked from commit 72e8607)
2018-06-23 11:29:27 +07:00
Ilya Shaydullin
78e2862e3b Fix is null condition
(cherry picked from commit 06490ca)
2018-06-23 11:29:03 +07:00
Ilya Shaydullin
63b04df6ef Removing data elements from response if the error throwing
(cherry picked from commit c7f114d)
2018-06-23 11:28:53 +07:00
379 changed files with 27721 additions and 41745 deletions

1
.gitattributes vendored
View file

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

8
.gitignore vendored
View file

@ -1,7 +1,5 @@
.phpcs-cache .idea/
composer.lock
composer.phar composer.phar
phpcs.xml composer.lock
phpstan.neon
vendor/ vendor/
/.idea bin/

View file

@ -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

View file

@ -1,66 +1,33 @@
dist: trusty
language: php language: php
dist: trusty
php: php:
- 7.1 - 5.6
- 7.2 - 7.0
- 7.3 - 7.1
- 7.4snapshot - 7.2
- nightly - nightly
env:
matrix:
- EXECUTOR= DEPENDENCIES=--prefer-lowest
- EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest
- EXECUTOR=
- EXECUTOR=coroutine
matrix:
allow_failures:
- php: nightly
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" ]]; then phpenv config-rm xdebug.ini || true; fi
- travis_retry composer self-update - phpenv config-rm xdebug.ini || true
- composer selfupdate
install: travis_retry composer update --prefer-dist install:
- composer install --dev --prefer-dist
- composer require react/promise:2.*
- composer require psr/http-message:1.*
script: ./vendor/bin/phpunit --group default,ReactPromise 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
jobs:
allow_failures:
- php: 7.4snapshot
- php: nightly
include:
- stage: Test
install:
- travis_retry composer update --prefer-dist {$DEPENDENCIES}
- stage: Test
env: COVERAGE
before_script:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
script:
- ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor.cov
- EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor-coroutine.cov
after_script:
- ./vendor/bin/phpcov merge /tmp/coverage --clover /tmp/clover.xml
- wget https://github.com/scrutinizer-ci/ocular/releases/download/1.5.2/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover /tmp/clover.xml
- stage: Code Quality
php: 7.1
env: CODING_STANDARD
install: travis_retry composer install --prefer-dist
script:
- ./vendor/bin/phpcs
- stage: Code Quality
php: 7.1
env: STATIC_ANALYSIS
install: travis_retry composer install --prefer-dist
script: composer static-analysis
after_success:
- if [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then composer require "satooshi/php-coveralls:^1.0" && travis_retry php bin/coveralls -v; fi

View file

@ -1,52 +1,4 @@
# 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 #### v0.12.4
- Allow stringeable objects to be serialized by StringType (#303) - Allow stringeable objects to be serialized by StringType (#303)

View file

@ -1,6 +1,7 @@
# 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.
@ -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 .
``` ```

View file

@ -1,6 +1,6 @@
# graphql-php # graphql-php
[![Build Status](https://travis-ci.org/webonyx/graphql-php.svg?branch=master)](https://travis-ci.org/webonyx/graphql-php) [![Build Status](https://travis-ci.org/webonyx/graphql-php.svg?branch=master)](https://travis-ci.org/webonyx/graphql-php)
[![Code Coverage](https://scrutinizer-ci.com/g/webonyx/graphql-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/webonyx/graphql-php) [![Coverage Status](https://coveralls.io/repos/github/webonyx/graphql-php/badge.svg)](https://coveralls.io/github/webonyx/graphql-php)
[![Latest Stable Version](https://poser.pugx.org/webonyx/graphql-php/version)](https://packagist.org/packages/webonyx/graphql-php) [![Latest Stable Version](https://poser.pugx.org/webonyx/graphql-php/version)](https://packagist.org/packages/webonyx/graphql-php)
[![License](https://poser.pugx.org/webonyx/graphql-php/license)](https://packagist.org/packages/webonyx/graphql-php) [![License](https://poser.pugx.org/webonyx/graphql-php/license)](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).

View file

@ -1,92 +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 ## Upgrade v0.11.x > v0.12.x
### Breaking: Minimum supported version is PHP5.6 ### Breaking: Minimum supported version is PHP5.6
@ -158,10 +69,6 @@ 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 ### Breaking: Most of previously deprecated classes and methods were removed
See deprecation notices for previous versions in details. See deprecation notices for previous versions in details.

View file

@ -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,12 +36,12 @@ 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();
$queryBuilder = new QueryGenerator($this->schema, 0.05); $queryBuilder = new QueryGenerator($this->schema, 0.05);
$this->smallQuery = $queryBuilder->buildQuery(); $this->smallQuery = $queryBuilder->buildQuery();
} }
@ -43,7 +49,8 @@ class HugeSchemaBench
{ {
$this->schemaBuilder $this->schemaBuilder
->buildSchema() ->buildSchema()
->getTypeMap(); ->getTypeMap()
;
} }
public function benchSchemaLazy() public function benchSchemaLazy()
@ -53,21 +60,21 @@ 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);
}) })
); );

View file

@ -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
); );

View file

@ -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,14 +30,12 @@ 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' => [
'name' => new NameNode(['value' => 'TestQuery']), new OperationDefinitionNode([
'operation' => 'query', 'name' => new NameNode(['value' => 'TestQuery']),
'selectionSet' => $this->buildSelectionSet($qtype->getFields()), 'operation' => 'query',
]), '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;

View file

@ -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';
} }

View file

@ -9,25 +9,21 @@
"API" "API"
], ],
"require": { "require": {
"php": "^7.1||^8.0", "php": ">=5.6",
"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": {
"bin-dir": "bin",
"platform": {
"php": "5.6.0"
},
"preferred-install": "dist", "preferred-install": "dist",
"sort-packages": true "sort-packages": true
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"GraphQL\\": "src/" "GraphQL\\": "src/"
@ -43,14 +39,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"
} }
} }

View file

@ -1,26 +1,21 @@
# 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) + [Nuwave Lighthouse](https://github.com/nuwave/lighthouse)
* [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 * 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)
* [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) Bundle for Symfony
* [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress
# GraphQL PHP Tools # GraphQL PHP Tools
* [GraphQLite](https://graphqlite.thecodingmachine.io) Define your complete schema with annotations * 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))
* [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) Define types with Doctrine ORM annotations * [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
* [DataLoaderPHP](https://github.com/overblog/dataloader-php) as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem) * [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental)
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) A PSR-15 middleware to support file uploads in GraphQL. * [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server
* [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 Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) - Simple library that provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches.
* [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 # General GraphQL Tools
* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration). * [GraphiQL](https://github.com/graphql/graphiql) - An in-browser IDE for exploring GraphQL
* [GraphiQL](https://github.com/graphql/graphiql) An in-browser IDE for exploring GraphQL
* [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij) * [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) -
GraphiQL as Google Chrome extension GraphiQL as Google Chrome extension

View file

@ -103,25 +103,23 @@ 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
? $property($objectValue, $args, $context, $info)
: $property;
} }
return $property instanceof \Closure ? $property($source, $args, $context) : $property;
}
``` ```
As you see it returns value by key (for arrays) or property (for objects). As you see it returns value by key (for arrays) or property (for objects).
@ -163,6 +161,7 @@ $userType = new ObjectType([
Keep in mind that **field resolver** has precedence over **default field resolver per type** which in turn 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

View file

@ -84,7 +84,7 @@ To change default **"Internal server error"** message to something else, use:
GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error"); GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error");
``` ```
# Debugging tools #Debugging tools
During development or debugging use `$result->toArray(true)` to add **debugMessage** key to During development or debugging use `$result->toArray(true)` to add **debugMessage** key to
each formatted error entry. If you also want to add exception trace - pass flags instead: each formatted error entry. If you also want to add exception trace - pass flags instead:
@ -116,7 +116,7 @@ This will make each error entry to look like this:
]; ];
``` ```
If you prefer 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.

View file

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

View file

@ -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'];
} }
], ],
], ],

View file

@ -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
@ -44,8 +44,8 @@ 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).

File diff suppressed because it is too large Load diff

View file

@ -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;
} }
] ]

View file

@ -114,7 +114,7 @@ Option | Type | Notes
name | `string` | **Required.** Name of the input field. When not set - inferred from **fields** array key 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
] ]

View file

@ -80,7 +80,7 @@ Option | Type | Notes
name | `string` | **Required.** Name of the field. When not set - inferred from **fields** array key (read about [shorthand field definition](#shorthand-field-definitions) below) 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):

View file

@ -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 */},
]); ]);
``` ```

View file

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

View file

@ -38,30 +38,6 @@ By default, such schema is created without any resolvers.
We have to rely on [default field resolver](../data-fetching.md#default-field-resolver) and **root value** in 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
Since 0.10.0
In order to enable **Interfaces**, **Unions** and custom field resolvers you can pass the second argument:
**type config decorator** to schema builder.
It accepts default type config produced by the builder and is expected to add missing options like
[**resolveType**](interfaces.md#configuration-options) for interface types or
[**resolveField**](object-types.md#configuration-options) for object types.
```php
<?php
use GraphQL\Utils\BuildSchema;
$typeConfigDecorator = function($typeConfig, $typeDefinitionNode) {
$name = $typeConfig['name'];
// ... add missing options to $typeConfig based on type $name
return $typeConfig;
};
$contents = file_get_contents('schema.graphql');
$schema = BuildSchema::build($contents, $typeConfigDecorator);
```
# Performance considerations # Performance considerations
Since 0.10.0 Since 0.10.0
@ -81,7 +57,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(AST::toArray($document), 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
} }

View file

@ -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'];
}, },
], ],

View file

@ -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};
} }
} }
]; ];

View file

@ -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);

View file

@ -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};
} }
} }
]; ];

View file

@ -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};
} }
} }
]; ];

View file

@ -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' => [

View file

@ -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: ',
]; ];

View file

@ -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'];
}, },
], ],

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -1,64 +1,60 @@
<?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;
$this->promise = new SyncPromise(); $this->promise = new SyncPromise();
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);
} }
} }

View file

@ -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();
} }

View file

@ -1,7 +1,4 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Error; namespace GraphQL\Error;
/** /**
@ -9,8 +6,7 @@ namespace GraphQL\Error;
*/ */
class Debug 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;
} }

View file

@ -1,22 +1,10 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Error; namespace GraphQL\Error;
use Exception;
use GraphQL\Language\AST\Node; 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,9 +20,9 @@ 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,21 +32,23 @@ 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;
@ -72,75 +62,34 @@ class Error extends Exception implements JsonSerializable, ClientAware
*/ */
private $source; private $source;
/** @var int[]|null */ /**
* @var array
*/
private $positions; private $positions;
/** @var bool */ /**
* @var bool
*/
private $isClientSafe; private $isClientSafe;
/** @var string */ /**
* @var string
*/
protected $category; protected $category;
/** @var mixed[]|null */
protected $extensions;
/** /**
* @param string $message * @var array
* @param Node|Node[]|Traversable|null $nodes
* @param mixed[]|null $positions
* @param mixed[]|null $path
* @param Throwable $previous
* @param mixed[] $extensions
*/ */
public function __construct( protected $extensions;
$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,24 +97,24 @@ 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;
$path = $path ?: $error->path;
} }
$nodes = $nodes ?: $error->nodes;
$path = $path ?: $error->path;
} }
$source = $positions = $originalError = null; $source = $positions = $originalError = null;
$extensions = []; $extensions = [];
if ($error instanceof self) { if ($error instanceof self) {
$message = $error->getMessage(); $message = $error->getMessage();
$originalError = $error; $originalError = $error;
$nodes = $error->nodes ?: $nodes; $nodes = $error->nodes ?: $nodes;
$source = $error->source; $source = $error->source;
$positions = $error->positions; $positions = $error->positions;
$extensions = $error->extensions; $extensions = $error->extensions;
} elseif ($error instanceof Exception || $error instanceof Throwable) { } else if ($error instanceof \Exception || $error instanceof \Throwable) {
$message = $error->getMessage(); $message = $error->getMessage();
$originalError = $error; $originalError = $error;
} else { } else {
$message = (string) $error; $message = (string) $error;
@ -182,14 +131,66 @@ class Error extends Exception implements JsonSerializable, ClientAware
); );
} }
/** /**
* @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|Node|null $nodes
* @param Source $source
* @param array|null $positions
* @param array|null $path
* @param \Throwable $previous
* @param array $extensions
*/
public function __construct(
$message,
$nodes = null,
Source $source = null,
$positions = null,
$path = null,
$previous = null,
array $extensions = []
)
{
parent::__construct($message, 0, $previous);
// Compute list of blame nodes.
if ($nodes instanceof \Traversable) {
$nodes = iterator_to_array($nodes);
} else if ($nodes && !is_array($nodes)) {
$nodes = [$nodes];
}
$this->nodes = $nodes;
$this->source = $source;
$this->positions = $positions;
$this->path = $path;
$this->extensions = $extensions ?: (
$previous && $previous instanceof self
? $previous->extensions
: []
);
if ($previous instanceof ClientAware) {
$this->isClientSafe = $previous->isClientSafe();
$this->category = $previous->getCategory() ?: static::CATEGORY_INTERNAL;
} else if ($previous) {
$this->isClientSafe = false;
$this->category = static::CATEGORY_INTERNAL;
} else {
$this->isClientSafe = true;
$this->category = static::CATEGORY_GRAPHQL;
}
}
/** /**
* @inheritdoc * @inheritdoc
*/ */
@ -211,38 +212,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 +249,26 @@ 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; $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);
}, } else if ($nodes) {
$positions $this->locations = array_filter(array_map(function ($node) {
); if ($node->loc) {
} elseif ($nodes) { return $node->loc->source->getLocation($node->loc->start);
$locations = array_filter( }
array_map( }, $nodes));
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 = [];
} }
@ -296,7 +278,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
} }
/** /**
* @return Node[]|null * @return array|Node[]|null
*/ */
public function getNodes() public function getNodes()
{ {
@ -307,9 +289,8 @@ class Error extends Exception implements JsonSerializable, ClientAware
* 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()
{ {
@ -317,7 +298,7 @@ class Error extends Exception implements JsonSerializable, ClientAware
} }
/** /**
* @return mixed[] * @return array
*/ */
public function getExtensions() public function getExtensions()
{ {
@ -328,44 +309,40 @@ class Error extends Exception implements JsonSerializable, ClientAware
* 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( if ($this->getExtensions()) {
$this->getLocations(), $arr = array_merge($this->getExtensions(), $arr);
static function (SourceLocation $loc) { }
return $loc->toSerializableArray();
}
);
if (! empty($locations)) { $locations = Utils::map($this->getLocations(), function(SourceLocation $loc) {
return $loc->toSerializableArray();
});
if (!empty($locations)) {
$arr['locations'] = $locations; $arr['locations'] = $locations;
} }
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();
} }

View file

@ -1,39 +1,12 @@
<?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\AST\Node;
use GraphQL\Language\Source; 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,16 +15,14 @@ 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)
{ {
@ -62,6 +33,7 @@ class FormattedError
* Prints a GraphQLError to a string, representing useful location information * Prints a GraphQLError to a string, representing useful location information
* about the error's position in the source. * about the error's position in the source.
* *
* @param Error $error
* @return string * @return string
*/ */
public static function printError(Error $error) public static function printError(Error $error)
@ -69,65 +41,63 @@ class FormattedError
$printedLocations = []; $printedLocations = [];
if ($error->nodes) { if ($error->nodes) {
/** @var Node $node */ /** @var Node $node */
foreach ($error->nodes as $node) { foreach($error->nodes as $node) {
if (! $node->loc) { if ($node->loc) {
continue; $printedLocations[] = self::highlightSourceAtLocation(
$node->loc->source,
$node->loc->source->getLocation($node->loc->start)
);
} }
if ($node->loc->source === null) {
continue;
}
$printedLocations[] = self::highlightSourceAtLocation(
$node->loc->source,
$node->loc->source->getLocation($node->loc->start)
);
} }
} elseif ($error->getSource() && $error->getLocations()) { } else if ($error->getSource() && $error->getLocations()) {
$source = $error->getSource(); $source = $error->getSource();
foreach ($error->getLocations() as $location) { foreach($error->getLocations() as $location) {
$printedLocations[] = self::highlightSourceAtLocation($source, $location); $printedLocations[] = self::highlightSourceAtLocation($source, $location);
} }
} }
return ! $printedLocations return !$printedLocations
? $error->getMessage() ? $error->getMessage()
: implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n"; : join("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
} }
/** /**
* Render a helpful description of the location of the error in the GraphQL * Render a helpful description of the location of the error in the GraphQL
* Source document. * Source document.
* *
* @param Source $source
* @param SourceLocation $location
* @return string * @return string
*/ */
private static function highlightSourceAtLocation(Source $source, SourceLocation $location) private static function highlightSourceAtLocation(Source $source, SourceLocation $location)
{ {
$line = $location->line; $line = $location->line;
$lineOffset = $source->locationOffset->line - 1; $lineOffset = $source->locationOffset->line - 1;
$columnOffset = self::getColumnOffset($source, $location); $columnOffset = self::getColumnOffset($source, $location);
$contextLine = $line + $lineOffset; $contextLine = $line + $lineOffset;
$contextColumn = $location->column + $columnOffset; $contextColumn = $location->column + $columnOffset;
$prevLineNum = (string) ($contextLine - 1); $prevLineNum = (string) ($contextLine - 1);
$lineNum = (string) $contextLine; $lineNum = (string) $contextLine;
$nextLineNum = (string) ($contextLine + 1); $nextLineNum = (string) ($contextLine + 1);
$padLen = strlen($nextLineNum); $padLen = strlen($nextLineNum);
$lines = preg_split('/\r\n|[\n\r]/', $source->body); $lines = preg_split('/\r\n|[\n\r]/', $source->body);
$lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0]; $lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0];
$outputLines = [ $outputLines = [
sprintf('%s (%s:%s)', $source->name, $contextLine, $contextColumn), "{$source->name} ($contextLine:$contextColumn)",
$line >= 2 ? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null, $line >= 2 ? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null,
self::lpad($padLen, $lineNum) . ': ' . $lines[$line - 1], self::lpad($padLen, $lineNum) . ': ' . $lines[$line - 1],
self::whitespace(2 + $padLen + $contextColumn - 1) . '^', self::whitespace(2 + $padLen + $contextColumn - 1) . '^',
$line < count($lines) ? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null, $line < count($lines)? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null
]; ];
return implode("\n", array_filter($outputLines)); return join("\n", array_filter($outputLines));
} }
/** /**
* @param Source $source
* @param SourceLocation $location
* @return int * @return int
*/ */
private static function getColumnOffset(Source $source, SourceLocation $location) private static function getColumnOffset(Source $source, SourceLocation $location)
@ -137,21 +107,17 @@ class FormattedError
/** /**
* @param int $len * @param int $len
*
* @return string * @return string
*/ */
private static function whitespace($len) private static function whitespace($len) {
{
return str_repeat(' ', $len); return str_repeat(' ', $len);
} }
/** /**
* @param int $len * @param int $len
*
* @return string * @return string
*/ */
private static function lpad($len, $str) private static function lpad($len, $str) {
{
return self::whitespace($len - mb_strlen($str)) . $str; return self::whitespace($len - mb_strlen($str)) . $str;
} }
@ -164,21 +130,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
* @param bool|int $debug
* @param string $internalErrorMessage
*
* @return mixed[]
*
* @throws Throwable
*
* @api * @api
* @param \Throwable $e
* @param bool|int $debug
* @param string $internalErrorMessage
* @return array
* @throws \Throwable
*/ */
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)
); );
@ -186,36 +149,31 @@ 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( if ($e->getExtensions()) {
$e->getLocations(), $formattedError = array_merge($e->getExtensions(), $formattedError);
static function (SourceLocation $loc) { }
return $loc->toSerializableArray();
} $locations = Utils::map($e->getLocations(), function(SourceLocation $loc) {
); 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,67 +187,56 @@ 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)
{ {
if (! $debug) { if (!$debug) {
return $formattedError; return $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)
); );
$debug = (int) $debug; $debug = (int) $debug;
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(),
]; ];
} }
$isTrivial = $e instanceof Error && ! $e->getPrevious(); $isTrivial = $e instanceof Error && !$e->getPrevious();
if (! $isTrivial) { if (!$isTrivial) {
$debugging = $e->getPrevious() ?: $e; $debugging = $e->getPrevious() ?: $e;
$formattedError['trace'] = static::toSafeTrace($debugging); $formattedError['trace'] = static::toSafeTrace($debugging);
} }
} }
return $formattedError; return $formattedError;
} }
@ -297,71 +244,67 @@ 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: if (
($trace[0]['class'] . '::' . $trace[0]['function'] === 'GraphQL\Utils\Utils::invariant')) { isset($trace[0]['function']) && isset($trace[0]['class']) &&
array_shift($trace); ('GraphQL\Utils\Utils::invariant' === $trace[0]['class'].'::'.$trace[0]['function'])) {
} elseif (! isset($trace[0]['file'])) {
// 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'])) {
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]); array_shift($trace);
}
if (isset($err['function'])) { return array_map(function($err) {
$func = $err['function']; $safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
$args = ! empty($err['args']) ? array_map([self::class, 'printVar'], $err['args']) : [];
$funcStr = $func . '(' . implode(', ', $args) . ')';
if (isset($err['class'])) { if (isset($err['function'])) {
$safeErr['call'] = $err['class'] . '::' . $funcStr; $func = $err['function'];
} else { $args = !empty($err['args']) ? array_map([__CLASS__, 'printVar'], $err['args']) : [];
$safeErr['function'] = $funcStr; $funcStr = $func . '(' . implode(", ", $args) . ')';
}
if (isset($err['class'])) {
$safeErr['call'] = $err['class'] . '::' . $funcStr;
} else {
$safeErr['function'] = $funcStr;
} }
}
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 +314,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 +335,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)
]; ];
} }
} }

View file

@ -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
{ {
} }

View file

@ -1,22 +1,19 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Error; namespace GraphQL\Error;
use GraphQL\Language\Source; use GraphQL\Language\Source;
use function sprintf;
class SyntaxError extends Error class SyntaxError extends Error
{ {
/** /**
* @param int $position * @param Source $source
* @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( parent::__construct(
sprintf('Syntax Error: %s', $description), "Syntax Error: $description",
null, null,
$source, $source,
[$position] [$position]

View file

@ -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

View file

@ -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,27 @@ use const E_USER_WARNING;
*/ */
final class Warning final class Warning
{ {
public const WARNING_ASSIGN = 2; const WARNING_ASSIGN = 2;
public const WARNING_CONFIG = 4; const WARNING_CONFIG = 4;
public const WARNING_FULL_SCHEMA_SCAN = 8; const WARNING_FULL_SCHEMA_SCAN = 8;
public const WARNING_CONFIG_DEPRECATION = 16; const WARNING_CONFIG_DEPRECATION = 16;
public const WARNING_NOT_A_TYPE = 32; const WARNING_NOT_A_TYPE = 32;
public const ALL = 63; 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 +42,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,40 +65,38 @@ 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);
} elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) { } else if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
self::$warned[$warningId] = true; self::$warned[$warningId] = true;
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING); trigger_error($errorMessage, $messageLevel ?: E_USER_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);
} elseif ((self::$enableWarnings & $warningId) > 0) { } else if ((self::$enableWarnings & $warningId) > 0) {
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING); trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
} }
} }

View file

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL\Exception;
use InvalidArgumentException;
use function gettype;
use function sprintf;
final class InvalidArgument extends InvalidArgumentException
{
/**
* @param mixed $argument
*/
public static function fromExpectedTypeAndArgument(string $expectedType, $argument) : self
{
return new self(sprintf('Expected type "%s", got "%s"', $expectedType, gettype($argument)));
}
}

View file

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

View file

@ -1,13 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Executor; namespace GraphQL\Executor;
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 +11,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 +28,7 @@ class ExecutionResult implements JsonSerializable
* contain original exception. * contain original exception.
* *
* @api * @api
* @var Error[] * @var \GraphQL\Error\Error[]
*/ */
public $errors; public $errors;
@ -43,25 +37,29 @@ 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;
$this->extensions = $extensions; $this->extensions = $extensions;
} }
@ -78,14 +76,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 +96,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 +116,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

View file

@ -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;
}

View file

@ -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, return $item instanceof Promise ? $item->adoptedPromise : $item;
static function ($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);
} }
} }

View file

@ -1,164 +1,117 @@
<?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
{ {
const PENDING = 'pending'; const PENDING = 'pending';
const FULFILLED = 'fulfilled'; const FULFILLED = 'fulfilled';
const REJECTED = 'rejected'; const REJECTED = 'rejected';
/** @var SplQueue */
public static $queue;
/** @var string */
public $state = self::PENDING;
/** @var ExecutionResult|Throwable */
public $result;
/** /**
* Promises created in `then` method of this promise and awaiting for resolution of this promise * @var \SplQueue
*
* @var mixed[][]
*/ */
private $waiting = []; public static $queue;
public static function runQueue() : void public static function getQueue()
{
return self::$queue ?: self::$queue = new \SplQueue();
}
public static function runQueue()
{ {
$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) {
case self::PENDING: case self::PENDING:
$this->state = self::REJECTED; $this->state = self::REJECTED;
$this->result = $reason; $this->result = $reason;
$this->enqueueWaitingPromises(); $this->enqueueWaitingPromises();
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");
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);
}
} }
}); 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");
} }
$this->waiting = []; return $this;
} }
public static function getQueue() : SplQueue public function then(callable $onFulfilled = null, callable $onRejected = null)
{ {
return self::$queue ?: self::$queue = new SplQueue(); if ($this->state === self::REJECTED && !$onRejected) {
}
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) { if ($this->state === self::FULFILLED && !$onFulfilled) {
return $this; return $this;
} }
$tmp = new self(); $tmp = new self();
$this->waiting[] = [$tmp, $onFulfilled, $onRejected]; $this->waiting[] = [$tmp, $onFulfilled, $onRejected];
if ($this->state !== self::PENDING) { if ($this->state !== self::PENDING) {
@ -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 = [];
}
} }

View file

@ -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
{ {
@ -33,22 +30,20 @@ class SyncPromiseAdapter implements PromiseAdapter
*/ */
public function convertThenable($thenable) public function convertThenable($thenable)
{ {
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);
} }
@ -105,22 +92,20 @@ class SyncPromiseAdapter implements PromiseAdapter
{ {
$all = new SyncPromise(); $all = new SyncPromise();
$total = count($promisesOrValues); $total = count($promisesOrValues);
$count = 0; $count = 0;
$result = []; $result = [];
foreach ($promisesOrValues as $index => $promiseOrValue) { foreach ($promisesOrValues as $index => $promiseOrValue) {
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,23 +117,24 @@ 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)
{ {
$this->beforeWait($promise); $this->beforeWait($promise);
$dfdQueue = Deferred::getQueue(); $dfdQueue = Deferred::getQueue();
$promiseQueue = SyncPromise::getQueue(); $promiseQueue = SyncPromise::getQueue();
while ($promise->adoptedPromise->state === SyncPromise::PENDING && while (
! ($dfdQueue->isEmpty() && $promiseQueue->isEmpty()) $promise->adoptedPromise->state === SyncPromise::PENDING &&
!($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
) { ) {
Deferred::runQueue(); Deferred::runQueue();
SyncPromise::runQueue(); SyncPromise::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)
{ {

View file

@ -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);
} }

View file

@ -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

View file

@ -1,7 +1,4 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Executor; namespace GraphQL\Executor;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -12,27 +9,20 @@ 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\InputType; use GraphQL\Type\Definition\InputType;
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\Utils\Value;
use stdClass;
use Throwable;
use function array_key_exists;
use function array_map;
use function sprintf;
class Values class Values
{ {
@ -41,36 +31,46 @@ 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 Schema $schema
* @param VariableDefinitionNode[] $varDefNodes * @param VariableDefinitionNode[] $varDefNodes
* @param mixed[] $inputs * @param array $inputs
* * @return array
* @return mixed[]
*/ */
public static function getVariableValues(Schema $schema, $varDefNodes, array $inputs) public static function getVariableValues(Schema $schema, $varDefNodes, array $inputs)
{ {
$errors = []; $errors = [];
$coercedValues = []; $coercedValues = [];
foreach ($varDefNodes as $varDefNode) { foreach ($varDefNodes as $varDefNode) {
$varName = $varDefNode->variable->name->value; $varName = $varDefNode->variable->name->value;
/** @var InputType|Type $varType */ /** @var InputType|Type $varType */
$varType = TypeInfo::typeFromAST($schema, $varDefNode->type); $varType = TypeInfo::typeFromAST($schema, $varDefNode->type);
if (Type::isInputType($varType)) { if (!Type::isInputType($varType)) {
if (array_key_exists($varName, $inputs)) { $errors[] = new Error(
$value = $inputs[$varName]; "Variable \"\$$varName\" expected value of type " .
'"' . Printer::doPrint($varDefNode->type) . '" which cannot be used as an input type.',
[$varDefNode->type]
);
} else {
if (!array_key_exists($varName, $inputs)) {
if ($varType instanceof NonNull) {
$errors[] = new Error(
"Variable \"\$$varName\" of required type " .
"\"{$varType}\" was not provided.",
[$varDefNode]
);
} else if ($varDefNode->defaultValue) {
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
}
} else {
$value = $inputs[$varName];
$coerced = Value::coerceValue($value, $varType, $varDefNode); $coerced = Value::coerceValue($value, $varType, $varDefNode);
/** @var Error[] $coercionErrors */ /** @var Error[] $coercionErrors */
$coercionErrors = $coerced['errors']; $coercionErrors = $coerced['errors'];
if (empty($coercionErrors)) { if ($coercionErrors) {
$coercedValues[$varName] = $coerced['value']; $messagePrelude = "Variable \"\$$varName\" got invalid value " . Utils::printSafeJson($value) . '; ';
} else {
$messagePrelude = sprintf(
'Variable "$%s" got invalid value %s; ',
$varName,
Utils::printSafeJson($value)
);
foreach ($coercionErrors as $error) { foreach($coercionErrors as $error) {
$errors[] = new Error( $errors[] = new Error(
$messagePrelude . $error->getMessage(), $messagePrelude . $error->getMessage(),
$error->getNodes(), $error->getNodes(),
@ -81,38 +81,90 @@ class Values
$error->getExtensions() $error->getExtensions()
); );
} }
} } else {
} else { $coercedValues[$varName] = $coerced['value'];
if ($varType instanceof NonNull) {
$errors[] = new Error(
sprintf(
'Variable "$%s" of required type "%s" was not provided.',
$varName,
$varType
),
[$varDefNode]
);
} elseif ($varDefNode->defaultValue !== null) {
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
} }
} }
} else {
$errors[] = new Error(
sprintf(
'Variable "$%s" expected value of type "%s" which cannot be used as an input type.',
$varName,
Printer::doPrint($varDefNode->type)
),
[$varDefNode->type]
);
} }
} }
return ['errors' => $errors, 'coerced' => $errors ? null : $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 = [];
/** @var ArgumentNode[] $argNodeMap */
$argNodeMap = $argNodes ? Utils::keyMap($argNodes, function (ArgumentNode $arg) {
return $arg->name->value;
}) : [];
foreach ($argDefs as $argDef) {
$name = $argDef->name;
$argType = $argDef->getType();
$argumentNode = isset($argNodeMap[$name]) ? $argNodeMap[$name] : null;
if (!$argumentNode) {
if ($argDef->defaultValueExists()) {
$coercedValues[$name] = $argDef->defaultValue;
} else if ($argType instanceof NonNull) {
throw new Error(
'Argument "' . $name . '" of required type ' .
'"' . Utils::printSafe($argType) . '" was not provided.',
[$node]
);
}
} else if ($argumentNode->value instanceof VariableNode) {
$variableName = $argumentNode->value->name->value;
if ($variableValues && array_key_exists($variableName, $variableValues)) {
// Note: this does not check that this variable value is correct.
// This assumes that this query has been validated and the variable
// usage here is of the correct type.
$coercedValues[$name] = $variableValues[$variableName];
} else if ($argDef->defaultValueExists()) {
$coercedValues[$name] = $argDef->defaultValue;
} else if ($argType instanceof NonNull) {
throw new Error(
'Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' .
'provided the variable "$' . $variableName . '" which was not provided ' .
'a runtime value.',
[ $argumentNode->value ]
);
}
} else {
$valueNode = $argumentNode->value;
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
if (Utils::isInvalid($coercedValue)) {
// Note: ValuesOfCorrectType validation should catch this before
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new Error(
'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.',
[ $argumentNode->value ]
);
}
$coercedValues[$name] = $coercedValue;
}
}
return $coercedValues;
} }
/** /**
@ -122,158 +174,50 @@ 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 FragmentSpreadNode|FieldNode|InlineFragmentNode|EnumValueDefinitionNode|FieldDefinitionNode $node * @param Directive $directiveDef
* @param mixed[]|null $variableValues * @param FragmentSpreadNode | FieldNode | InlineFragmentNode | EnumValueDefinitionNode | FieldDefinitionNode $node
* @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, return $directive->name->value === $directiveDef->name;
static function (DirectiveNode $directive) use ($directiveDef) { });
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) * @deprecated as of 0.12 (Use coerceValue() directly for richer information)
* * @param $value
* @param mixed[] $value * @param InputType $type
* * @return array
* @return string[]
*/ */
public static function isValidPHPValue($value, InputType $type) public static function isValidPHPValue($value, InputType $type)
{ {
$errors = Value::coerceValue($value, $type)['errors']; $errors = Value::coerceValue($value, $type)['errors'];
return $errors return $errors
? array_map( ? array_map(function(/*\Throwable */$error) { return $error->getMessage(); }, $errors)
static function (Throwable $error) {
return $error->getMessage();
},
$errors
)
: []; : [];
} }
} }

View file

@ -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);
}
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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)
* *
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param mixed[]|null $variableValues
* @param ValidationRule[] $validationRules
*
* @api * @api
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param array|null $variableValues
* @param string|null $operationName
* @param callable $fieldResolver
* @param array $validationRules
*
* @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.
* *
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param mixed[]|null $variableValues
* @param ValidationRule[]|null $validationRules
*
* @api * @api
* @param PromiseAdapter $promiseAdapter
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param array|null $variableValues
* @param string|null $operationName
* @param callable $fieldResolver
* @param array $validationRules
*
* @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,38 +125,36 @@ 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);
if (! empty($validationErrors)) { if (!empty($validationErrors)) {
return $promiseAdapter->createFulfilled( return $promiseAdapter->createFulfilled(
new ExecutionResult(null, $validationErrors) new ExecutionResult(null, $validationErrors)
); );
} else {
return Executor::promiseToExecute(
$promiseAdapter,
$schema,
$documentNode,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver
);
} }
return Executor::promiseToExecute(
$promiseAdapter,
$schema,
$documentNode,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver
);
} catch (Error $e) { } catch (Error $e) {
return $promiseAdapter->createFulfilled( return $promiseAdapter->createFulfilled(
new ExecutionResult(null, [$e]) new ExecutionResult(null, [$e])
@ -174,29 +165,29 @@ 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( trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement', __METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement',
E_USER_DEPRECATED E_USER_DEPRECATED
); );
$result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(); $promiseAdapter = Executor::getPromiseAdapter(),
$result = self::promiseToExecute(
$promiseAdapter,
$schema, $schema,
$source, $source,
$rootValue, $rootValue,
@ -208,40 +199,40 @@ 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( trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement', __METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement',
E_USER_DEPRECATED E_USER_DEPRECATED
); );
$result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(); $promiseAdapter = Executor::getPromiseAdapter(),
$result = self::promiseToExecute(
$promiseAdapter,
$schema, $schema,
$source, $source,
$rootValue, $rootValue,
@ -249,22 +240,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 +260,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();
} }

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -1,14 +1,11 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
interface DefinitionNode
{
/** /**
* export type DefinitionNode = * export type DefinitionNode =
* | ExecutableDefinitionNode * | ExecutableDefinitionNode
* | TypeSystemDefinitionNode; // experimental non-spec addition. * | TypeSystemDefinitionNode; // experimental non-spec addition.
*/ */
interface DefinitionNode
{
} }

View file

@ -1,23 +1,30 @@
<?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 */ /**
* @var StringValueNode|null
*/
public $description; public $description;
} }

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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[]|null|NodeList
*/
public $values; public $values;
/** @var StringValueNode|null */ /**
* @var StringValueNode|null
*/
public $description; public $description;
} }

View file

@ -1,20 +1,25 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
class EnumTypeExtensionNode extends Node implements TypeExtensionNode class EnumTypeExtensionNode extends Node implements TypeExtensionNode
{ {
/** @var string */ /**
* @var string
*/
public $kind = NodeKind::ENUM_TYPE_EXTENSION; public $kind = NodeKind::ENUM_TYPE_EXTENSION;
/** @var NameNode */ /**
* @var NameNode
*/
public $name; public $name;
/** @var DirectiveNode[]|null */ /**
* @var DirectiveNode[]|null
*/
public $directives; public $directives;
/** @var EnumValueDefinitionNode[]|null */ /**
* @var EnumValueDefinitionNode[]|null
*/
public $values; public $values;
} }

View file

@ -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 StringValueNode|null
*/
public $description; public $description;
} }

View file

@ -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;
} }

View file

@ -1,14 +1,11 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
interface ExecutableDefinitionNode extends DefinitionNode
{
/** /**
* export type ExecutableDefinitionNode = * export type ExecutableDefinitionNode =
* | OperationDefinitionNode * | OperationDefinitionNode
* | FragmentDefinitionNode; * | FragmentDefinitionNode;
*/ */
interface ExecutableDefinitionNode extends DefinitionNode
{
} }

View file

@ -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[]|NodeList
*/
public $arguments; public $arguments;
/** @var TypeNode */ /**
* @var TypeNode
*/
public $type; public $type;
/** @var DirectiveNode[]|NodeList */ /**
* @var DirectiveNode[]|NodeList
*/
public $directives; public $directives;
/** @var StringValueNode|null */ /**
* @var StringValueNode|null
*/
public $description; public $description;
} }

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -1,15 +1,13 @@
<?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 ExecutableDefinitionNode, HasSelectionSet
{ {
/** @var string */
public $kind = NodeKind::FRAGMENT_DEFINITION; public $kind = NodeKind::FRAGMENT_DEFINITION;
/** @var NameNode */ /**
* @var NameNode
*/
public $name; public $name;
/** /**
@ -20,12 +18,18 @@ class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, H
*/ */
public $variableDefinitions; public $variableDefinitions;
/** @var NamedTypeNode */ /**
* @var NamedTypeNode
*/
public $typeCondition; public $typeCondition;
/** @var DirectiveNode[]|NodeList */ /**
* @var DirectiveNode[]|NodeList
*/
public $directives; public $directives;
/** @var SelectionSetNode */ /**
* @var SelectionSetNode
*/
public $selectionSet; public $selectionSet;
} }

View file

@ -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;
} }

View file

@ -1,7 +1,4 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
interface HasSelectionSet interface HasSelectionSet

View file

@ -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;
} }

View file

@ -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[]|null
*/
public $directives; public $directives;
/** @var InputValueDefinitionNode[]|null */ /**
* @var InputValueDefinitionNode[]|null
*/
public $fields; public $fields;
/** @var StringValueNode|null */ /**
* @var StringValueNode|null
*/
public $description; public $description;
} }

View file

@ -1,20 +1,25 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
class InputObjectTypeExtensionNode extends Node implements TypeExtensionNode class InputObjectTypeExtensionNode extends Node implements TypeExtensionNode
{ {
/** @var string */ /**
* @var string
*/
public $kind = NodeKind::INPUT_OBJECT_TYPE_EXTENSION; public $kind = NodeKind::INPUT_OBJECT_TYPE_EXTENSION;
/** @var NameNode */ /**
* @var NameNode
*/
public $name; public $name;
/** @var DirectiveNode[]|null */ /**
* @var DirectiveNode[]|null
*/
public $directives; public $directives;
/** @var InputValueDefinitionNode[]|null */ /**
* @var InputValueDefinitionNode[]|null
*/
public $fields; public $fields;
} }

View file

@ -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 StringValueNode|null
*/
public $description; public $description;
} }

View file

@ -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;
} }

View file

@ -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[]|null
*/
public $directives; public $directives;
/** @var FieldDefinitionNode[]|null */ /**
* @var FieldDefinitionNode[]|null
*/
public $fields; public $fields;
/** @var StringValueNode|null */ /**
* @var StringValueNode|null
*/
public $description; public $description;
} }

View file

@ -1,20 +1,25 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
class InterfaceTypeExtensionNode extends Node implements TypeExtensionNode class InterfaceTypeExtensionNode extends Node implements TypeExtensionNode
{ {
/** @var string */ /**
* @var string
*/
public $kind = NodeKind::INTERFACE_TYPE_EXTENSION; public $kind = NodeKind::INTERFACE_TYPE_EXTENSION;
/** @var NameNode */ /**
* @var NameNode
*/
public $name; public $name;
/** @var DirectiveNode[]|null */ /**
* @var DirectiveNode[]|null
*/
public $directives; public $directives;
/** @var FieldDefinitionNode[]|null */ /**
* @var FieldDefinitionNode[]|null
*/
public $fields; public $fields;
} }

View file

@ -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;
} }

View file

@ -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[]|NodeList
*/
public $values; public $values;
} }

View file

@ -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,31 +46,27 @@ 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)
{ {
$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->end = $endToken->end;
} }
$this->start = $startToken->start;
$this->end = $endToken->end;
} }
} }

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -1,58 +1,54 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function get_object_vars;
use function is_array;
use function is_scalar;
use function json_encode;
/**
* type Node = NameNode
* | DocumentNode
* | OperationDefinitionNode
* | VariableDefinitionNode
* | VariableNode
* | SelectionSetNode
* | FieldNode
* | ArgumentNode
* | FragmentSpreadNode
* | InlineFragmentNode
* | FragmentDefinitionNode
* | IntValueNode
* | FloatValueNode
* | StringValueNode
* | BooleanValueNode
* | EnumValueNode
* | ListValueNode
* | ObjectValueNode
* | ObjectFieldNode
* | DirectiveNode
* | ListTypeNode
* | NonNullTypeNode
*/
abstract class Node abstract class Node
{ {
/** @var Location */ /**
type Node = NameNode
| DocumentNode
| OperationDefinitionNode
| VariableDefinitionNode
| VariableNode
| SelectionSetNode
| FieldNode
| ArgumentNode
| FragmentSpreadNode
| InlineFragmentNode
| FragmentDefinitionNode
| IntValueNode
| FloatValueNode
| StringValueNode
| BooleanValueNode
| EnumValueNode
| ListValueNode
| ObjectValueNode
| ObjectFieldNode
| DirectiveNode
| ListTypeNode
| NonNullTypeNode
*/
public $kind;
/**
* @var Location
*/
public $loc; public $loc;
/** /**
* @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars * @param array $vars
*/ */
public function __construct(array $vars) public function __construct(array $vars)
{ {
if (empty($vars)) { if (!empty($vars)) {
return; Utils::assign($this, $vars);
} }
Utils::assign($this, $vars);
} }
/** /**
* @return self * @return $this
*/ */
public function cloneDeep() public function cloneDeep()
{ {
@ -60,9 +56,8 @@ abstract class Node
} }
/** /**
* @param string|NodeList|Location|Node|(Node|NodeList|Location)[] $value * @param $value
* * @return array|Node
* @return string|NodeList|Location|Node
*/ */
private function cloneValue($value) private function cloneValue($value)
{ {
@ -71,7 +66,7 @@ abstract class Node
foreach ($value as $key => $arrValue) { foreach ($value as $key => $arrValue) {
$cloned[$key] = $this->cloneValue($arrValue); $cloned[$key] = $this->cloneValue($arrValue);
} }
} elseif ($value instanceof self) { } else if ($value instanceof Node) {
$cloned = clone $value; $cloned = clone $value;
foreach (get_object_vars($cloned) as $prop => $propValue) { foreach (get_object_vars($cloned) as $prop => $propValue) {
$cloned->{$prop} = $this->cloneValue($propValue); $cloned->{$prop} = $this->cloneValue($propValue);
@ -89,35 +84,34 @@ abstract class Node
public function __toString() public function __toString()
{ {
$tmp = $this->toArray(true); $tmp = $this->toArray(true);
return json_encode($tmp);
return (string) json_encode($tmp);
} }
/** /**
* @param bool $recursive * @param bool $recursive
* * @return array
* @return mixed[]
*/ */
public function toArray($recursive = false) public function toArray($recursive = false)
{ {
if ($recursive) { if ($recursive) {
return $this->recursiveToArray($this); return $this->recursiveToArray($this);
} else {
$tmp = (array) $this;
if ($this->loc) {
$tmp['loc'] = [
'start' => $this->loc->start,
'end' => $this->loc->end
];
}
return $tmp;
} }
$tmp = (array) $this;
if ($this->loc !== null) {
$tmp['loc'] = [
'start' => $this->loc->start,
'end' => $this->loc->end,
];
}
return $tmp;
} }
/** /**
* @return mixed[] * @param Node $node
* @return array
*/ */
private function recursiveToArray(Node $node) private function recursiveToArray(Node $node)
{ {
@ -125,30 +119,28 @@ abstract class Node
'kind' => $node->kind, 'kind' => $node->kind,
]; ];
if ($node->loc !== null) { if ($node->loc) {
$result['loc'] = [ $result['loc'] = [
'start' => $node->loc->start, 'start' => $node->loc->start,
'end' => $node->loc->end, 'end' => $node->loc->end
]; ];
} }
foreach (get_object_vars($node) as $prop => $propValue) { foreach (get_object_vars($node) as $prop => $propValue) {
if (isset($result[$prop])) { if (isset($result[$prop]))
continue; continue;
}
if ($propValue === null) { if ($propValue === null)
continue; continue;
}
if (is_array($propValue) || $propValue instanceof NodeList) { if (is_array($propValue) || $propValue instanceof NodeList) {
$tmp = []; $tmp = [];
foreach ($propValue as $tmp1) { foreach ($propValue as $tmp1) {
$tmp[] = $tmp1 instanceof Node ? $this->recursiveToArray($tmp1) : (array) $tmp1; $tmp[] = $tmp1 instanceof Node ? $this->recursiveToArray($tmp1) : (array) $tmp1;
} }
} elseif ($propValue instanceof Node) { } else if ($propValue instanceof Node) {
$tmp = $this->recursiveToArray($propValue); $tmp = $this->recursiveToArray($propValue);
} elseif (is_scalar($propValue) || $propValue === null) { } else if (is_scalar($propValue) || null === $propValue) {
$tmp = $propValue; $tmp = $propValue;
} else { } else {
$tmp = null; $tmp = null;
@ -156,7 +148,6 @@ abstract class Node
$result[$prop] = $tmp; $result[$prop] = $tmp;
} }
return $result; return $result;
} }
} }

View file

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
class NodeKind class NodeKind
@ -9,130 +7,139 @@ class NodeKind
// constants from language/kinds.js: // constants from language/kinds.js:
const NAME = 'Name'; const NAME = 'Name';
// Document // Document
const DOCUMENT = 'Document'; const DOCUMENT = 'Document';
const OPERATION_DEFINITION = 'OperationDefinition'; const OPERATION_DEFINITION = 'OperationDefinition';
const VARIABLE_DEFINITION = 'VariableDefinition'; const VARIABLE_DEFINITION = 'VariableDefinition';
const VARIABLE = 'Variable'; const VARIABLE = 'Variable';
const SELECTION_SET = 'SelectionSet'; const SELECTION_SET = 'SelectionSet';
const FIELD = 'Field'; const FIELD = 'Field';
const ARGUMENT = 'Argument'; const ARGUMENT = 'Argument';
// Fragments // Fragments
const FRAGMENT_SPREAD = 'FragmentSpread'; const FRAGMENT_SPREAD = 'FragmentSpread';
const INLINE_FRAGMENT = 'InlineFragment'; const INLINE_FRAGMENT = 'InlineFragment';
const FRAGMENT_DEFINITION = 'FragmentDefinition'; const FRAGMENT_DEFINITION = 'FragmentDefinition';
// Values // Values
const INT = 'IntValue'; const INT = 'IntValue';
const FLOAT = 'FloatValue'; const FLOAT = 'FloatValue';
const STRING = 'StringValue'; const STRING = 'StringValue';
const BOOLEAN = 'BooleanValue'; const BOOLEAN = 'BooleanValue';
const ENUM = 'EnumValue'; const ENUM = 'EnumValue';
const NULL = 'NullValue'; const NULL = 'NullValue';
const LST = 'ListValue'; const LST = 'ListValue';
const OBJECT = 'ObjectValue'; const OBJECT = 'ObjectValue';
const OBJECT_FIELD = 'ObjectField'; const OBJECT_FIELD = 'ObjectField';
// Directives // Directives
const DIRECTIVE = 'Directive'; const DIRECTIVE = 'Directive';
// Types // Types
const NAMED_TYPE = 'NamedType'; const NAMED_TYPE = 'NamedType';
const LIST_TYPE = 'ListType'; const LIST_TYPE = 'ListType';
const NON_NULL_TYPE = 'NonNullType'; const NON_NULL_TYPE = 'NonNullType';
// Type System Definitions // Type System Definitions
const SCHEMA_DEFINITION = 'SchemaDefinition'; const SCHEMA_DEFINITION = 'SchemaDefinition';
const OPERATION_TYPE_DEFINITION = 'OperationTypeDefinition'; const OPERATION_TYPE_DEFINITION = 'OperationTypeDefinition';
// Type Definitions // Type Definitions
const SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition'; const SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition';
const OBJECT_TYPE_DEFINITION = 'ObjectTypeDefinition'; const OBJECT_TYPE_DEFINITION = 'ObjectTypeDefinition';
const FIELD_DEFINITION = 'FieldDefinition'; const FIELD_DEFINITION = 'FieldDefinition';
const INPUT_VALUE_DEFINITION = 'InputValueDefinition'; const INPUT_VALUE_DEFINITION = 'InputValueDefinition';
const INTERFACE_TYPE_DEFINITION = 'InterfaceTypeDefinition'; const INTERFACE_TYPE_DEFINITION = 'InterfaceTypeDefinition';
const UNION_TYPE_DEFINITION = 'UnionTypeDefinition'; const UNION_TYPE_DEFINITION = 'UnionTypeDefinition';
const ENUM_TYPE_DEFINITION = 'EnumTypeDefinition'; const ENUM_TYPE_DEFINITION = 'EnumTypeDefinition';
const ENUM_VALUE_DEFINITION = 'EnumValueDefinition'; const ENUM_VALUE_DEFINITION = 'EnumValueDefinition';
const INPUT_OBJECT_TYPE_DEFINITION = 'InputObjectTypeDefinition'; const INPUT_OBJECT_TYPE_DEFINITION = 'InputObjectTypeDefinition';
// Type Extensions // Type Extensions
const SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension'; const SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension';
const OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension'; const OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension';
const INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension'; const INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension';
const UNION_TYPE_EXTENSION = 'UnionTypeExtension'; const UNION_TYPE_EXTENSION = 'UnionTypeExtension';
const ENUM_TYPE_EXTENSION = 'EnumTypeExtension'; const ENUM_TYPE_EXTENSION = 'EnumTypeExtension';
const INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension'; const INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension';
// Directive Definitions // Directive Definitions
const DIRECTIVE_DEFINITION = 'DirectiveDefinition'; const DIRECTIVE_DEFINITION = 'DirectiveDefinition';
// Type System Extensions /**
const SCHEMA_EXTENSION = 'SchemaExtension'; * @todo conver to const array when moving to PHP5.6
* @var array
/** @var string[] */ */
public static $classMap = [ public static $classMap = [
self::NAME => NameNode::class, NodeKind::NAME => NameNode::class,
// Document // Document
self::DOCUMENT => DocumentNode::class, NodeKind::DOCUMENT => DocumentNode::class,
self::OPERATION_DEFINITION => OperationDefinitionNode::class, NodeKind::OPERATION_DEFINITION => OperationDefinitionNode::class,
self::VARIABLE_DEFINITION => VariableDefinitionNode::class, NodeKind::VARIABLE_DEFINITION => VariableDefinitionNode::class,
self::VARIABLE => VariableNode::class, NodeKind::VARIABLE => VariableNode::class,
self::SELECTION_SET => SelectionSetNode::class, NodeKind::SELECTION_SET => SelectionSetNode::class,
self::FIELD => FieldNode::class, NodeKind::FIELD => FieldNode::class,
self::ARGUMENT => ArgumentNode::class, NodeKind::ARGUMENT => ArgumentNode::class,
// Fragments // Fragments
self::FRAGMENT_SPREAD => FragmentSpreadNode::class, NodeKind::FRAGMENT_SPREAD => FragmentSpreadNode::class,
self::INLINE_FRAGMENT => InlineFragmentNode::class, NodeKind::INLINE_FRAGMENT => InlineFragmentNode::class,
self::FRAGMENT_DEFINITION => FragmentDefinitionNode::class, NodeKind::FRAGMENT_DEFINITION => FragmentDefinitionNode::class,
// Values // Values
self::INT => IntValueNode::class, NodeKind::INT => IntValueNode::class,
self::FLOAT => FloatValueNode::class, NodeKind::FLOAT => FloatValueNode::class,
self::STRING => StringValueNode::class, NodeKind::STRING => StringValueNode::class,
self::BOOLEAN => BooleanValueNode::class, NodeKind::BOOLEAN => BooleanValueNode::class,
self::ENUM => EnumValueNode::class, NodeKind::ENUM => EnumValueNode::class,
self::NULL => NullValueNode::class, NodeKind::NULL => NullValueNode::class,
self::LST => ListValueNode::class, NodeKind::LST => ListValueNode::class,
self::OBJECT => ObjectValueNode::class, NodeKind::OBJECT => ObjectValueNode::class,
self::OBJECT_FIELD => ObjectFieldNode::class, NodeKind::OBJECT_FIELD => ObjectFieldNode::class,
// Directives // Directives
self::DIRECTIVE => DirectiveNode::class, NodeKind::DIRECTIVE => DirectiveNode::class,
// Types // Types
self::NAMED_TYPE => NamedTypeNode::class, NodeKind::NAMED_TYPE => NamedTypeNode::class,
self::LIST_TYPE => ListTypeNode::class, NodeKind::LIST_TYPE => ListTypeNode::class,
self::NON_NULL_TYPE => NonNullTypeNode::class, NodeKind::NON_NULL_TYPE => NonNullTypeNode::class,
// Type System Definitions // Type System Definitions
self::SCHEMA_DEFINITION => SchemaDefinitionNode::class, NodeKind::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
self::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class, NodeKind::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class,
// Type Definitions // Type Definitions
self::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class, NodeKind::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class,
self::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class, NodeKind::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class,
self::FIELD_DEFINITION => FieldDefinitionNode::class, NodeKind::FIELD_DEFINITION => FieldDefinitionNode::class,
self::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class, NodeKind::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class,
self::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class, NodeKind::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class,
self::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class, NodeKind::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class,
self::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class, NodeKind::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class,
self::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class, NodeKind::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class,
self::INPUT_OBJECT_TYPE_DEFINITION => InputObjectTypeDefinitionNode::class, NodeKind::INPUT_OBJECT_TYPE_DEFINITION =>InputObjectTypeDefinitionNode::class,
// Type Extensions // Type Extensions
self::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class, NodeKind::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class,
self::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class, NodeKind::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class,
self::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class, NodeKind::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class,
self::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class, NodeKind::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class,
self::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class, NodeKind::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class,
self::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class, NodeKind::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class,
// Directive Definitions // Directive Definitions
self::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class, NodeKind::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class
]; ];
} }

View file

@ -1,27 +1,22 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
use ArrayAccess;
use Countable;
use Generator;
use GraphQL\Utils\AST; use GraphQL\Utils\AST;
use IteratorAggregate;
use function array_merge;
use function array_splice;
use function count;
use function is_array;
class NodeList implements ArrayAccess, IteratorAggregate, Countable /**
* Class NodeList
*
* @package GraphQL\Utils
*/
class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
{ {
/** @var Node[]|mixed[] */ /**
* @var array
*/
private $nodes; private $nodes;
/** /**
* @param Node[]|mixed[] $nodes * @param array $nodes
*
* @return static * @return static
*/ */
public static function create(array $nodes) public static function create(array $nodes)
@ -30,7 +25,8 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
} }
/** /**
* @param Node[]|mixed[] $nodes * NodeList constructor.
* @param array $nodes
*/ */
public function __construct(array $nodes) public function __construct(array $nodes)
{ {
@ -39,7 +35,6 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
/** /**
* @param mixed $offset * @param mixed $offset
*
* @return bool * @return bool
*/ */
public function offsetExists($offset) public function offsetExists($offset)
@ -49,7 +44,6 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
/** /**
* @param mixed $offset * @param mixed $offset
*
* @return mixed * @return mixed
*/ */
public function offsetGet($offset) public function offsetGet($offset)
@ -84,10 +78,9 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
} }
/** /**
* @param int $offset * @param int $offset
* @param int $length * @param int $length
* @param mixed $replacement * @param mixed $replacement
*
* @return NodeList * @return NodeList
*/ */
public function splice($offset, $length, $replacement = null) public function splice($offset, $length, $replacement = null)
@ -96,26 +89,25 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
} }
/** /**
* @param NodeList|Node[] $list * @param $list
*
* @return NodeList * @return NodeList
*/ */
public function merge($list) public function merge($list)
{ {
if ($list instanceof self) { if ($list instanceof NodeList) {
$list = $list->nodes; $list = $list->nodes;
} }
return new NodeList(array_merge($this->nodes, $list)); return new NodeList(array_merge($this->nodes, $list));
} }
/** /**
* @return Generator * @return \Generator
*/ */
public function getIterator() public function getIterator()
{ {
foreach ($this->nodes as $key => $_) { $count = count($this->nodes);
yield $this->offsetGet($key); for ($i = 0; $i < $count; $i++) {
yield $this->offsetGet($i);
} }
} }

View file

@ -1,14 +1,12 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
class NonNullTypeNode extends Node implements TypeNode class NonNullTypeNode extends Node implements TypeNode
{ {
/** @var string */
public $kind = NodeKind::NON_NULL_TYPE; public $kind = NodeKind::NON_NULL_TYPE;
/** @var NamedTypeNode | ListTypeNode */ /**
* @var NameNode | ListTypeNode
*/
public $type; public $type;
} }

View file

@ -1,11 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
class NullValueNode extends Node implements ValueNode class NullValueNode extends Node implements ValueNode
{ {
/** @var string */
public $kind = NodeKind::NULL; public $kind = NodeKind::NULL;
} }

View file

@ -1,17 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Language\AST; namespace GraphQL\Language\AST;
class ObjectFieldNode extends Node class ObjectFieldNode extends Node
{ {
/** @var string */
public $kind = NodeKind::OBJECT_FIELD; public $kind = NodeKind::OBJECT_FIELD;
/** @var NameNode */ /**
* @var NameNode
*/
public $name; public $name;
/** @var ValueNode */ /**
* @var ValueNode
*/
public $value; public $value;
} }

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