mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-04-10 04:31:02 +00:00
Compare commits
34 commits
Author | SHA1 | Date | |
---|---|---|---|
|
4c545e5ec4 | ||
|
f2622fe7fb | ||
|
b36caceade | ||
|
f1fc5d66c9 | ||
|
761bcfb6f7 | ||
|
6996e2da2b | ||
|
4209a2f60e | ||
|
19235f5a2d | ||
|
639f493e47 | ||
|
297bac0dea | ||
|
68f0be08cb | ||
|
788581ad24 | ||
|
f7132eb34f | ||
|
3da424dacf | ||
|
fce0e4dd22 | ||
|
0d63d74cbb | ||
|
d5b4d446ce | ||
|
5c9b5576e6 | ||
|
b705ee797f | ||
|
17392e05dd | ||
|
c258109844 | ||
|
50dfd3fac2 | ||
|
c5b78c66e9 | ||
|
47bcabfd7b | ||
|
4ef7920961 | ||
|
167c3e7354 | ||
|
a8cd87acff | ||
|
e9cd1daedb | ||
|
2540436a2c | ||
|
d53f7f041e | ||
|
91daa23c5f | ||
|
57c77623ee | ||
|
78e2862e3b | ||
|
63b04df6ef |
26 changed files with 315 additions and 123 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ composer.phar
|
||||||
composer.lock
|
composer.lock
|
||||||
vendor/
|
vendor/
|
||||||
bin/
|
bin/
|
||||||
|
phpstan.phar
|
||||||
|
|
13
.travis.yml
13
.travis.yml
|
@ -9,7 +9,13 @@ php:
|
||||||
- 7.2
|
- 7.2
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
matrix:
|
jobs:
|
||||||
|
include:
|
||||||
|
- stage: Code Quality
|
||||||
|
php: 7.2
|
||||||
|
env: STATIC_ANALYSIS
|
||||||
|
install: travis_retry composer install --prefer-dist
|
||||||
|
script: composer static-analysis
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- php: nightly
|
- php: nightly
|
||||||
|
|
||||||
|
@ -22,10 +28,7 @@ before_install:
|
||||||
- phpenv config-rm xdebug.ini || true
|
- phpenv config-rm xdebug.ini || true
|
||||||
- composer selfupdate
|
- composer selfupdate
|
||||||
|
|
||||||
install:
|
install: composer install --dev --prefer-dist
|
||||||
- composer install --dev --prefer-dist
|
|
||||||
- composer require react/promise:2.*
|
|
||||||
- composer require psr/http-message:1.*
|
|
||||||
|
|
||||||
script: if [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then bin/phpunit --coverage-clover build/logs/clover.xml --group default,ReactPromise; else bin/phpunit --group default,ReactPromise; fi
|
script: 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
|
||||||
|
|
||||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -1,4 +1,25 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
#### v0.12.6
|
||||||
|
- Bugfix: Call to a member function getLocation() on null (#336)
|
||||||
|
- Fixed several errors discovered by static analysis (#329)
|
||||||
|
|
||||||
|
#### v0.12.5
|
||||||
|
- Execution performance optimization for lists
|
||||||
|
|
||||||
|
#### v0.12.4
|
||||||
|
- Allow stringeable objects to be serialized by StringType (#303)
|
||||||
|
|
||||||
|
#### v0.12.3
|
||||||
|
- StandardServer: add support for the multipart/form-data content type (#300)
|
||||||
|
|
||||||
|
#### v0.12.2
|
||||||
|
- SchemaPrinter: Use multi-line block for trailing quote (#294)
|
||||||
|
|
||||||
|
#### v0.12.1
|
||||||
|
- Fixed bug in validation rule OverlappingFieldsCanBeMerged (#292)
|
||||||
|
- Added one more breaking change note in UPGRADE.md (#291)
|
||||||
|
- Spec compliance: remove `data` entry from response on top-level error (#281)
|
||||||
|
|
||||||
## v0.12.0
|
## v0.12.0
|
||||||
- RFC: Block String (multi-line strings via triple-quote """string""")
|
- RFC: Block String (multi-line strings via triple-quote """string""")
|
||||||
- GraphQL Schema SDL: Descriptions as strings (including multi-line)
|
- GraphQL Schema SDL: Descriptions as strings (including multi-line)
|
||||||
|
|
27
UPGRADE.md
27
UPGRADE.md
|
@ -10,6 +10,33 @@ Exception inside `parseLiteral()`, `parseValue()` and `serialize()`.
|
||||||
|
|
||||||
Returning null from any of these methods will now be treated as valid result.
|
Returning null from any of these methods will now be treated as valid result.
|
||||||
|
|
||||||
|
### Breaking: Custom scalar types parseLiteral() declaration changed
|
||||||
|
A new parameter was added to `parseLiteral()`, which also needs to be added to any custom scalar type extending from `ScalarType`
|
||||||
|
|
||||||
|
Before:
|
||||||
|
```php
|
||||||
|
class MyType extends ScalarType {
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
public function parseLiteral($valueNode) {
|
||||||
|
//custom implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
```php
|
||||||
|
class MyType extends ScalarType {
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
public function parseLiteral($valueNode, array $variables = null) {
|
||||||
|
//custom implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Breaking: Descriptions in comments are not used as descriptions by default anymore
|
### Breaking: Descriptions in comments are not used as descriptions by default anymore
|
||||||
Descriptions now need to be inside Strings or BlockStrings in order to be picked up as
|
Descriptions now need to be inside Strings or BlockStrings in order to be picked up as
|
||||||
description. If you want to keep the old behaviour you can supply the option `commentDescriptions`
|
description. If you want to keep the old behaviour you can supply the option `commentDescriptions`
|
||||||
|
|
|
@ -14,16 +14,14 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^4.8",
|
"phpunit/phpunit": "^4.8",
|
||||||
"psr/http-message": "^1.0"
|
"psr/http-message": "^1.0",
|
||||||
|
"react/promise": "2.*"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"bin-dir": "bin",
|
"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/"
|
||||||
|
@ -36,6 +34,15 @@
|
||||||
"GraphQL\\Examples\\Blog\\": "examples/01-blog/Blog/"
|
"GraphQL\\Examples\\Blog\\": "examples/01-blog/Blog/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"static-analysis": [
|
||||||
|
"rm phpstan.phar || true",
|
||||||
|
"@composer req --ansi --no-interaction --dev phpstan/phpstan-shim",
|
||||||
|
"cp -f vendor/phpstan/phpstan-shim/phpstan.phar .",
|
||||||
|
"@composer rem --ansi --dev phpstan/phpstan-shim",
|
||||||
|
"@php phpstan.phar analyse --ansi -l 1 -c phpstan.neon src"
|
||||||
|
]
|
||||||
|
},
|
||||||
"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"
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
* [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
|
* [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
|
||||||
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental)
|
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental)
|
||||||
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server
|
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server
|
||||||
|
* [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.
|
||||||
|
|
||||||
# General GraphQL Tools
|
# General GraphQL Tools
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ echo json_encode($output);
|
||||||
|
|
||||||
Our example is finished. Try it by running:
|
Our example is finished. Try it by running:
|
||||||
```sh
|
```sh
|
||||||
php -S localhost:8000 graphql.php
|
php -S localhost:8080 graphql.php
|
||||||
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
|
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -58,4 +58,4 @@ $trackDirective = new Directive([
|
||||||
```
|
```
|
||||||
|
|
||||||
See possible directive locations in
|
See possible directive locations in
|
||||||
[`GraphQL\Type\Definition\DirectiveLocation`](../reference.md#graphqltypedefinitiondirectivelocation).
|
[`GraphQL\Language\DirectiveLocation`](../reference.md#graphqllanguagedirectivelocation).
|
||||||
|
|
0
phpstan.neon
Normal file
0
phpstan.neon
Normal file
|
@ -265,7 +265,7 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
|
||||||
}, $positions);
|
}, $positions);
|
||||||
} else if ($nodes) {
|
} else if ($nodes) {
|
||||||
$this->locations = array_filter(array_map(function ($node) {
|
$this->locations = array_filter(array_map(function ($node) {
|
||||||
if ($node->loc) {
|
if ($node->loc && $node->loc->source) {
|
||||||
return $node->loc->source->getLocation($node->loc->start);
|
return $node->loc->source->getLocation($node->loc->start);
|
||||||
}
|
}
|
||||||
}, $nodes));
|
}, $nodes));
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
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;
|
||||||
|
@ -56,6 +57,11 @@ class ExecutionContext
|
||||||
*/
|
*/
|
||||||
public $errors;
|
public $errors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var PromiseAdapter
|
||||||
|
*/
|
||||||
|
public $promises;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
$schema,
|
$schema,
|
||||||
$fragments,
|
$fragments,
|
||||||
|
|
|
@ -273,6 +273,11 @@ class Executor
|
||||||
*/
|
*/
|
||||||
private $promises;
|
private $promises;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \SplObjectStorage
|
||||||
|
*/
|
||||||
|
private $subFieldCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executor constructor.
|
* Executor constructor.
|
||||||
*
|
*
|
||||||
|
@ -285,6 +290,7 @@ class Executor
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->exeContext = $context;
|
$this->exeContext = $context;
|
||||||
|
$this->subFieldCache = new \SplObjectStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -311,7 +317,10 @@ class Executor
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
->then(function ($data) {
|
->then(function ($data) {
|
||||||
return new ExecutionResult((array) $data, $this->exeContext->errors);
|
if ($data !== null){
|
||||||
|
$data = (array) $data;
|
||||||
|
}
|
||||||
|
return new ExecutionResult($data, $this->exeContext->errors);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,7 +560,7 @@ class Executor
|
||||||
switch ($selection->kind) {
|
switch ($selection->kind) {
|
||||||
case NodeKind::FIELD:
|
case NodeKind::FIELD:
|
||||||
if (!$this->shouldIncludeNode($selection)) {
|
if (!$this->shouldIncludeNode($selection)) {
|
||||||
continue;
|
continue 2;
|
||||||
}
|
}
|
||||||
$name = self::getFieldEntryKey($selection);
|
$name = self::getFieldEntryKey($selection);
|
||||||
if (!isset($fields[$name])) {
|
if (!isset($fields[$name])) {
|
||||||
|
@ -563,7 +572,7 @@ class Executor
|
||||||
if (!$this->shouldIncludeNode($selection) ||
|
if (!$this->shouldIncludeNode($selection) ||
|
||||||
!$this->doesFragmentConditionMatch($selection, $runtimeType)
|
!$this->doesFragmentConditionMatch($selection, $runtimeType)
|
||||||
) {
|
) {
|
||||||
continue;
|
continue 2;
|
||||||
}
|
}
|
||||||
$this->collectFields(
|
$this->collectFields(
|
||||||
$runtimeType,
|
$runtimeType,
|
||||||
|
@ -575,14 +584,14 @@ class Executor
|
||||||
case NodeKind::FRAGMENT_SPREAD:
|
case NodeKind::FRAGMENT_SPREAD:
|
||||||
$fragName = $selection->name->value;
|
$fragName = $selection->name->value;
|
||||||
if (!empty($visitedFragmentNames[$fragName]) || !$this->shouldIncludeNode($selection)) {
|
if (!empty($visitedFragmentNames[$fragName]) || !$this->shouldIncludeNode($selection)) {
|
||||||
continue;
|
continue 2;
|
||||||
}
|
}
|
||||||
$visitedFragmentNames[$fragName] = true;
|
$visitedFragmentNames[$fragName] = true;
|
||||||
|
|
||||||
/** @var FragmentDefinitionNode|null $fragment */
|
/** @var FragmentDefinitionNode|null $fragment */
|
||||||
$fragment = isset($exeContext->fragments[$fragName]) ? $exeContext->fragments[$fragName] : null;
|
$fragment = isset($exeContext->fragments[$fragName]) ? $exeContext->fragments[$fragName] : null;
|
||||||
if (!$fragment || !$this->doesFragmentConditionMatch($fragment, $runtimeType)) {
|
if (!$fragment || !$this->doesFragmentConditionMatch($fragment, $runtimeType)) {
|
||||||
continue;
|
continue 2;
|
||||||
}
|
}
|
||||||
$this->collectFields(
|
$this->collectFields(
|
||||||
$runtimeType,
|
$runtimeType,
|
||||||
|
@ -1308,12 +1317,28 @@ class Executor
|
||||||
&$result
|
&$result
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// Collect sub-fields to execute to complete this value.
|
$subFieldNodes = $this->collectSubFields($returnType, $fieldNodes);
|
||||||
$subFieldNodes = new \ArrayObject();
|
return $this->executeFields($returnType, $result, $path, $subFieldNodes);
|
||||||
$visitedFragmentNames = new \ArrayObject();
|
}
|
||||||
|
|
||||||
foreach ($fieldNodes as $fieldNode) {
|
/**
|
||||||
if (isset($fieldNode->selectionSet)) {
|
* @param ObjectType $returnType
|
||||||
|
* @param $fieldNodes
|
||||||
|
* @return \ArrayObject
|
||||||
|
*/
|
||||||
|
private function collectSubFields(ObjectType $returnType, $fieldNodes)
|
||||||
|
{
|
||||||
|
if (!isset($this->subFieldCache[$returnType])) {
|
||||||
|
$this->subFieldCache[$returnType] = new \SplObjectStorage();
|
||||||
|
}
|
||||||
|
if (!isset($this->subFieldCache[$returnType][$fieldNodes])) {
|
||||||
|
// Collect sub-fields to execute to complete this value.
|
||||||
|
$subFieldNodes = new \ArrayObject();
|
||||||
|
$visitedFragmentNames = new \ArrayObject();
|
||||||
|
foreach ($fieldNodes as $fieldNode) {
|
||||||
|
if (!isset($fieldNode->selectionSet)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$subFieldNodes = $this->collectFields(
|
$subFieldNodes = $this->collectFields(
|
||||||
$returnType,
|
$returnType,
|
||||||
$fieldNode->selectionSet,
|
$fieldNode->selectionSet,
|
||||||
|
@ -1321,9 +1346,9 @@ class Executor
|
||||||
$visitedFragmentNames
|
$visitedFragmentNames
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
$this->subFieldCache[$returnType][$fieldNodes] = $subFieldNodes;
|
||||||
}
|
}
|
||||||
|
return $this->subFieldCache[$returnType][$fieldNodes];
|
||||||
return $this->executeFields($returnType, $result, $path, $subFieldNodes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,15 +6,6 @@ use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\AST\NodeList;
|
use GraphQL\Language\AST\NodeList;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
|
|
||||||
class VisitorOperation
|
|
||||||
{
|
|
||||||
public $doBreak;
|
|
||||||
|
|
||||||
public $doContinue;
|
|
||||||
|
|
||||||
public $removeNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility for efficient AST traversal and modification.
|
* Utility for efficient AST traversal and modification.
|
||||||
*
|
*
|
||||||
|
@ -258,6 +249,7 @@ class Visitor
|
||||||
|
|
||||||
if ($visitFn) {
|
if ($visitFn) {
|
||||||
$result = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors);
|
$result = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors);
|
||||||
|
$editValue = null;
|
||||||
|
|
||||||
if ($result !== null) {
|
if ($result !== null) {
|
||||||
if ($result instanceof VisitorOperation) {
|
if ($result instanceof VisitorOperation) {
|
||||||
|
|
11
src/Language/VisitorOperation.php
Normal file
11
src/Language/VisitorOperation.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
namespace GraphQL\Language;
|
||||||
|
|
||||||
|
class VisitorOperation
|
||||||
|
{
|
||||||
|
public $doBreak;
|
||||||
|
|
||||||
|
public $doContinue;
|
||||||
|
|
||||||
|
public $removeNode;
|
||||||
|
}
|
|
@ -71,6 +71,8 @@ class Helper
|
||||||
}
|
}
|
||||||
} else if (stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
|
} else if (stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
|
||||||
$bodyParams = $_POST;
|
$bodyParams = $_POST;
|
||||||
|
} else if (stripos($contentType, 'multipart/form-data') !== false) {
|
||||||
|
$bodyParams = $_POST;
|
||||||
} else if (null === $contentType) {
|
} else if (null === $contentType) {
|
||||||
throw new RequestError('Missing "Content-Type" header');
|
throw new RequestError('Missing "Content-Type" header');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,7 +5,7 @@ use GraphQL\Error\FormattedError;
|
||||||
use GraphQL\Error\InvariantViolation;
|
use GraphQL\Error\InvariantViolation;
|
||||||
use GraphQL\Executor\ExecutionResult;
|
use GraphQL\Executor\ExecutionResult;
|
||||||
use GraphQL\Executor\Promise\Promise;
|
use GraphQL\Executor\Promise\Promise;
|
||||||
use GraphQL\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Message\StreamInterface;
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
|
|
@ -62,6 +62,11 @@ class FieldDefinition
|
||||||
*/
|
*/
|
||||||
public $config;
|
public $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var null|callable
|
||||||
|
*/
|
||||||
|
public $complexityFn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var OutputType
|
* @var OutputType
|
||||||
*/
|
*/
|
||||||
|
@ -108,7 +113,7 @@ class FieldDefinition
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array|Config $field
|
* @param array $field
|
||||||
* @param string $typeName
|
* @param string $typeName
|
||||||
* @return FieldDefinition
|
* @return FieldDefinition
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -24,7 +24,7 @@ class InputObjectField
|
||||||
public $description;
|
public $description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var callback|InputType
|
* @var callable|InputType
|
||||||
*/
|
*/
|
||||||
public $type;
|
public $type;
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,9 @@ represent free-form human-readable text.';
|
||||||
if ($value === null) {
|
if ($value === null) {
|
||||||
return 'null';
|
return 'null';
|
||||||
}
|
}
|
||||||
|
if (is_object($value) && method_exists($value, '__toString')) {
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
if (!is_scalar($value)) {
|
if (!is_scalar($value)) {
|
||||||
throw new Error("String cannot represent non scalar value: " . Utils::printSafe($value));
|
throw new Error("String cannot represent non scalar value: " . Utils::printSafe($value));
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,15 +275,35 @@ class SchemaPrinter
|
||||||
return self::printDescriptionWithComments($lines, $indentation, $firstInBlock);
|
return self::printDescriptionWithComments($lines, $indentation, $firstInBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
$description = ($indentation && !$firstInBlock) ? "\n" : '';
|
$description = ($indentation && !$firstInBlock)
|
||||||
if (count($lines) === 1 && mb_strlen($lines[0]) < 70) {
|
? "\n" . $indentation . '"""'
|
||||||
$description .= $indentation . '"""' . self::escapeQuote($lines[0]) . "\"\"\"\n";
|
: $indentation . '"""';
|
||||||
return $description;
|
|
||||||
|
// In some circumstances, a single line can be used for the description.
|
||||||
|
if (
|
||||||
|
count($lines) === 1 &&
|
||||||
|
mb_strlen($lines[0]) < 70 &&
|
||||||
|
substr($lines[0], -1) !== '"'
|
||||||
|
) {
|
||||||
|
return $description . self::escapeQuote($lines[0]) . "\"\"\"\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
$description .= $indentation . "\"\"\"\n";
|
// Format a multi-line block quote to account for leading space.
|
||||||
foreach ($lines as $line) {
|
$hasLeadingSpace = isset($lines[0]) &&
|
||||||
$description .= $indentation . self::escapeQuote($line) . "\n";
|
(
|
||||||
|
substr($lines[0], 0, 1) === ' ' ||
|
||||||
|
substr($lines[0], 0, 1) === '\t'
|
||||||
|
);
|
||||||
|
if (!$hasLeadingSpace) {
|
||||||
|
$description .= "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$lineLength = count($lines);
|
||||||
|
for ($i = 0; $i < $lineLength; $i++) {
|
||||||
|
if ($i !== 0 || !$hasLeadingSpace) {
|
||||||
|
$description .= $indentation;
|
||||||
|
}
|
||||||
|
$description .= self::escapeQuote($lines[$i]) . "\n";
|
||||||
}
|
}
|
||||||
$description .= $indentation . "\"\"\"\n";
|
$description .= $indentation . "\"\"\"\n";
|
||||||
|
|
||||||
|
|
|
@ -426,7 +426,7 @@ class OverlappingFieldsCanBeMerged extends AbstractValidationRule
|
||||||
$fragmentNames1Length = count($fragmentNames1);
|
$fragmentNames1Length = count($fragmentNames1);
|
||||||
if ($fragmentNames1Length !== 0) {
|
if ($fragmentNames1Length !== 0) {
|
||||||
$comparedFragments = [];
|
$comparedFragments = [];
|
||||||
for ($i = 0; $i < $fragmentNames2Length; $i++) {
|
for ($i = 0; $i < $fragmentNames1Length; $i++) {
|
||||||
$this->collectConflictsBetweenFieldsAndFragment(
|
$this->collectConflictsBetweenFieldsAndFragment(
|
||||||
$context,
|
$context,
|
||||||
$conflicts,
|
$conflicts,
|
||||||
|
|
|
@ -49,10 +49,10 @@ class VariablesDefaultValueAllowed extends AbstractValidationRule
|
||||||
|
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
NodeKind::SELECTION_SET => function(SelectionSetNode $node) use ($context) {
|
NodeKind::SELECTION_SET => function(SelectionSetNode $node) {
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) use ($context) {
|
NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) {
|
||||||
return Visitor::skipNode();
|
return Visitor::skipNode();
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -732,7 +732,6 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||||
';
|
';
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
'data' => null,
|
|
||||||
'errors' => [
|
'errors' => [
|
||||||
FormattedError::create($this->nonNullSyncError->getMessage(), [new SourceLocation(2, 17)])
|
FormattedError::create($this->nonNullSyncError->getMessage(), [new SourceLocation(2, 17)])
|
||||||
]
|
]
|
||||||
|
@ -750,7 +749,6 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||||
$ast = Parser::parse($doc);
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
'data' => null,
|
|
||||||
'errors' => [
|
'errors' => [
|
||||||
FormattedError::create($this->nonNullPromiseError->getMessage(), [new SourceLocation(2, 17)]),
|
FormattedError::create($this->nonNullPromiseError->getMessage(), [new SourceLocation(2, 17)]),
|
||||||
]
|
]
|
||||||
|
@ -789,7 +787,6 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||||
$ast = Parser::parse($doc);
|
$ast = Parser::parse($doc);
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
'data' => null,
|
|
||||||
'errors' => [
|
'errors' => [
|
||||||
[
|
[
|
||||||
'debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullPromise.',
|
'debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullPromise.',
|
||||||
|
|
|
@ -69,6 +69,28 @@ class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testParsesMultipartFormdataRequest()
|
||||||
|
{
|
||||||
|
$query = '{my query}';
|
||||||
|
$variables = ['test' => 1, 'test2' => 2];
|
||||||
|
$operation = 'op';
|
||||||
|
|
||||||
|
$post = [
|
||||||
|
'query' => $query,
|
||||||
|
'variables' => $variables,
|
||||||
|
'operationName' => $operation
|
||||||
|
];
|
||||||
|
$parsed = [
|
||||||
|
'raw' => $this->parseRawMultipartFormdataRequest($post),
|
||||||
|
'psr' => $this->parsePsrMultipartFormdataRequest($post)
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($parsed as $method => $parsedBody) {
|
||||||
|
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
|
||||||
|
$this->assertFalse($parsedBody->isReadOnly(), $method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function testParsesJSONRequest()
|
public function testParsesJSONRequest()
|
||||||
{
|
{
|
||||||
$query = '{my query}';
|
$query = '{my query}';
|
||||||
|
@ -327,6 +349,37 @@ class RequestParsingTest extends \PHPUnit_Framework_TestCase
|
||||||
return $helper->parsePsrRequest($psrRequest);
|
return $helper->parsePsrRequest($psrRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $postValue
|
||||||
|
* @return OperationParams|OperationParams[]
|
||||||
|
*/
|
||||||
|
private function parseRawMultipartFormDataRequest($postValue)
|
||||||
|
{
|
||||||
|
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data; boundary=----FormBoundary';
|
||||||
|
$_SERVER['REQUEST_METHOD'] = 'POST';
|
||||||
|
$_POST = $postValue;
|
||||||
|
|
||||||
|
$helper = new Helper();
|
||||||
|
return $helper->parseHttpRequest(function() {
|
||||||
|
throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $postValue
|
||||||
|
* @return array|Helper
|
||||||
|
*/
|
||||||
|
private function parsePsrMultipartFormDataRequest($postValue)
|
||||||
|
{
|
||||||
|
$psrRequest = new PsrRequestStub();
|
||||||
|
$psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary'];
|
||||||
|
$psrRequest->method = 'POST';
|
||||||
|
$psrRequest->parsedBody = $postValue;
|
||||||
|
|
||||||
|
$helper = new Helper();
|
||||||
|
return $helper->parsePsrRequest($psrRequest);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $getValue
|
* @param $getValue
|
||||||
* @return OperationParams
|
* @return OperationParams
|
||||||
|
|
|
@ -150,6 +150,7 @@ class ScalarSerializationTest extends \PHPUnit_Framework_TestCase
|
||||||
$this->assertSame('true', $stringType->serialize(true));
|
$this->assertSame('true', $stringType->serialize(true));
|
||||||
$this->assertSame('false', $stringType->serialize(false));
|
$this->assertSame('false', $stringType->serialize(false));
|
||||||
$this->assertSame('null', $stringType->serialize(null));
|
$this->assertSame('null', $stringType->serialize(null));
|
||||||
|
$this->assertSame('2', $stringType->serialize(new ObjectIdStub(2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSerializesOutputStringsCannotRepresentArray()
|
public function testSerializesOutputStringsCannotRepresentArray()
|
||||||
|
|
|
@ -11,6 +11,7 @@ use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Definition\EnumType;
|
use GraphQL\Type\Definition\EnumType;
|
||||||
use GraphQL\Type\Definition\UnionType;
|
use GraphQL\Type\Definition\UnionType;
|
||||||
|
use GraphQL\Utils\BuildSchema;
|
||||||
use GraphQL\Utils\SchemaPrinter;
|
use GraphQL\Utils\SchemaPrinter;
|
||||||
|
|
||||||
class SchemaPrinterTest extends \PHPUnit_Framework_TestCase
|
class SchemaPrinterTest extends \PHPUnit_Framework_TestCase
|
||||||
|
@ -24,13 +25,13 @@ class SchemaPrinterTest extends \PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
private function printSingleFieldSchema($fieldConfig)
|
private function printSingleFieldSchema($fieldConfig)
|
||||||
{
|
{
|
||||||
$root = new ObjectType([
|
$query = new ObjectType([
|
||||||
'name' => 'Root',
|
'name' => 'Query',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'singleField' => $fieldConfig
|
'singleField' => $fieldConfig
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
return $this->printForTest(new Schema(['query' => $root]));
|
return $this->printForTest(new Schema(['query' => $query]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,11 +43,7 @@ class SchemaPrinterTest extends \PHPUnit_Framework_TestCase
|
||||||
'type' => Type::string()
|
'type' => Type::string()
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField: String
|
singleField: String
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -61,11 +58,7 @@ type Root {
|
||||||
'type' => Type::listOf(Type::string())
|
'type' => Type::listOf(Type::string())
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField: [String]
|
singleField: [String]
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -80,11 +73,7 @@ type Root {
|
||||||
'type' => Type::nonNull(Type::string())
|
'type' => Type::nonNull(Type::string())
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField: String!
|
singleField: String!
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -99,11 +88,7 @@ type Root {
|
||||||
'type' => Type::nonNull(Type::listOf(Type::string()))
|
'type' => Type::nonNull(Type::listOf(Type::string()))
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField: [String]!
|
singleField: [String]!
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -118,11 +103,7 @@ type Root {
|
||||||
'type' => Type::listOf(Type::nonNull(Type::string()))
|
'type' => Type::listOf(Type::nonNull(Type::string()))
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField: [String!]
|
singleField: [String!]
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -137,11 +118,7 @@ type Root {
|
||||||
'type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))
|
'type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField: [String!]!
|
singleField: [String!]!
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -189,11 +166,7 @@ type Root {
|
||||||
'args' => ['argOne' => ['type' => Type::int()]]
|
'args' => ['argOne' => ['type' => Type::int()]]
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField(argOne: Int): String
|
singleField(argOne: Int): String
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -209,11 +182,7 @@ type Root {
|
||||||
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => 2]]
|
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => 2]]
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField(argOne: Int = 2): String
|
singleField(argOne: Int = 2): String
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -229,11 +198,7 @@ type Root {
|
||||||
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => null]]
|
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => null]]
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField(argOne: Int = null): String
|
singleField(argOne: Int = null): String
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -249,11 +214,7 @@ type Root {
|
||||||
'args' => ['argOne' => ['type' => Type::nonNull(Type::int())]]
|
'args' => ['argOne' => ['type' => Type::nonNull(Type::int())]]
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField(argOne: Int!): String
|
singleField(argOne: Int!): String
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -272,11 +233,7 @@ type Root {
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField(argOne: Int, argTwo: String): String
|
singleField(argOne: Int, argTwo: String): String
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -296,11 +253,7 @@ type Root {
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField(argOne: Int = 1, argTwo: String, argThree: Boolean): String
|
singleField(argOne: Int = 1, argTwo: String, argThree: Boolean): String
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -320,11 +273,7 @@ type Root {
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField(argOne: Int, argTwo: String = "foo", argThree: Boolean): String
|
singleField(argOne: Int, argTwo: String = "foo", argThree: Boolean): String
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -344,11 +293,7 @@ type Root {
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
$this->assertEquals('
|
$this->assertEquals('
|
||||||
schema {
|
type Query {
|
||||||
query: Root
|
|
||||||
}
|
|
||||||
|
|
||||||
type Root {
|
|
||||||
singleField(argOne: Int, argTwo: String, argThree: Boolean = false): String
|
singleField(argOne: Int, argTwo: String, argThree: Boolean = false): String
|
||||||
}
|
}
|
||||||
', $output);
|
', $output);
|
||||||
|
@ -661,6 +606,78 @@ type Query {
|
||||||
', $output);
|
', $output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it One-line prints a short description
|
||||||
|
*/
|
||||||
|
public function testOneLinePrintsAShortDescription()
|
||||||
|
{
|
||||||
|
$description = 'This field is awesome';
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
"type" => Type::string(),
|
||||||
|
"description" => $description
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals('
|
||||||
|
type Query {
|
||||||
|
"""This field is awesome"""
|
||||||
|
singleField: String
|
||||||
|
}
|
||||||
|
', $output);
|
||||||
|
|
||||||
|
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
|
||||||
|
$recreatedField = $recreatedRoot->getFields()['singleField'];
|
||||||
|
$this->assertEquals($description, $recreatedField->description);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Does not one-line print a description that ends with a quote
|
||||||
|
*/
|
||||||
|
public function testDoesNotOneLinePrintADescriptionThatEndsWithAQuote()
|
||||||
|
{
|
||||||
|
$description = 'This field is "awesome"';
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
"type" => Type::string(),
|
||||||
|
"description" => $description
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals('
|
||||||
|
type Query {
|
||||||
|
"""
|
||||||
|
This field is "awesome"
|
||||||
|
"""
|
||||||
|
singleField: String
|
||||||
|
}
|
||||||
|
', $output);
|
||||||
|
|
||||||
|
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
|
||||||
|
$recreatedField = $recreatedRoot->getFields()['singleField'];
|
||||||
|
$this->assertEquals($description, $recreatedField->description);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Preserves leading spaces when printing a description
|
||||||
|
*/
|
||||||
|
public function testPReservesLeadingSpacesWhenPrintingADescription()
|
||||||
|
{
|
||||||
|
$description = ' This field is "awesome"';
|
||||||
|
$output = $this->printSingleFieldSchema([
|
||||||
|
"type" => Type::string(),
|
||||||
|
"description" => $description
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals('
|
||||||
|
type Query {
|
||||||
|
""" This field is "awesome"
|
||||||
|
"""
|
||||||
|
singleField: String
|
||||||
|
}
|
||||||
|
', $output);
|
||||||
|
|
||||||
|
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
|
||||||
|
$recreatedField = $recreatedRoot->getFields()['singleField'];
|
||||||
|
$this->assertEquals($description, $recreatedField->description);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @it Print Introspection Schema
|
* @it Print Introspection Schema
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Add table
Reference in a new issue