Compare commits

...

563 commits

Author SHA1 Message Date
Vladimir Razuvaev
b72ba3c93a
Merge pull request #518 from simPod/fix-internal-directives
Fix internal directives
2019-07-13 21:12:53 +07:00
Simon Podlipsky
1ac5af1d8b Fix internal directives 2019-07-12 21:01:10 +03:00
Vladimir Razuvaev
a01b089058
Merge pull request #517 from simPod/upgrade-phpstan
Upgrade PHPStan to 0.11.12
2019-07-12 16:35:34 +07:00
Vladimir Razuvaev
4401f4dd18
Merge pull request #504 from simPod/fielddef-types
Add FieldDefinition return type
2019-07-12 16:32:45 +07:00
Vladimir Razuvaev
005b1a38c5
Merge pull request #502 from simPod/drop-deprecated-schema
Drop deprecated GraphQL\Schema
2019-07-12 16:31:50 +07:00
Vladimir Razuvaev
974258b352
Merge pull request #503 from simPod/fix-reference-executor
Fix ReferenceExecutor
2019-07-12 16:31:29 +07:00
Simon Podlipsky
a502c33254 Upgrade PHPStan to 0.11.12 2019-07-10 18:05:47 +03:00
Vladimir Razuvaev
22a0da9b98
Merge pull request #505 from spawnia/root-value-consistent-naming
Consistently name the first resolver argument and the root value
2019-07-03 17:33:20 +07:00
Benedikt Franke
8c4e7b178d Fix up some overly eager renamings in the docs 2019-07-01 12:17:04 +02:00
Benedikt Franke
3b33167c87 Rename $rootValue where applicable 2019-07-01 12:12:01 +02:00
Benedikt Franke
d037ab7ec3 Merge branch 'master' into root-value-consistent-naming
# Conflicts:
#	docs/data-fetching.md
#	src/Executor/Executor.php
#	tests/Executor/DeferredFieldsTest.php
2019-07-01 08:23:00 +02:00
Vladimir Razuvaev
54064b37b3
Merge pull request #511 from spawnia/scalar-coercion
Resolve todo in Boolean coercion, add explanation, update test names to match reference implementation
2019-07-01 13:03:03 +07:00
Vladimir Razuvaev
f7443b6f0c
Merge pull request #510 from spawnia/split-into-multiline
Split some long lines into multiples
2019-07-01 12:56:45 +07:00
Vladimir Razuvaev
9704baf422
Merge pull request #508 from spawnia/consistency-and-clarity
Nonfunctional changes to improve consistency and clarity
2019-07-01 12:52:21 +07:00
Vladimir Razuvaev
ed1d835bd5
Merge pull request #507 from spawnia/resolver-arguments-order
Fix misleading comments and docs about resolver arguments: Context is 3rd, ResolveInfo is 4th
2019-07-01 12:50:01 +07:00
Vladimir Razuvaev
3b27abafca
Merge pull request #506 from spawnia/multiline-ternary
Spread ternary expressions across multiple lines
2019-07-01 12:49:03 +07:00
spawnia
c069d20ca7 And a few more in tests 2019-06-30 23:09:32 +02:00
spawnia
19a37609f4 Add a few more 2019-06-30 23:08:24 +02:00
spawnia
99453076b5 Remove &$ in phpdoc 2019-06-30 20:57:08 +02:00
spawnia
24f236403a More consistent naming 2019-06-30 20:54:56 +02:00
spawnia
8da3043702 Rename some test variable names 2019-06-23 21:26:38 +02:00
spawnia
a222cc9137 Reword a comment 2019-06-23 21:08:49 +02:00
spawnia
e704f8cc5c Split some long lines into multiples 2019-06-23 21:05:58 +02:00
spawnia
a34bb68d65 Resolve todo in Boolean coercion, add explanation, update test names to match reference implementation 2019-06-23 21:03:17 +02:00
spawnia
218e02a88c Revert composer.json changes 2019-06-23 18:46:27 +02:00
spawnia
9ca7bb6ea1 Expand one letter variable names 2019-06-23 18:42:25 +02:00
spawnia
65e4488ce8 Reformat some comments 2019-06-23 18:41:47 +02:00
spawnia
bc66034f40 Rename parameters and private fields to match what they contain 2019-06-23 18:40:56 +02:00
spawnia
65a3a8d13e Add missing renaming in ReferenceExecutor 2019-06-23 18:36:07 +02:00
spawnia
8381f67bd8 Add one more 2019-06-23 18:34:19 +02:00
spawnia
91b72f145d Context is the 3rd, ResolveInfo the 4th resolver argument 2019-06-23 18:25:02 +02:00
spawnia
6e91e2181c Spread ternary expressions across multiple lines 2019-06-23 18:04:30 +02:00
spawnia
03c33c9dc2 Consistently name the $rootValue argument 2019-06-23 17:30:57 +02:00
Vladimir Razuvaev
93ccd7351d Array in variables in place of object shouldn't cause fatal error (fixes #467) 2019-06-19 19:29:03 +07:00
Vladimir Razuvaev
ed1746e800 Error handling and schema validation improvements (#404) 2019-06-19 18:59:51 +07:00
Vladimir Razuvaev
84a52c6c76 Provide a path with a correct list index to resolveType callback of the union and interface types (fixes #396) 2019-06-19 17:11:24 +07:00
Vladimir Razuvaev
261f8f5ebd AST: Replaced array with NodeList where missing 2019-06-19 15:52:16 +07:00
Vladimir Razuvaev
d1d4455eaa Fixed warning added by one of the previous commits 2019-06-19 15:49:22 +07:00
Vladimir Razuvaev
d5fbf1b29f Merge branch 'master' of https://github.com/webonyx/graphql-php 2019-06-19 15:09:05 +07:00
Vladimir Razuvaev
c336d01bd2 Added .idea to .gitignore 2019-06-19 15:08:51 +07:00
Vladimir Razuvaev
752010b341 Documentation fix (#499) 2019-06-19 15:06:45 +07:00
Simon Podlipsky
368a9ee2f7 Add FieldDefinition return type 2019-06-17 14:54:05 +02:00
Simon Podlipsky
6086792824 Fix ReferenceExecutor 2019-06-17 14:18:12 +02:00
Simon Podlipsky
d259a303d3 Drop deprecated GraphQL\Schema 2019-06-17 14:15:46 +02:00
Vladimir Razuvaev
0a71f9fba9
Merge pull request #496 from simPod/fixes
Another code cleanup
2019-06-17 16:48:04 +07:00
Vladimir Razuvaev
61453a4f0b
Merge pull request #495 from spyl94/query-plan-should-work-on-interfaces
QueryPlan can now be used on interfaces not only objects.
2019-06-17 16:46:45 +07:00
Simon Podlipsky
6a5325a448 Another code cleanup 2019-06-12 15:43:17 +02:00
Aurélien David
e87460880c
QueryPlan can now be used on interfaces not only objects.
It's often the case to use interfaces in queries:

interface Pet { name: String! }

Query {
 pets: [Pet]
}
2019-06-12 11:59:42 +02:00
Vladimir Razuvaev
173a4297d9
Merge pull request #494 from simPod/cleanup-warning
Cleanup Warning
2019-06-12 16:40:02 +07:00
Vladimir Razuvaev
6f6a39468c
Merge pull request #493 from simPod/upgrade-phpstan
Upgrade PHPStan
2019-06-12 16:03:40 +07:00
Simon Podlipsky
a22a083220 Cleanup Warning 2019-06-12 11:03:14 +02:00
Vladimir Razuvaev
acc0442152 Merged branch 0.13.x into master 2019-06-12 15:45:44 +07:00
Simon Podlipsky
21592f8f28 Upgrade PHPStan 2019-06-12 10:22:18 +02:00
Vladimir Razuvaev
e17f578842
Merge pull request #492 from spawnia/circular-references
Add schema validation: Input Objects must not contain non-nullable circular references
2019-06-12 14:55:05 +07:00
Vladimir Razuvaev
6d9275e6bc
Merge pull request #474 from theofidry/patch-1
Ignore examples directory
2019-06-12 14:50:02 +07:00
Vladimir Razuvaev
747cb49ae3
Merge pull request #364 from simPod/instanceof
Replace node kind checks by InstanceOf checks
2019-06-12 14:48:50 +07:00
Vladimir Razuvaev
cdcf5b4473 v0.13.5 2019-06-12 14:16:37 +07:00
spawnia
692d10c127 Add Unreleases section to the Changelog 2019-06-10 22:30:25 +02:00
spawnia
6c82b85e79 Revert "Add changelog entry"
This reverts commit e5528d14
2019-06-10 22:24:54 +02:00
spawnia
e5528d14ab Add changelog entry 2019-06-10 22:23:06 +02:00
spawnia
c9faa3489b Add schema validation: Input Objects must not contain non-nullable circular references
Spec change: https://github.com/graphql/graphql-spec/pull/445
Reference implementation: https://github.com/graphql/graphql-js/pull/1359
2019-06-10 22:15:23 +02:00
Vladimir Razuvaev
bd02ccd47e
Merge pull request #486 from mcg-web/fix-coroutine-when-using-promise
Fix coroutine executor when using with promise
2019-06-06 16:50:40 +07:00
Jeremiah VALERIE
08d9493b2c
Fix coroutine executor when using with promise 2019-06-02 20:49:10 +02:00
Jeremiah VALERIE
0b4b1485e0
Add a test to cover promises 2019-06-02 20:49:10 +02:00
Vladimir Razuvaev
387f416984
Merge pull request #483 from mfn/patch-1
Add Laravel GraphQL implementation
2019-05-29 18:06:17 +07:00
Markus Podar
cf90a8d338
Add Laravel GraphQL implementation
It's a spiritual success to the archived https://github.com/Folkloreatelier/laravel-graphql project which was previously removed in https://github.com/webonyx/graphql-php/pull/455
2019-05-28 22:15:29 +02:00
Vladimir Razuvaev
2fd21dd231
Merge pull request #479 from stefsic/stefsic-readme-typo
Update README.md
2019-05-23 16:50:35 +07:00
stefania
2173bb9696
Update README.md
Fix "LICENCE" typo
2019-05-22 15:27:40 -04:00
Vladimir Razuvaev
e55c7d72cb v0.13.4 2019-05-13 14:12:05 +07:00
Vladimir Razuvaev
ef9a24b01f
Merge pull request #477 from mcg-web/max-query-int-forced
Force int when setting max query depth
2019-05-13 14:10:33 +07:00
Jeremiah VALERIE
40ac5ed269
Force int when setting max query depth 2019-05-13 07:50:47 +02:00
Vladimir Razuvaev
27539d5af0 v0.13.3 2019-05-11 14:48:12 +07:00
Vladimir Razuvaev
58d86abaf7
Merge pull request #476 from mcg-web/fix-bc
Fix BC
2019-05-11 11:44:23 +07:00
Jeremiah VALERIE
c803c455b4
Fix bc 2019-05-10 14:22:06 +02:00
Vladimir Razuvaev
9fc4d11425 v0.13.2 2019-05-10 13:03:54 +07:00
Vladimir Razuvaev
d0ab4dc8d8 Merge branch 'master' into 0.13.x
# Conflicts:
#	composer.json
#	src/Server/OperationParams.php
#	src/Type/Definition/EnumType.php
#	src/Type/Definition/InputObjectType.php
#	src/Type/Definition/InterfaceType.php
#	src/Type/Definition/ListOfType.php
#	src/Type/Definition/ResolveInfo.php
#	src/Type/Definition/UnionType.php
#	src/Validator/Rules/ValuesOfCorrectType.php
#	tests/Utils/SchemaExtenderTest.php
2019-05-10 12:19:45 +07:00
Vladimir Razuvaev
231919fbb2 Merge branch 'master' of https://github.com/webonyx/graphql-php 2019-05-10 12:03:22 +07:00
Vladimir Razuvaev
22e3b0e981
Merge pull request #475 from mll-lab/nodelist-offsets
Use foreach for iterating over NodeList
2019-05-10 11:57:44 +07:00
Benedikt Franke
019ed04a51
Update src/Language/AST/NodeList.php
Co-Authored-By: Šimon Podlipský <simon@podlipsky.net>
2019-05-09 19:33:37 +02:00
spawnia
e5b955ecc8 Use array_keys for iterating over NodeList 2019-05-09 17:04:17 +02:00
Théo FIDRY
e94db8a045
Update .gitattributes 2019-05-08 17:28:01 +02:00
Vladimir Razuvaev
49b7aced87
Merge pull request #472 from terabaud/patch-1
Documentation fix: add missing semicolon in generated cache file
2019-05-06 12:21:57 +07:00
Vladimir Razuvaev
0bd13d1828
Merge pull request #469 from simPod/fix-batch-query
Fix invalid query in docs
2019-05-06 12:17:05 +07:00
Lea Rosema
179944495e
fix: add missing semicolon in generated cache file
The `require $cacheFilename` statement leads to a PHP error due to a missing semicolon in the generated cache file.
2019-05-03 15:15:22 +02:00
Simon Podlipsky
0308cf0c0c Fix invalid query in docs 2019-05-03 00:56:11 +02:00
Vladimir Razuvaev
11d32d4568
Merge pull request #465 from mll-lab/enum-default-values
Enum default values
2019-03-29 15:41:12 +07:00
spawnia
db915d8812 Fix codestyle 2019-03-29 09:27:33 +01:00
Vladimir Razuvaev
5821caa249
Merge pull request #466 from simPod/upgrade-doctrine-cs
Upgrade Doctrine CS
2019-03-29 15:14:04 +07:00
Vladimir Razuvaev
8435c3111e
Merge pull request #464 from mll-lab/contributing
Clarify some bits of CONTRIBUTING.md and unify formatting
2019-03-28 19:09:04 +07:00
Vladimir Razuvaev
edec095055
Update README.md 2019-03-28 19:04:50 +07:00
Simon Podlipsky
06529e1924 Upgrade Doctrine CS 2019-03-28 12:18:35 +01:00
spawnia
ddebd9a414 Add test for default enum input coercion 2019-03-28 11:26:30 +01:00
spawnia
1acddf4e22 Mention default value definition for enum args in docs 2019-03-28 11:25:57 +01:00
spawnia
569945cd37 Clarify some bits of CONTRIBUTING.md and unify formatting 2019-03-28 11:24:06 +01:00
Vladimir Razuvaev
b088720d40
Merge pull request #458 from simPod/upgrade-phpstan
Upgrade PHPStan
2019-03-23 17:57:09 +07:00
Vladimir Razuvaev
b3bb316224
Merge pull request #457 from enumag/patch-4
Fix annotations
2019-03-19 12:19:51 +07:00
Simon Podlipsky
23ece09407 Upgrade PHPStan 2019-03-18 13:02:58 +01:00
Jáchym Toušek
1864facda8
Fix annotations 2019-03-18 12:31:33 +01:00
Vladimir Razuvaev
d15a9405cd
Merge pull request #455 from spawnia/patch-1
Remove deprecated tools and improve Lighthouse description
2019-03-17 23:22:41 +07:00
Benedikt Franke
d4742a76e5
Remove deprecated tools and improve Lighthouse description
- The folklore package was archived by the owner.
- The lighthouse relay tools were integrated with the core
2019-03-14 11:16:15 +01:00
Vladimir Razuvaev
f107cc2076 Merge branch 'master' of https://github.com/webonyx/graphql-php 2019-03-09 22:35:12 +07:00
Vladimir Razuvaev
0226b08429 v0.13.1 2019-03-09 22:34:29 +07:00
Simon Podlipsky
255ecbd709 Test against PHP 7.4
(cherry picked from commit dfefdf24cb)
2019-03-09 22:27:54 +07:00
Petr Skoda
8c66fa8d1e Standardise whitespace
(cherry picked from commit d20a6a9d56)
2019-03-09 22:27:40 +07:00
Petr Skoda
7405ddc852 Fix incorrect array type of rootValue in PHPDocs
(cherry picked from commit 77448ba623)
2019-03-09 22:27:30 +07:00
Simon Podlipsky
0cbc1c9c07 Support PHP 8
(cherry picked from commit bc637414e5)
2019-03-09 22:27:17 +07:00
Petr Skoda
c628fa39a1 Fix incorrect array type of contextValue in PHPDocs
(cherry picked from commit 7d59811c4f)
2019-03-09 22:27:03 +07:00
Stefano Torresi
a0f214a9f9 Update docs intro verbiage
Just throwing my 2 cents: I don't think it's fair to say that "it's intended to be a replacement", given that multiple API paradigms can coexist in the same system and each of them have their trade-offs.

(cherry picked from commit b1ab1820b6)
2019-03-09 22:26:44 +07:00
Jan Bukva
153f6f862e Fix return annotation of resolveType() in InterfaceType
(cherry picked from commit edb5268583)
2019-03-09 22:26:29 +07:00
Simon Podlipsky
610979555d Merge clovers
(cherry picked from commit fd7465521a)
2019-03-09 22:26:10 +07:00
Simon Podlipsky
ababa18157 Fix Deferred
(cherry picked from commit 828a9fb002)
2019-03-09 22:25:47 +07:00
Jan Bukva
f52dfcfaef Use key-value foreach
(cherry picked from commit 00490d289c)
2019-03-09 22:24:46 +07:00
Jan Bukva
8b8ea0d4a3 Fix CoroutineExecutor::resultToArray for associative arrays
(cherry picked from commit 2295b96a49)
2019-03-09 22:24:39 +07:00
Jan Bukva
42b20e7651 Failing test for CoroutineExecutor removes associative array when using custom scalar producing JSON
(cherry picked from commit 31d89acfae)
2019-03-09 22:24:17 +07:00
chriszarate
08992de960 Allow extensions to be provided in GET request.
(cherry picked from commit 244ec66ecc)
2019-03-09 22:20:14 +07:00
Torsten Blindert
1d8f526d91 TASK: Code style
(cherry picked from commit 9609d2ac84)
2019-03-09 22:18:54 +07:00
Torsten Blindert
fda73f3212 TASK: Added test
(cherry picked from commit d5fddfd504)
2019-03-09 22:18:45 +07:00
Torsten Blindert
22cee49747 BUGFIX: expect ->getType() to throw
(cherry picked from commit 62b0036437)
2019-03-09 22:18:35 +07:00
Erik Gaal
59c128c54a Add NullableType interface
(cherry picked from commit 9a0dbff26b)
2019-03-09 22:17:22 +07:00
chriszarate
a116127436 Add extensions to OperationParams instance.
(cherry picked from commit f644c1a837)
2019-03-09 22:16:45 +07:00
chriszarate
9ada606919 Fix linting issue and typos.
(cherry picked from commit c33e41f2bf)
2019-03-09 22:16:30 +07:00
chriszarate
1e778d259e Apollo server/client compatibility. Look for queryid in extensions.
(cherry picked from commit 63b4e3f0a4)
2019-03-09 22:15:55 +07:00
Simon Podlipsky
b005803bf6 Add PHP 7.3 to Travis
(cherry picked from commit f95d1e81ea)
2019-03-09 22:15:26 +07:00
Yury
21dc3fe664 Error handling improvements
(cherry picked from commit 33e3c9c338)
2019-03-09 22:15:11 +07:00
Simon Podlipsky
16d42dead3 Document BC and fix types in ResolveInfo
(cherry picked from commit 376e927505)
2019-03-09 22:14:35 +07:00
Simon Podlipsky
bf471838ae Added all possible scalar types to Node constructor
(cherry picked from commit ead1b864bc)
2019-03-09 22:14:24 +07:00
Vladimir Razuvaev
29eba82093
Merge pull request #436 from keulinho/add-queryplan
Added query plan
2019-03-09 21:37:44 +07:00
Vladimir Razuvaev
e6e9d9ea22 Merge branch 'master' of https://github.com/webonyx/graphql-php 2019-03-04 19:03:55 +07:00
Vladimir Razuvaev
f24e00f4b4
Merge pull request #449 from hughdevore/patch-1
Add WP-GraphQL to complementary tools.
2019-03-01 23:54:04 +07:00
Hughie Devore
4a39dadd0d
Add WP-GraphQL to complementary tools.
We utilized this repo for our work in WP GraphQL and wanted to let WP users know that it's available for use.
2019-02-28 13:23:23 -07:00
Vladimir Razuvaev
616fc10837
Merge pull request #431 from simPod/fix-bc-finder
Invert instance of in BCFinder
2019-02-19 10:53:03 +07:00
Vladimir Razuvaev
2f73bbe96c
Merge pull request #442 from moufmouf/patch-1
Adding GraphQLite to the list of PHP Tools
2019-02-19 10:48:52 +07:00
Vladimir Razuvaev
b667d8b3a3
Merge pull request #445 from Chrisdowson/master
change Document url
2019-02-19 10:48:01 +07:00
段小强
441d70c7e5
change Document url
change Document url
2019-02-19 11:21:25 +08:00
David Négrier
972532cf6c
Adding GraphQLite to the list of PHP Tools 2019-02-12 10:46:28 +01:00
Vladimir Razuvaev
e467f80149
Merge pull request #440 from simPod/php7.4
Test against PHP 7.4
2019-02-11 15:31:09 +07:00
Simon Podlipsky
d97fac6ab0 Invert instance of in BCFinder 2019-02-11 09:16:01 +01:00
Simon Podlipsky
dfefdf24cb Test against PHP 7.4 2019-02-11 08:37:05 +01:00
Vladimir Razuvaev
20e98aefa4
Merge pull request #438 from skodak/executioncontext
Fix incorrect array type of contextValue in PHPDocs
2019-02-11 10:56:05 +07:00
Vladimir Razuvaev
ff16350aa1
Merge pull request #439 from simPod/php8
Support PHP 8
2019-02-11 10:55:23 +07:00
Petr Skoda
d20a6a9d56 Standardise whitespace 2019-02-11 08:53:36 +13:00
Petr Skoda
77448ba623 Fix incorrect array type of rootValue in PHPDocs 2019-02-11 08:53:24 +13:00
Simon Podlipsky
bc637414e5 Support PHP 8 2019-02-08 15:08:11 +01:00
Petr Skoda
7d59811c4f Fix incorrect array type of contextValue in PHPDocs 2019-02-07 09:18:17 +13:00
Vladimir Razuvaev
31bbc416a5
Merge pull request #437 from stefanotorresi/patch-1
Update docs intro verbiage
2019-02-04 19:18:53 +07:00
Stefano Torresi
b1ab1820b6
Update docs intro verbiage
Just throwing my 2 cents: I don't think it's fair to say that "it's intended to be a replacement", given that multiple API paradigms can coexist in the same system and each of them have their trade-offs.
2019-02-04 12:38:31 +01:00
Jonas Elfering
5b3f44c7a3 add queryplan 2019-02-01 22:39:00 +01:00
Vladimir Razuvaev
1dc291b073
Merge pull request #435 from JanBukva/fix-interface-type-return
Fix return annotation of resolveType() in InterfaceType
2019-01-29 17:50:03 +07:00
Jan Bukva
edb5268583
Fix return annotation of resolveType() in InterfaceType 2019-01-28 22:46:20 +01:00
Vladimir Razuvaev
6544197ef8
Merge pull request #426 from simPod/merge-clovers
Merge clovers
2018-12-31 13:07:26 +03:00
Vladimir Razuvaev
5ac3eeab18
Merge pull request #418 from Quartz/improvement/apollo-compatible-persisted-queries
Allow extensions to be provided in GET request.
2018-12-31 13:05:09 +03:00
Vladimir Razuvaev
a80d38747f
Merge pull request #421 from JanBukva/new-executor-forces-array
Fix new executor is removing associative array when using custom scalar
2018-12-31 13:00:58 +03:00
Vladimir Razuvaev
9c1a89710e
Merge pull request #422 from simPod/fix-deferred
Fix Deferred
2018-12-31 12:40:58 +03:00
Simon Podlipsky
fd7465521a Merge clovers 2018-12-31 00:32:42 +01:00
Simon Podlipsky
828a9fb002 Fix Deferred 2018-12-27 22:50:12 +01:00
Jan Bukva
00490d289c
Use key-value foreach 2018-12-27 21:53:16 +01:00
Jan Bukva
2295b96a49
Fix CoroutineExecutor::resultToArray for associative arrays 2018-12-27 16:38:40 +01:00
Jan Bukva
31d89acfae
Failing test for CoroutineExecutor removes associative array when using custom scalar producing JSON 2018-12-27 16:01:27 +01:00
chriszarate
244ec66ecc Allow extensions to be provided in GET request. 2018-12-11 22:58:57 -05:00
Vladimir Razuvaev
f96bd2740d
Merge pull request #416 from erikgaal/master
Add NullableType interface
2018-12-07 18:26:04 +07:00
Vladimir Razuvaev
ead704022c
Merge pull request #417 from bytorsten/extension-fix
BUGFIX: ExtendSchema should be able to introduce new types
2018-12-07 16:58:05 +07:00
Torsten Blindert
9609d2ac84 TASK: Code style 2018-12-05 19:23:07 +01:00
Torsten Blindert
d5fddfd504 TASK: Added test 2018-12-05 19:13:32 +01:00
Torsten Blindert
62b0036437 BUGFIX: expect ->getType() to throw 2018-12-05 18:55:14 +01:00
Erik Gaal
9a0dbff26b Add NullableType interface 2018-12-05 11:43:27 +01:00
Vladimir Razuvaev
e22b400373
Merge pull request #413 from Quartz/improvement/apollo-compatible-persisted-queries
Apollo persisted query compatibility: Look for queryId in extensions.
2018-12-04 19:33:04 +07:00
chriszarate
f644c1a837 Add extensions to OperationParams instance. 2018-12-02 15:10:09 -05:00
chriszarate
c33e41f2bf Fix linting issue and typos. 2018-12-01 19:43:51 -05:00
chriszarate
63b4e3f0a4 Apollo server/client compatibility. Look for queryid in extensions. 2018-12-01 19:06:59 -05:00
Vladimir Razuvaev
039577d9eb
Merge pull request #403 from simPod/fix-conditions
Improve comparison in ReferenceExecutor
2018-12-01 23:04:34 +07:00
Vladimir Razuvaev
6610f4e2da
Merge pull request #406 from simPod/fix-type
Added all possible scalar types to Node constructor
2018-12-01 23:04:11 +07:00
Vladimir Razuvaev
19d9c523c5
Merge pull request #407 from simPod/document-bc
Document BC and fix types in ResolveInfo
2018-12-01 23:03:19 +07:00
Vladimir Razuvaev
99f93939db
Merge pull request #408 from yura3d/error-handling-improvements
Error handling improvements
2018-12-01 23:02:57 +07:00
Vladimir Razuvaev
8338db0480
Merge pull request #409 from simPod/php7.3
Add PHP 7.3 to Travis
2018-12-01 22:59:16 +07:00
Simon Podlipsky
f95d1e81ea Add PHP 7.3 to Travis 2018-11-29 11:10:51 +01:00
Yury
33e3c9c338
Error handling improvements 2018-11-28 17:35:21 +03:00
Simon Podlipsky
376e927505 Document BC and fix types in ResolveInfo 2018-11-27 14:05:55 +01:00
Simon Podlipsky
ead1b864bc Added all possible scalar types to Node constructor 2018-11-27 12:22:01 +01:00
Vladimir Razuvaev
b2cea8b538
Merge pull request #402 from simPod/complementary-tools
Complementary tools
2018-11-27 18:09:57 +07:00
Vladimir Razuvaev
012082d1d9 Minor documentation fix 2018-11-27 17:53:03 +07:00
Vladimir Razuvaev
9bb8e73277 v0.13.0 2018-11-27 17:23:58 +07:00
Vladimir Razuvaev
627cc786a5 Fixed codestyle in benchmarks 2018-11-27 17:22:29 +07:00
Vladimir Razuvaev
c45fa1a4b1 Default Error Formatting: Display error category under extensions key (#389) 2018-11-27 16:37:33 +07:00
Vladimir Razuvaev
54263d50c0 Added a note about experimental executor in the changelog 2018-11-27 15:51:53 +07:00
Vladimir Razuvaev
e1b4d438db Ability to override internal types (closes #401) 2018-11-27 15:39:20 +07:00
Simon Podlipsky
e344c8a441 Improve comparison in ReferenceExecutor 2018-11-23 12:17:33 +01:00
Simon Podlipsky
90b51e1a5d Replace node kind checks by InstanceOf checks 2018-11-23 12:07:48 +01:00
Simon Podlipsky
396d3370dc Mention simPod/GraphQL-Utils
Resolves #70
Closes #393
2018-11-23 11:00:26 +01:00
Simon Podlipsky
d591eccd9f Clean complementary tools 2018-11-23 10:58:58 +01:00
Vladimir Razuvaev
bdbb30c604 Schema validation improvements 2018-11-22 22:23:14 +07:00
Vladimir Razuvaev
89fa0c3e67 More composer scripts + related changes to CONTRIBUTING.md 2018-11-22 19:57:53 +07:00
Vladimir Razuvaev
9cb6b4e6f3 Additional tests for errors with extensions 2018-11-22 19:47:10 +07:00
Vladimir Razuvaev
d8d032f0f6 Merge branch 'markhuot-patch-1' 2018-11-22 18:44:22 +07:00
Vladimir Razuvaev
c0ae3ccdaf Fix possessive its vs contraction it's (resolving conflicts) 2018-11-22 18:43:35 +07:00
Vladimir Razuvaev
7c19777dff Ensure interface has at least 1 concrete type 2018-11-21 20:11:11 +07:00
Vladimir Razuvaev
779774b162 ReferenceExecutor: Remove excessive arguments 2018-11-21 18:54:11 +07:00
Vladimir Razuvaev
b4be42acdf Fix printing of ASTs of argument definitions with descriptions 2018-11-21 18:53:44 +07:00
Vladimir Razuvaev
25cfebbd37 Lexer: unify unexpected character errors 2018-11-21 18:09:57 +07:00
Vladimir Razuvaev
0f3ebaa20b Note about new executor in UPGRADE.md 2018-11-21 17:53:12 +07:00
Vladimir Razuvaev
21e0c830a6
Merge pull request #314 from jakubkulhan/compiler
Experimental executor with improved performance
2018-11-21 14:19:21 +07:00
Mark Huot
4811cd198f
Fixing test for possessive/contraction "its" change 2018-11-19 09:13:57 -05:00
Mark Huot
7249e2611a
Fix possessive its vs contraction it's 2018-11-11 07:59:45 -05:00
Jakub Kulhan
b5d3341995 Pluggable executor implementations; new faster executor using coroutines 2018-11-06 23:32:50 +01:00
Vladimir Razuvaev
98807286f7
Merge pull request #386 from nagledb/fix-definition-spelling
Fixed misspelling "defintion" in several places
2018-11-05 03:19:01 +07:00
David B. Nagle
f39f0f5517 Fixed misspelling "defintion" in several places 2018-11-03 15:35:18 -07:00
Vladimir Razuvaev
23169603fc Removed old experimental code 2018-11-03 23:25:23 +07:00
Vladimir Razuvaev
6f8aed800e
Merge pull request #369 from nagledb/nagledb-doc-default-field-resolver
Updated default field resolver code in documentation.
2018-11-03 16:13:57 +07:00
Vladimir Razuvaev
1a1bf17c6a Added test case for extended schema resolvers 2018-11-03 16:01:41 +07:00
Vladimir Razuvaev
cd38568aaa
Merge pull request #381 from jbtbnl/master
Fix resolveFn key for schema extensions
2018-11-03 15:56:57 +07:00
Vladimir Razuvaev
9ee62b014e
Merge pull request #384 from nagledb/restore-resolver-docs
Restore "Defining resolvers" documentation
2018-11-03 15:48:53 +07:00
David B. Nagle
4a096b14ec Restore "Defining resolvers" documentation
Content was inadvertently removed in d6add77540
2018-11-02 21:10:54 -07:00
jbtbnl
427ac1b5d5
Fix resolve Fn key 2018-10-26 12:09:14 +02:00
Vladimir Razuvaev
2bddfe2225
Merge pull request #375 from simPod/fix-build
Fixing build
2018-10-25 13:29:41 +02:00
Simon Podlipsky
0d954b6ecc Constrain PHPStan versions 2018-10-22 23:37:28 +02:00
Simon Podlipsky
644f97634b Little fixes 2018-10-22 23:37:28 +02:00
Vladimir Razuvaev
6cce6742eb
Merge pull request #367 from simPod/phpstan-strict
Use PHPStan strict rules
2018-10-22 17:28:59 +02:00
David B. Nagle
e32bbb726d Updated default field resolver code in documentation.
Updated code to match definition in GraphQL\Executor\Executor::defaultFieldResolver. Change is notable because previous version did not indicate that the closure receives the $info variable.
2018-10-10 20:05:21 -07:00
Vladimir Razuvaev
5fb970b3f1 Codestyle fix 2018-10-10 11:41:24 +02:00
Vladimir Razuvaev
0ea7ffa601 Node in UPGRADE.md about broken AST caches 2018-10-10 11:33:53 +02:00
Vladimir Razuvaev
4bc9dfc6f8 Reverted unneeded change in the defaultFieldResolver 2018-10-10 11:29:14 +02:00
Vladimir Razuvaev
c7fcd4eb48 Got rid of PHP_EOL usages (we never do or expect carriage returns) 2018-10-10 11:26:48 +02:00
Simon Podlipsky
90d0156291
Use PHPStan strict rules
Two rules excluded: missing type hints and empty() usage
2018-10-09 17:46:55 +02:00
Vladimir Razuvaev
0070cb4039
Merge pull request #366 from enumag/patch-1
Fix annotation
2018-10-09 16:18:36 +02:00
Jáchym Toušek
e4c743cf8c
Fix annotation 2018-10-09 14:46:41 +02:00
Torsten Blindert
e7de069bd5 FEATURE: SchemaExtender (#362)
FEATURE: SchemaExtender (#362)
2018-10-08 15:55:20 +02:00
Gilles Maes
7ff3e9399f Expand is_array check in defaultTypeResolver to allow for ArrayAccess… (#361)
Expand is_array check in defaultTypeResolver to allow for ArrayAccess objects as well
2018-10-08 15:53:07 +02:00
Vladimir Razuvaev
1417a43697
Merge pull request #363 from simPod/bump-cs
Bump cs
2018-10-05 12:04:42 +02:00
Simon Podlipsky
dd4a5076f6
Fix CS in src/Validator 2018-10-05 10:47:58 +02:00
Simon Podlipsky
e664c4455e
Fix CS in src/Utils 2018-10-05 10:47:58 +02:00
Simon Podlipsky
18a5639cb7
Fix CS in src/Type 2018-10-05 10:47:57 +02:00
Simon Podlipsky
7ba98ce773
Fix CS in src/Server 2018-10-05 10:47:57 +02:00
Simon Podlipsky
a95d2ad140
Fix CS in src/Language 2018-10-05 10:47:57 +02:00
Simon Podlipsky
bfebcd7bee
Fix CS in src/Executor 2018-10-05 10:47:57 +02:00
Simon Podlipsky
0063bd6c15
Fix CS in src/Error 2018-10-05 10:47:57 +02:00
Simon Podlipsky
73e75b6314
Fix CS in src 2018-10-05 10:47:57 +02:00
Simon Podlipsky
07c070d795
Fix CS in tests 2018-10-05 10:47:57 +02:00
Simon Podlipsky
af31ca7ad8
Bump Doctrine CS to v5 2018-10-05 10:24:10 +02:00
Vladimir Razuvaev
849c15dbf8
Merge pull request #354 from simPod/self
Use self:: in tests where appropriate
2018-10-02 15:12:05 +02:00
Vladimir Razuvaev
84f13650a2
Merge pull request #358 from simPod/php-bench
PHPBench
2018-10-02 15:11:50 +02:00
Vladimir Razuvaev
aed406eade
Merge pull request #360 from bytorsten/expand-extension
Add missing extensionASTNodes
2018-10-02 15:08:26 +02:00
Torsten Blindert
5f5c7d89be TASK: Code style #4 2018-10-02 09:58:44 +02:00
Torsten Blindert
5a1caf0549 TASK: Code style #2 2018-10-02 09:34:29 +02:00
Torsten Blindert
a3d050ff6b TASK: Code style 2018-10-02 09:09:12 +02:00
Torsten Blindert
0262f59a3f BUGFIX: Directives args have to be FieldArguments, not FieldDefinitions 2018-10-01 20:28:05 +02:00
Torsten Blindert
b65aa4e657 TASK: Change KnownDirectives rule to match reference
https://github.com/graphql/graphql-js/blob/master/src/validation/rules/KnownDirectives.js
2018-10-01 19:48:56 +02:00
Torsten Blindert
b1cd086177 TASK: Allow Schema extensions 2018-10-01 19:46:36 +02:00
Torsten Blindert
0c33cfa88f TASK: Allow UnionType extensions 2018-10-01 19:45:48 +02:00
Torsten Blindert
1dc2b939cb TASK: Allow ScalarType extensions 2018-10-01 19:45:36 +02:00
Torsten Blindert
b0ee36feb4 TASK: Allow InputObjectType extensions 2018-10-01 19:45:20 +02:00
Torsten Blindert
1d15ae7f3e TASK: Allow EnumType extensions 2018-10-01 19:44:55 +02:00
Simon Podlipsky
68bfde953d
PHPBench 2018-09-26 11:18:14 +02:00
Simon Podlipsky
c4e06ba528
Use self:: in tests where appropriate 2018-09-19 20:50:32 +02:00
Vladimir Razuvaev
4d4282b60f
Merge pull request #351 from simPod/fix-typo
Fix typo and cleanup
2018-09-10 14:21:33 +07:00
Simon Podlipsky
450a1932e5
Fix typo and cleanup 2018-09-09 17:32:53 +02:00
Vladimir Razuvaev
6bdead3fe3
Merge pull request #337 from spawnia/patch-1
Add Debug::RETHROW_UNSAFE_EXCEPTIONS flag
2018-09-06 12:09:05 +07:00
Vladimir Razuvaev
85f4c774a6
Merge pull request #349 from simPod/improve-stan-config
Improve PHPStan Configuration
2018-09-06 12:08:06 +07:00
Simon Podlipsky
e2e6d70ea8
Improve PHPStan Configuration 2018-09-05 00:09:22 +02:00
spawnia
0ddb7519bb More coding style -.-
Can this be done automatically?
2018-09-03 22:46:14 +02:00
spawnia
1ec4927f69 Add namespace to class 2018-09-03 22:13:05 +02:00
spawnia
c4fe304efe Fix coding style 2018-09-03 21:43:02 +02:00
Benedikt Franke
e7513e356a
Merge branch 'master' into patch-1 2018-09-03 21:25:36 +02:00
spawnia
326e0b4719 Add Debug::RETHROW_UNSAFE_EXCEPTIONS flag
This allows for a more granular default for which Exceptions are thrown, taking
the settings made through the ClientAware interface into account.

- Add docs
- Add a test case
- Differentiate clearly from other test
2018-09-03 21:18:04 +02:00
Vladimir Razuvaev
86503e2e35
Merge pull request #333 from mcg-web/static_analysis
Static analysis
2018-09-02 23:53:29 +07:00
Jeremiah VALERIE
a400f27dce
Ignore phpcs cache file 2018-09-02 18:26:35 +02:00
Jeremiah VALERIE
48c6f56640
Use php 7.1 build instead of 7.2 2018-09-02 18:26:35 +02:00
Jeremiah VALERIE
eed9cc7f1b
Give some love to PHP CS 2018-09-02 18:26:35 +02:00
Jeremiah VALERIE
2f63387864
Add CODING_STANDARD env var 2018-09-02 17:39:09 +02:00
Jeremiah VALERIE
be12d6f273
Sort gitignore alphabetic 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
21a7611820
fix script path 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
ff0733d013
Use .dist extension 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
7428cb8a31
Fix namespaces 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
62de403f27
Use right assert method 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
eb7ff7048d
Removed unused use for anonymous functions 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
b5b27c95b1
Also check tests folder 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
6a4c815b6d
Cleanup composer script 2018-09-02 17:19:54 +02:00
Jeremiah VALERIE
da70134c38
Fix travis globale install 2018-09-02 17:19:54 +02:00
Jeremiah VALERIE
6866779d26
Remove bin custom path 2018-09-02 17:19:53 +02:00
Jeremiah VALERIE
18954ea655
Fix typehint 2018-09-02 17:19:16 +02:00
Jeremiah VALERIE
05dbc0fb96
Fix sprintf call 2018-09-02 17:19:16 +02:00
Jeremiah VALERIE
31601d710b
Add default config file 2018-09-02 17:19:16 +02:00
Jeremiah VALERIE
d8f41e854f
Add static analysis tool 2018-09-02 17:19:15 +02:00
Vladimir Razuvaev
6c40fec35a
Merge pull request #341 from adactio/patch-1
Remove the word "simply"
2018-09-02 22:05:51 +07:00
Vladimir Razuvaev
c7d8cf4ea2
Merge pull request #348 from simPod/check-cs
Check CS in the whole project
2018-09-02 22:05:12 +07:00
Simon Podlipsky
7dbd72cebf
Check CS in the whole project
Closes #284
2018-09-02 16:46:58 +02:00
Vladimir Razuvaev
a4edb34deb
Merge pull request #343 from simPod/update-contibuting
Add it() explanation to Contribution guidelines
2018-09-02 21:36:59 +07:00
Vladimir Razuvaev
bfff27ef34
Merge pull request #347 from simPod/fix-cs-test
Fix CS in tests
2018-09-02 21:30:04 +07:00
Vladimir Razuvaev
ec43a2e01a
Merge pull request #346 from simPod/fix-cs-tests-validator
Fix CS in tests/Validator
2018-09-02 21:29:33 +07:00
Vladimir Razuvaev
d70c8e87e5
Merge pull request #345 from simPod/fix-cs-test-utils
Fix CS in tests/Utils
2018-09-02 21:27:39 +07:00
Vladimir Razuvaev
4e43a2cbcd
Merge pull request #344 from simPod/fix-cs-test-type
Fix CS in tests/Type
2018-09-02 21:26:11 +07:00
Vladimir Razuvaev
ce0272b447
Merge pull request #340 from simPod/cs-language-server
Fix CS in tests/Server
2018-09-02 21:23:47 +07:00
Vladimir Razuvaev
a7af4663b8
Merge pull request #339 from simPod/cs-language-test
Fix CS in test/Language
2018-09-02 21:22:39 +07:00
Vladimir Razuvaev
7f99bf478f
Merge pull request #338 from simPod/cs-executor-test
Fix CS in tests/Executor
2018-09-02 21:20:54 +07:00
Vladimir Razuvaev
9327e75a16
Merge pull request #342 from simPod/cs-src
Fix CS in src
2018-09-02 21:17:07 +07:00
Simon Podlipsky
d398e59ced
Fix CS in tests 2018-09-02 13:39:30 +02:00
Simon Podlipsky
737da333fb
Fix CS in tests/Utils 2018-09-02 13:10:47 +02:00
Simon Podlipsky
caa50d6db9
Fix CS in tests/Validator 2018-09-02 13:08:49 +02:00
Simon Podlipsky
b886742968
Fix CS in tests/Type 2018-09-02 12:55:40 +02:00
Simon Podlipsky
046bd02d6c
Add it() explanation to Contribution guidelines 2018-09-02 12:07:37 +02:00
Simon Podlipsky
76e1c33b68
Fix CS in src 2018-09-02 10:39:03 +02:00
Simon Podlipsky
bd8722652a
Fix CS in tests/Server 2018-09-02 10:18:45 +02:00
Jeremy Keith
33cb80335e
Remove the word "simply" 2018-09-01 22:32:02 +01:00
Simon Podlipsky
d1f49bedbd
Fix CS in test/Language 2018-09-01 22:16:02 +02:00
Simon Podlipsky
b02d25e62c
Fix CS in tests/Executor 2018-09-01 20:05:38 +02:00
Benedikt Franke
503ac4619a
Check if an exception is internal before rethrowing 2018-08-31 17:45:41 +02:00
Vladimir Razuvaev
ec54d6152b
Merge pull request #335 from simPod/test-void
Add void return typehint to test methods
2018-08-31 19:57:04 +07:00
Simon Podlipsky
715146cdd1
Add void return typehint to test methods 2018-08-31 14:41:18 +02:00
Vladimir Razuvaev
573a77ce0c
Merge pull request #334 from simPod/replace-it
Replace misleading @it by @see annotation
2018-08-31 18:51:00 +07:00
Simon Podlipsky
a30b3104a0
Replace misleading @it by @see annotation 2018-08-31 10:55:14 +02:00
Vladimir Razuvaev
cf4cefc2bc
Merge pull request #332 from simPod/cs-tests-error
Fix CS in tests/Error
2018-08-28 17:24:55 +07:00
Vladimir Razuvaev
ef93557a5d
Merge pull request #331 from simPod/cs-type
Fix CS in src/Type
2018-08-28 17:23:42 +07:00
Simon Podlipsky
3170951620
Fix CS in /tests/Error 2018-08-28 11:09:46 +02:00
Simon Podlipsky
00d547dc06
Fix CS in src/Type 2018-08-27 18:14:07 +02:00
Vladimir Razuvaev
8817e8e010 Throws descriptive error when non-type used instead of interface 2018-08-22 18:53:25 +07:00
Vladimir Razuvaev
ea6a21a13b Cleanup variables test 2018-08-22 18:40:15 +07:00
Vladimir Razuvaev
672ff0b7d6 Add nested nullable test in VariablesInAllowedPosition 2018-08-22 17:55:42 +07:00
Vladimir Razuvaev
804daa188e Add more BuildSchema tests + cleanup 2018-08-22 16:14:47 +07:00
Vladimir Razuvaev
f123e5c954 Remove redundancy in schema printer tests 2018-08-22 15:41:26 +07:00
Vladimir Razuvaev
227f0b867d Serial execution should support sync execution (+tests for the serial execution) 2018-08-22 15:10:10 +07:00
Vladimir Razuvaev
d87c1aba5c Additional printer test 2018-08-22 12:28:13 +07:00
Vladimir Razuvaev
6e7cf27579 Fixed composer lint command 2018-08-22 12:07:40 +07:00
Vladimir Razuvaev
90623f68d7 Fixed code style after recent changes 2018-08-22 12:07:21 +07:00
Vladimir Razuvaev
00caed811b Fixed SchemaPrinter (broken after the recent merge) 2018-08-22 11:45:54 +07:00
Vladimir Razuvaev
9ae8b9f26e Merge branch 'master' of https://github.com/webonyx/graphql-php
# Conflicts:
#	src/Utils/AST.php
2018-08-22 11:43:30 +07:00
Vladimir Razuvaev
7d326c44d8
Merge pull request #328 from simPod/utils-cs
Fix CS in Utils
2018-08-22 11:33:55 +07:00
Simon Podlipsky
49f34d3243
Fix CS in Utils 2018-08-22 02:51:55 +02:00
Vladimir Razuvaev
d44ec9e809 Merge branch 'master' of https://github.com/webonyx/graphql-php
# Conflicts:
#	src/Utils/AST.php
2018-08-21 22:10:50 +07:00
Vladimir Razuvaev
a3ef1be1ab
Merge pull request #323 from simPod/language-cs
Fix CS in src/Language
2018-08-21 22:00:01 +07:00
Simon Podlipsky
ad8693cb8a
Fix CS in src/Language 2018-08-20 20:26:21 +02:00
Vladimir Razuvaev
dbeaf46631
Merge pull request #324 from bkonetzny/docs-scalar-method-signature
Fixed method signature in custom scalar docs
2018-08-20 17:40:06 +07:00
Vladimir Razuvaev
0caaa1fa3b
Merge pull request #325 from Smolevich/fixes-in-benchmarks
Remove use instance of deprecated class GraphQL\Schema
2018-08-20 17:39:44 +07:00
Vladimir Razuvaev
23fce6385f
Merge pull request #319 from jrots/master
Fix for fatal php error: Call to a member function getLocation() on null, before normal error handling
2018-08-20 16:27:10 +07:00
Stanislav Shupilkin
189d474633 Remove use instance of deprecated class GraphQL\Schema 2018-08-17 19:19:33 +03:00
Bastian Konetzny
86fa8b1301
Fixed method signature in custom scalar docs 2018-08-16 09:43:15 +02:00
Vladimir Razuvaev
cc39b3ecbf
Merge pull request #321 from simPod/validator-cs
Fix CS in Validator folder
2018-08-12 01:08:05 +07:00
Vladimir Razuvaev
64c463e889 Schema: getTypeMap() should include input types defined in directive arguments 2018-08-08 15:47:42 +07:00
Simon Podlipsky
4c327a6c16
Fix CS in Validator folder 2018-08-08 10:44:05 +02:00
Vladimir Razuvaev
0d93d190f8 Updated introspection test 2018-08-08 15:11:33 +07:00
Vladimir Razuvaev
56e91d008e Update printSchema for recent SDL change to implements 2018-08-08 15:07:08 +07:00
Vladimir Razuvaev
fcb9c24bb5 Fix astFromValue to correctly handle integers and strings 2018-08-08 14:57:44 +07:00
Vladimir Razuvaev
49ec89b28f Fixed broken description printing 2018-08-08 01:46:32 +07:00
Vladimir Razuvaev
03d7d1851c NonNull test: fixed naming for consistency 2018-08-08 01:43:23 +07:00
Vladimir Razuvaev
d9aee43129 Printer: created special function to add descriptions 2018-08-08 01:28:34 +07:00
Vladimir Razuvaev
ccb9486d21 SchemaParser: additional test case 2018-08-08 01:18:59 +07:00
Vladimir Razuvaev
a19fc3d208 RFC: SDL - Separate multiple inherited interfaces with & 2018-08-08 01:11:47 +07:00
Vladimir Razuvaev
8e02fdc537 Parser: allowLegacySDLEmptyFields option + minor naming tweaks 2018-08-08 00:41:20 +07:00
Vladimir Razuvaev
ea13c9edbf Updated PHPUnit to the latest version 2018-08-08 00:13:21 +07:00
Vladimir Razuvaev
c3d69c7c2b Fixed tests for PHPUnit 7+ 2018-08-07 23:59:48 +07:00
Vladimir Razuvaev
4227404aee Added test for useful error when returning invalid value from resolveType 2018-08-07 23:33:20 +07:00
Vladimir Razuvaev
f4008f0fb2 Error formatting: display error extensions under extensions key 2018-08-07 23:33:20 +07:00
Vladimir Razuvaev
39df711eac More definition tests (for type validation) 2018-08-07 23:32:26 +07:00
Jayme Rotsaert
942d4995c5 Fix: fatal Call to a member function getLocation(), before normal error is thrown 2018-08-01 18:16:48 +02:00
Vladimir Razuvaev
392b567f23
Merge pull request #317 from simPod/upgrade-phpunit
Upgrade PHPUnit
2018-07-31 01:15:46 +07:00
Vladimir Razuvaev
48b44fbdc5
Merge pull request #318 from robbieaverill/patch-1
FIX Ensure that __toString return value is always casted as a string
2018-07-30 13:29:34 +07:00
Robbie Averill
c962afc566
FIX Ensure that __toString return value is always casted as a string
`json_encode` can return false on failure, which causes PHP errors since `__toString` must return a string.
2018-07-30 16:36:09 +12:00
Vladimir Razuvaev
2f2b54a3d6 Perf: memoize collectSubfields 2018-07-30 00:36:10 +07:00
Simon Podlipsky
24b6b736b2
Upgrade PHPUnit 2018-07-29 19:01:39 +02:00
Vladimir Razuvaev
bedbd32fb7
Merge pull request #311 from Smolevich/update-complementary
Add link to GraphQL IDE in complementary-tools.md
2018-07-20 00:29:06 +07:00
Stanislav Shupilkin
34500bd8b2 Add link to GraphQL IDE in complementary-tools.md 2018-07-19 19:24:55 +03:00
Vladimir Razuvaev
0a6eaa1173
Merge pull request #306 from simPod/cs-server
Fixed code style for the /Server
2018-07-12 21:03:10 +07:00
Vladimir Razuvaev
c02c218c71
Merge pull request #304 from simPod/update-contibuting
Add CS info into Contributing docs
2018-07-12 20:56:34 +07:00
Simon Podlipsky
1b42de0658
@return self 2018-07-10 01:34:35 +03:00
Simon Podlipsky
ec2ff0d4bf
CS /Server 2018-07-10 01:34:35 +03:00
Simon Podlipsky
54456b1160
Add CS info into Contributing docs 2018-07-10 00:51:08 +03:00
Théo FIDRY
c1a62fdb05 Allow stringeable objects to be serialized by StringType
Closes #302

(cherry picked from commit c258109)
2018-07-08 04:44:10 +07:00
Vladimir Razuvaev
e515964a73
Merge pull request #295 from simPod/refactoring
Executor: fixed code style / minor refactoring
2018-07-07 21:58:54 +07:00
Vladimir Razuvaev
15672ab66c
Merge pull request #296 from danez/patch-2
fix: Correct namespace and link for DirectiveLocation in docs
2018-07-07 21:46:38 +07:00
Vladimir Razuvaev
a59b2803be
Merge pull request #298 from vasily-kartashov/master
Update complementary-tools.md
2018-07-07 21:46:04 +07:00
Vladimir Razuvaev
dbafdf849e
Merge pull request #300 from MySchoolManagement/support-multipart-formdata
Adds support for the multipart/form-data content type
2018-07-07 21:45:21 +07:00
Iain Mckay
750ce383ec Adds support for the multipart/form-data content type 2018-07-05 08:52:29 +02:00
Vasily Kartashov
ec77f439fb
Update complementary-tools.md
Adding reference to graphql-batch-processing library
2018-06-29 13:56:14 +04:00
Daniel Tschinder
1b6fb4c29c
fix: Correct namespace and link for DirectiveLocation in docs 2018-06-28 11:58:51 +02:00
Simon Podlipsky
cd1cc911e7
Refactoring 2018-06-26 14:37:19 +02:00
Vladimir Razuvaev
ddc3a01f09
Merge pull request #294 from danez/fix-block-printing
Use multi-line block for trailing quote
2018-06-25 23:45:29 +07:00
Daniel Tschinder
6e64983f82 Use multi-line block for trailing quote
ref: fdc10bb918 (diff-ebaed8492e8d884ee4f2255e39909568)
2018-06-25 14:26:53 +02:00
Vladimir Razuvaev
a032367e26 Disabled code style check of the whole project in Travis (for now) 2018-06-23 13:50:25 +07:00
Vladimir Razuvaev
5a90e9bd64
Merge pull request #288 from simPod/phpcs
RFC: PHP CS
2018-06-23 12:32:26 +07:00
Vladimir Razuvaev
89369fd8ec
Merge pull request #291 from danez/patch-1
Add one more breaking change in 0.12
2018-06-23 11:19:13 +07:00
Vladimir Razuvaev
6195029215
Merge pull request #292 from danez/patch-2
Fix wrong length being used in validator.
2018-06-23 11:18:07 +07:00
Daniel Tschinder
8ba146071d
Fix wrong length being used in validator. 2018-06-22 16:56:19 +02:00
Daniel Tschinder
3a4f520da7
Improve example 2018-06-22 15:46:42 +02:00
Daniel Tschinder
300b58093b
Add one more breaking change in 0.12 2018-06-22 15:45:21 +02:00
Simon Podlipsky
f44ff2cfe7
Add composer script for linting 2018-06-21 10:18:34 +02:00
Simon Podlipsky
4bc3b7885c
Fix CS in Error folder 2018-06-19 19:50:12 +02:00
Simon Podlipsky
fe5c3bdee5
Introduce PHPCS 2018-06-19 19:50:12 +02:00
Vladimir Razuvaev
e31947a452
Removed scrutinizer badge 2018-06-19 19:21:15 +07:00
Vladimir Razuvaev
f0c2f12222
Merge pull request #286 from simPod/cleanup-travis
Cleanup CI
2018-06-19 17:55:29 +07:00
Simon Podlipsky
4e9ad1fd75
Cleanup CI 2018-06-16 00:20:54 +02:00
Vladimir Razuvaev
82b1dbd836
Merge pull request #282 from han8gui/patch-1
Docs: fixed invalid port number in getting started guide
2018-05-27 20:04:42 +07:00
Vladimir Razuvaev
9452655fcd
Merge pull request #281 from icerockdev/master
Removing data elements from response if the error throwing
2018-05-27 20:02:14 +07:00
Vladimir Razuvaev
01e084a9ee Added changelog for 0.12.0 2018-05-27 19:42:05 +07:00
Vladimir Razuvaev
9201df1b39 v0.12.0 2018-05-27 19:23:13 +07:00
Vladimir Razuvaev
1b22f95a86 Removed previously deprecated classes/methods 2018-05-27 19:13:32 +07:00
Vladimir Razuvaev
2580750d4c
Merge pull request #277 from danez/throwitall
Make Types throw instead of returning Utils::undefined()
2018-05-27 18:08:39 +07:00
Ben Zhu
72e8607e47
fix port bug 2018-05-09 11:18:43 +08:00
Ilya Shaydullin
06490cae8b Fix is null condition 2018-05-07 17:12:21 +07:00
Ilya Shaydullin
c7f114d90b Removing data elements from response if the error throwing 2018-05-07 17:01:18 +07:00
Daniel Tschinder
62748279d4 Cleanup imports 2018-04-24 15:38:44 +02:00
Daniel Tschinder
f140149127 Make Types throw instead of returning Utils::undefined() 2018-04-24 15:14:31 +02:00
Vladimir Razuvaev
ec0985619f
Merge pull request #273 from kikoseijo/master
Add Nuwave Lighthouse to Docs.
2018-04-19 11:52:54 +08:00
Vladimir Razuvaev
f265320c3c Merge branch 'master' of https://github.com/webonyx/graphql-php 2018-04-17 17:49:52 +08:00
Vladimir Razuvaev
63299157d8 Note in UPGRADE.md about minimum PHP version requirement in the next major release 2018-04-17 17:49:10 +08:00
Kiko Seijo
98b4355b94 Fix misspell. 2018-04-16 20:52:38 +02:00
Kiko Seijo
d6b16ba0ec Add Nuwave Lighthouse to Docs. 2018-04-16 20:49:49 +02:00
Vladimir Razuvaev
afbc327ea7
Merge pull request #271 from PowerKiKi/github-case
Use correct case for GitHub
2018-04-15 22:39:19 +08:00
Adrien Crivelli
6c55f20f43
Use correct case for GitHub 2018-04-15 17:38:16 +09:00
Vladimir Razuvaev
4418f4f975
Merge pull request #270 from adri/allow-validation-to-be-skipped
Validation > Performance > Return early if rules are empty to avoid visiting elements
2018-04-13 13:18:04 +08:00
Adrian Philipp
b3791378fa Return early if rules are empty to avoid visiting elements
This is tested already in testPassesValidationWithEmptyRules
2018-04-12 17:36:24 +02:00
Vladimir Razuvaev
568cae584b
Merge pull request #260 from simPod/drop-unsupported-php
Drop support for EOL PHP versions
2018-04-08 20:37:29 +08:00
Vladimir Razuvaev
ccbc91a97f
Merge pull request #267 from ruudk/patch-1
Fix missing sprintf in Schema.php
2018-04-06 17:14:58 +08:00
Ruud Kamphuis
66108bec84
Fix missing sprintf 2018-04-05 17:14:28 +02:00
Vladimir Razuvaev
87729589e0
Merge pull request #265 from sarukuku/master
Add content type header to curl commands on server example
2018-04-05 14:47:40 +08:00
sarukuku
9b94ac2f06 Better argument order for curl. Related to #264. 2018-04-04 16:34:05 +03:00
sarukuku
f2678b4a10 Add content type header to curl. This fixes #264. 2018-04-04 16:26:57 +03:00
Simon Podlipsky
bcf396868a
Drop support for EOL PHP versions 2018-03-30 14:48:49 +02:00
Vladimir Razuvaev
5c57d3b379 Note in UPGRADE.md about changes in input format of the Standard Server 2018-03-29 20:31:31 +08:00
Vladimir Razuvaev
b141ed2d72 Merge branch 'master' of https://github.com/webonyx/graphql-php 2018-03-29 20:30:32 +08:00
Vladimir Razuvaev
7762430bc3
Merge pull request #256 from camuthig/fix-operation-param-operation-name
Parse operation name from operationName instead of operation
2018-03-29 20:25:39 +08:00
Chris Muthig
68eb325d18
Add a unit test covering the operationName parsing 2018-03-28 13:55:59 -07:00
Vladimir Razuvaev
a1b1436f7d Remove notice about removing typeConfigDecorator from UPGRADE.md since we reverted this change and it still exists 2018-03-26 12:28:11 +08:00
Vladimir Razuvaev
2913f07050
Merge pull request #258 from ScullWM/patch-1
Fix Symfony PSR7 link
2018-03-26 12:17:24 +08:00
Vladimir Razuvaev
45baa5f185
Merge pull request #248 from danez/012
Port 0.12.3 changes from graphql-js
2018-03-26 11:43:59 +08:00
Daniel Tschinder
3e067cc60f Readd type decorator and fix lazy type loading 2018-03-06 12:53:28 +01:00
Thomas P
2a4c0a111a
Fix Symfony PSR7 link
Previous URL no longer exist and return a 404.
I've update the url to https://symfony.com/doc/current/components/psr7.html
2018-03-05 15:06:38 +01:00
Chris Muthig
8aa6dc17a5
Parse operation name from operationName instead of operation 2018-03-03 15:41:55 -08:00
Daniel Tschinder
f9a366e69a Add Fallback for DirectiveLocations 2018-02-16 16:54:06 +01:00
Daniel Tschinder
61fe317faf Update docs 2018-02-16 16:50:38 +01:00
Daniel Tschinder
5e7cf2aacb Skip test on PHP < 7 2018-02-16 16:47:11 +01:00
Daniel Tschinder
dc6e814de3 Fix orList to be the same as in JS and follow the chicago style for commas 2018-02-16 16:39:59 +01:00
Daniel Tschinder
d92a2dab21 Add suggestions for invalid values
For misspelled enums or field names, these suggestions can be helpful.

This also changes the suggestions algorithm to better detect case-sensitivity mistakes, which are common

ref: graphql/graphql-js#1153
2018-02-16 16:19:25 +01:00
Daniel Tschinder
48c5e64a08 Adding an interface to a type is now a dangerous change.
ref: graphql/graphql-js#992
2018-02-16 15:30:27 +01:00
Daniel Tschinder
d71b45d60e Find breaking directive changes
ref: graphql/graphql-js#1152
2018-02-16 00:15:19 +01:00
Daniel Tschinder
ddfeee314c Fix path argument. Enchance visit test to validate all arguments
ref: graphl/graphql-js#1149
2018-02-15 22:44:17 +01:00
Daniel Tschinder
58e0c7a178 Validate literals in a single rule with finer precision
This generalizes the "arguments of correct type" and "default values of correct type" to a single rule "values of correct type" which has been re-written to rely on a traversal rather than the utility function `isValidLiteralValue`. To reduce breaking scope, this does not remove that utility even though it's no longer used directly within the library. Since the default values rule included another validation rule that rule was renamed to a more apt "variable default value allowed".

This also includes the original errors from custom scalars in the validation error output, solving the remainder of graphql/graphql-js#821.

ref: graphql/graphql-js#1144
2018-02-15 21:29:14 +01:00
Daniel Tschinder
17520876d8 Update some validators to latest upstream version
This includes:
graphql/graphql-js#1147
graphql/graphql-js#355

This also fixes two bugs in the Schema
 - types that were not found where still added to the typeMap
 - InputObject args should not be searched for types.
2018-02-15 17:19:53 +01:00
Daniel Tschinder
949b853678 Add experimental support for parsing variable definitions in fragments
ref: graphql/graphql-js#1141
2018-02-15 13:37:45 +01:00
Daniel Tschinder
fde7df534d Robust type info
There are possibilities for errors during validation if a schema is not valid when provided to TypeInfo. Most checks for validity existed, but some did not. This asks flow to make those checks required and adds the remaining ones. Important now that we allow construction of invalid schema.

ref: graphql/graphql-js#1143
2018-02-15 12:22:29 +01:00
Daniel Tschinder
97e8a9e200 Move schema validation into separate step (type constructors)
This is the second step of moving work from type constructors to the schema validation function.

ref: graphql/graphql-js#1132
2018-02-15 12:14:08 +01:00
Daniel Tschinder
6d08c342c9 Address recent SDL spec changes
This should be the last set of spec changes for a standardized SDL

ref: graphql/graphql-js#1139
2018-02-13 18:18:50 +01:00
Daniel Tschinder
50cbfb4a44 Fix Bug in PossibleFragmentSpreads validator
ref: graphql/graphql-js@7e147a8dd6
2018-02-13 18:08:05 +01:00
Daniel Tschinder
9387548aa1 Better Predicates
Introduces new assertion functions for each kind of type mirroring the existing ones for the higher order types.

ref: graphql/graphql-js#1137
2018-02-13 18:04:03 +01:00
Daniel Tschinder
60df83f47e Preserve original coercion errors, improve error quality.
This is a fairly major refactoring of coerceValue which returns an Either so it can return a complete collection of errors. This allows originalError to be preserved for scalar coercion errors and ensures *all* errors are represented in the response.

This had a minor change to the logic in execute / subscribe to allow for buildExecutionContext to abrupt complete with multiple errors.

ref: graphql/graphql-js#1133
2018-02-13 16:51:44 +01:00
Daniel Tschinder
6d45a22ba4 Always extract extensions from the original error if possible
ref: graphql/graphql-js#2d08496720088dbe65ebea312c8526bd48fb8ee8
2018-02-13 10:42:56 +01:00
Daniel Tschinder
cf276340a4 Fix printError/locations for multiple nodes.
If a GraphQLError represents multiple nodes across files (could happen for validation across multiple parsed files) then the reported locations and printError output can be incorrect for the second node. This ensures locations are derived from nodes whenever possible to get correct location and amends comment documentation.
ref: graphql/graphql-js#1131
2018-02-13 10:42:51 +01:00
Daniel Tschinder
06c6c4bd97 Validate schema root types and directives
This moves validation out of GraphQLSchema's constructor (but not yet from other type constructors), which is responsible for root type validation and interface implementation checking.

Reduces time to construct GraphQLSchema significantly, shifting the time to validation.

This also allows for much looser rules within the schema builders, which implicitly validate while trying to adhere to flow types. Instead we use any casts to loosen the rules to defer that to validation where errors can be richer.

This also loosens the rule that a schema can only be constructed if it has a query type, moving that to validation as well. That makes flow typing slightly less nice, but allows for incremental schema building which is valuable

ref: graphql/graphql-js#1124
2018-02-13 10:42:35 +01:00
Daniel Tschinder
15374a31dd New: printError()
Lifted from / inspired by a similar change in graphql/graphql-js#722, this creates a new function `printError()` (and uses it as the implementation for `GraphQLError#toString()`) which prints location information in the context of an error.

This is moved from the syntax error where it used to be hard-coded, so it may now be used to format validation errors, value coercion errors, or any other error which may be associated with a location.

ref: graphql/graphql-js

BREAKING CHANGE: The SyntaxError message does not contain the codeframe anymore and only the message, (string) $error will print the codeframe.
2018-02-12 12:23:39 +01:00
Daniel Tschinder
f661f38215 Fix unhandled error when parsing custom scalar literals.
This factors out the enum value validation from scalar value validation and ensures the same try/catch is used in isValidLiteralValue as isValidPHPValue and protecting from errors in valueFromAST.
ref: graphql/graphql-js#1126
2018-02-11 22:31:04 +01:00
Daniel Tschinder
481cdc9a82 Include test that printSchema includes non-spec directives.
ref: graphql/graphql-js@007407deb0

Also fixes expected <-> actual in this testfile
2018-02-11 21:32:40 +01:00
Daniel Tschinder
0c984a83bb Allow constructing GraphQLError with single node.
A common case is encountering an error which blames to a single AST node. Ensure the GraphQLError constructor can handle this case.

ref: graphql/graphql-js#1123
2018-02-11 21:18:54 +01:00
Daniel Tschinder
b5106a06c9 SDL Spec changes
This adds the recent changes to the SDL proposal.

ref: graphql/graphql-js#1117
2018-02-11 21:08:53 +01:00
Daniel Tschinder
74854d55a0 Read-only AST types
ref: graphql/graphql-js#1121
2018-02-11 18:28:34 +01:00
Daniel Tschinder
ff63e07b05 Improve introspection types + new getIntrospectionQuery()
This adds a new function `getIntrospectionQuery()` which allows for some minor configuration over the resulting query text: to exclude descriptions if your use case does not require them.

ref: graphql/graphql-js#1113
2018-02-11 18:19:52 +01:00
Daniel Tschinder
6e358eb26c Fix infinite loop on invalid queries in OverlappingFields
`OverlappingFieldsCanBeMerged` would infinite loop when passed something like

```graphql
fragment A on User {
  name
  ...A
}
```

It's not `OverlappingFieldsCanBeMerged`'s responsibility to detect that validation error, but we still would ideally avoid infinite looping.

This detects that case, and pretends that the infinite spread wasn't there for the purposes of this validation step.

Also, by memoizing and checking for self-references this removes duplicate reports.

ref: graphql/graphql-js#1111
2018-02-11 17:58:48 +01:00
Daniel Tschinder
7b05673d8d Validation: improving overlapping fields quality
This improves the overlapping fields validation performance and improves error reporting quality by separating the concepts of checking fields "within" a single collection of fields from checking fields "between" two different collections of fields. This ensures for deeply overlapping fields that nested fields are not checked against each other repeatedly. Extending this concept further, fragment spreads are no longer expanded inline before looking for conflicts, instead the fields within a fragment are compared to the fields with the selection set which contained the referencing fragment spread.

e.g.

```graphql
{
  same: a
  same: b
  ...X
}

fragment X on T {
  same: c
  same: d
}
```

In the above example, the initial query body is checked "within" so `a` is compared to `b`. Also, the fragment `X` is checked "within" so `c` is compared to `d`. Because of the fragment spread, the query body and fragment `X` are checked "between" so that `a` and `b` are each compared to `c` and `d`. In this trivial example, no fewer checks are performed, but in the case where fragments are referenced multiple times, this reduces the overall number of checks (regardless of memoization).

**BREAKING**: This can change the order of fields reported when a conflict arises when fragment spreads are involved. If you are checking the precise output of errors (e.g. for unit tests), you may find existing errors change from `"a" and "c" are different fields` to `"c" and "a" are different fields`.

From a perf point of view, this is fairly minor as the memoization "PairSet" was already keeping these repeated checks from consuming time, however this will reduce the number of memoized hits because of the algorithm improvement.

From an error reporting point of view, this reports nearest-common-ancestor issues when found in a fragment that comes later in the validation process. I've added a test which fails with the existing impl and now passes, as well as changed a comment.

This also fixes an error where validation issues could be missed because of an over-eager memoization. I've also modified the `PairSet` to be aware of both forms of memoization, also represented by a previously failing test.

ref: graphql/graphql-js#386
2018-02-11 17:45:35 +01:00
Daniel Tschinder
58453c31f7 Improve validation error message when field names conflict
ref: graphql/graphql-js#363
2018-02-11 14:11:21 +01:00
Daniel Tschinder
d70a9a5e53 Update to match SDL changes
This changes the parsing grammar and validation rules to more correctly implement the current state of the GraphQL SDL proposal (facebook/graphql#90)

ref: graphql/graphl-js#1102
2018-02-11 13:27:26 +01:00
Daniel Tschinder
0c32982171 Fix KnownDirectives validator to support all directives 2018-02-11 13:15:51 +01:00
Daniel Tschinder
d6add77540 Add Docs 2018-02-10 18:46:37 +01:00
Daniel Tschinder
1da3801614 Add predicates to for built-in types
ref: graphql/graphql-js#924
2018-02-10 18:45:52 +01:00
Daniel Tschinder
c4f11a577e Allow to extend GraphQL errors with additional properties
ref: graphql/graphql-js#928
2018-02-10 18:45:38 +01:00
Daniel Tschinder
2cbccb87db Remove duplicated code from buildASTSchema and extendSchema
ref: graphql/graphql-js#1000

BREAKING CHANGE: SchemaBuilder::build() and buildAST() and constructor
removed the typedecorator, as not needed anymore as library can now resolve
union and interfaces from generated schemas.
2018-02-10 18:45:32 +01:00
Daniel Tschinder
48c33302a8 (Potentially Breaking) Allow serializing scalars as null.
This changes the check for null/undefined to a check for undefined to determine if scalar serialization was successful or not, allowing `null` to be returned from serialize() without indicating error.

This is potentially breaking for any existing custom scalar which returned `null` from `serialize()` to indicate failure. To account for this change, it should either throw an error or return `undefined`.

ref: graphql/graphql-js#1104
2018-02-10 18:45:27 +01:00
Daniel Tschinder
27ce24b5fe Fix parsing of default values in build-schema
* Generalizes building a value from an AST, since "scalar" could be misleading, and supporting variable values within custom scalar literals can be valuable.
* Replaces isNullish with isInvalid since `null` is a meaningful value as a result of literal parsing.
* Provide reasonable default version of 'parseLiteral'

ref: 714ee980aa
ref: https://github.com/graphql/graphql-js/pull/903

# Conflicts:
#	src/Utils/BuildSchema.php
#	tests/Utils/BuildSchemaTest.php
2018-02-10 18:45:23 +01:00
Daniel Tschinder
2123946dbd Add warnings for nullable changes
ref: db4cfdc31d
ref: graphql/graphql-js#1096

# Conflicts:
#	tests/Utils/FindBreakingChangesTest.php
2018-02-10 18:45:18 +01:00
Daniel Tschinder
17a8c26fc9 Simplify operationTypes validation
ref: graphql/graphql-js#999

# Conflicts:
#	src/Utils/BuildSchema.php
2018-02-10 18:45:14 +01:00
Daniel Tschinder
1fdb3da7fb Remove notes about subscription being experimental
ref: graphql/graphql-js#bf4a25a33a62280e82680518adc279e34ec816e0
2018-02-10 18:45:10 +01:00
Daniel Tschinder
98e397ce44 Add additional number lexing test
ref: graphql/graphql-js#72421378550cf51b13c6db59b8fc912591fd1a4b
2018-02-10 18:45:06 +01:00
Daniel Tschinder
4e26de3588 Support for union types when using buildSchema
* Adds support for resolving union/interface types when using a generated schema
* Move resolveType __typename checking into defaultResolveType
* Clean up existing tests and improve error messages

ref: graphql/graphql-js#947

# Conflicts:
#	src/Utils/BuildSchema.php
#	tests/Utils/BuildSchemaTest.php
2018-02-10 18:45:01 +01:00
Daniel Tschinder
7705e50e44 Fix print of block string with leading space and quotation
ref: graphql/graphql-js#1190
2018-02-10 18:44:56 +01:00
Daniel Tschinder
022c490011 RFC: Descriptions as strings
As discussed in facebook/graphql#90

This proposes replacing leading comment blocks as descriptions in the schema definition language with leading strings (typically block strings).

While I think there is some reduced ergonomics of using a string literal instead of a comment to write descriptions (unless perhaps you are accustomed to Python or Clojure), there are some compelling advantages:

* Descriptions are first-class in the AST of the schema definition language.
* Comments can remain "ignored" characters.
* No ambiguity between commented out regions and descriptions.

Specific to this reference implementation, since this is a breaking change and comment descriptions in the experimental SDL have fairly wide usage, I've left the comment description implementation intact and allow it to be enabled via an option. This should help with allowing upgrading with minimal impact on existing codebases and aid in automated transforms.

BREAKING CHANGE: This does not parse descriptions from comments by default anymore and the value of description in Nodes changed from string to StringValueNode
2018-02-10 18:44:51 +01:00
Daniel Tschinder
e65638f6f4 Improvements to printing block strings
ref: graphql/graphql-js#f9e67c403a4667372684ee8c3e82e1f0ba27031b
2018-02-10 18:44:45 +01:00
Daniel Tschinder
8747ff8954 RFC: Block String
This RFC adds a new form of `StringValue`, the multi-line string, similar to that found in Python and Scala.

A multi-line string starts and ends with a triple-quote:

```
"""This is a triple-quoted string
and it can contain multiple lines"""
```

Multi-line strings are useful for typing literal bodies of text where new lines should be interpretted literally. In fact, the only escape sequence used is `\"""` and `\` is otherwise allowed unescaped. This is beneficial when writing documentation within strings which may reference the back-slash often:

```
"""
In a multi-line string \n and C:\\ are unescaped.
"""
```

The primary value of multi-line strings are to write long-form input directly in query text, in tools like GraphiQL, and as a prerequisite to another pending RFC to allow docstring style documentation in the Schema Definition Language.

Ref: graphql/graphql-js#926
2018-02-10 18:43:26 +01:00
Daniel Tschinder
46816a7cda Uniform parsing of queries with short-hand syntax with regular queries 2018-02-09 14:33:04 +01:00
Daniel Tschinder
eb9ac66af8 Fix how TypeInfo handles inline fragments without type
ref: graphql/graphql-js#1041
2018-02-09 14:32:44 +01:00
Vladimir Razuvaev
94525c0025
Merge pull request #234 from mcg-web/some-fixes
Some fast fixes
2018-02-08 11:39:15 +07:00
Jeremiah VALERIE
944ccebc08
Fix Utils::printSafe with bool true 2018-02-02 08:18:46 +01:00
Vladimir Razuvaev
ccecc3ce1b
Merge pull request #232 from Poky85/fix-content-type-header
Fix Standard Server PSR request parsing
2018-01-22 13:21:18 +07:00
Jiri Pokorny
dca2091351 Fix content-type header match when parsing PSR request. 2018-01-21 21:02:43 +01:00
Vladimir Razuvaev
7310b25730 Switched license to MIT for full compatibility with graphql-js 2018-01-19 14:12:52 +07:00
Vladimir Razuvaev
8cd154776e Added graphql-upload to the list of complementary tools 2018-01-19 13:58:07 +07:00
Vladimir Razuvaev
4f223ba11d Fixed PSR request parsing, broken after recent changes 2018-01-13 18:08:07 +07:00
Vladimir Razuvaev
918bbff2bd
Merge branch 'master' into no-parsing 2018-01-13 17:20:00 +07:00
Vladimir Razuvaev
9944a689bf Exclude nulls from serialized AST 2018-01-13 16:25:06 +07:00
Vladimir Razuvaev
8b17953fe5 Fixed bug preventing use of parser noLocation option for serialization / deserialization 2018-01-13 15:45:09 +07:00
Vladimir Razuvaev
61f6ccfe76
Merge pull request #222 from giansalex/master
Exclude files in release
2018-01-02 22:43:31 +07:00
Giancarlos Salas
90978ea78d
update ignore files 2018-01-02 10:02:18 -05:00
Vladimir Razuvaev
7c3737609f Doc-block fix 2018-01-01 22:12:51 +07:00
Vladimir Razuvaev
b534bfbbf1 Docs: Mention PSR15-compliant middleware in complementary tools section 2018-01-01 22:07:15 +07:00
Vladimir Razuvaev
88db2f8a21
Merge pull request #224 from PowerKiKi/concise-tests
Leverage PHPUnit `setExpectedException()` instead of custom code
2018-01-01 21:33:08 +07:00
Adrien Crivelli
5cbaf973e1
Leverage PHPUnit setExpectedException() instead of custom code
Closes #219
2018-01-01 18:06:08 +09:00
Giancarlos Salas
5c6f69c254 exclude files in release 2017-12-29 21:01:42 -05:00
Adrien Crivelli
178b179db3
Drop support non pre-parsed PSR-7 request body
This revert #202 (commit 9d37f4c) because trying to parse PSR-7 request
was a mistake. The whole point of PSR-7 is to allow for interoperability
and be able to use specialized libs for body parsing (amongst many other
things). Trying to parse ourselves would be opening a can of worm if/when
other content types have to be supported. It is more correct and future
safe to require that the body is parsed before being passed to GraphQL.
2017-12-21 15:01:57 +09:00
Vladimir Razuvaev
a3f18b51f7
Merge pull request #211 from enumag/patch-2
indentation fix
2017-12-13 14:24:29 +07:00
Jáchym Toušek
f6c3fe3758
indentation 2017-12-12 13:36:20 +01:00
Vladimir Razuvaev
b97cad0f4a v0.11.5 2017-12-12 16:03:21 +07:00
Vladimir Razuvaev
bb75586aa5
Merge pull request #210 from enumag/patch-1
Allow objects with __toString in IDType
2017-12-12 16:02:09 +07:00
Jáchym Toušek
9e2c1dae87
Add test 2017-12-12 09:53:15 +01:00
Jáchym Toušek
25e341e9d9
Allow objects with __toString in IDType 2017-12-12 08:56:03 +01:00
Vladimir Razuvaev
3536280fac v0.11.4 2017-11-28 20:27:06 +07:00
Vladimir Razuvaev
9c563d5c00
Merge pull request #199 from roippi/findbreakingchanges
port findBreakingChanges
2017-11-28 20:14:36 +07:00
Vladimir Razuvaev
ea05c92723 v0.11.3 2017-11-28 12:38:56 +07:00
Vladimir Razuvaev
0af2fe79f2 StandardServer: a bit more validation for parsed json PSR-7 request (related to #202) 2017-11-28 12:28:54 +07:00
Vladimir Razuvaev
9d37f4c0d9
Merge pull request #202 from PowerKiKi/support-non-parsed-psr7
Support non pre-parsed PSR-7 request body
2017-11-28 12:01:15 +07:00
Adrien Crivelli
11c9429fab
Support non pre-parsed PSR-7 request body
Because PSR-7 specification only specify that `getParsedBody()` **may**
return the parsed body for `application/json`, we cannot assume that it
is always the case. So if the value returned parsed body is an empty array,
it means we should try to parse it ourselves (`null` would mean no body at
all according to spec).

With this modification we try to used given parsed body, but fallback on
trying to parse the body if necessary. This leave the door open to custom
implementation of parsing if needed, while making it easier to use out of
the box.
2017-11-26 19:57:32 +09:00
Ben Roberts
b18dfd670f testFindsAllDangerousChanges 2017-11-21 12:30:18 -05:00
Ben Roberts
533b8b8b5f testDetectsAdditionsToUnionType 2017-11-21 12:18:28 -05:00
Ben Roberts
e0a63ec792 testDetectsEnumValueAdditions 2017-11-21 11:53:14 -05:00
Ben Roberts
c4ae03454a testFindDangerousArgChanges 2017-11-21 11:50:11 -05:00
Ben Roberts
3c0ed787ba testDetectsAllBreakingChanges 2017-11-21 11:33:45 -05:00
Ben Roberts
dbccf9b196 testDetectsRemovalOfInterfaces 2017-11-20 14:39:06 -05:00
Ben Roberts
0bb689d340 testArgsThatMoveAwayFromNonNull 2017-11-20 14:12:26 -05:00
Ben Roberts
90f35f26a2 testDoesNotFlagArgsWithSameTypeSignature 2017-11-20 13:57:52 -05:00
Ben Roberts
0fd5abc833 testDetectsAdditionOfFieldArg 2017-11-20 13:48:21 -05:00
Ben Roberts
42d8ac07f9 testDetectsFieldArgumentTypeChange 2017-11-20 12:52:49 -05:00
Ben Roberts
4ea6cbe839 bugfix var ref 2017-11-17 17:35:33 -05:00
Ben Roberts
fc9c5e85aa testDetectsRemovalOfFieldArgument 2017-11-17 17:18:26 -05:00
Ben Roberts
cac011246e testDetectsValuesRemovedFromEnum 2017-11-17 16:24:04 -05:00
Ben Roberts
98ce1ccc69 testDetectsIfTypeWasRemovedFromUnion 2017-11-17 16:08:44 -05:00
Ben Roberts
dde2747918 testDetectsNonNullFieldAddedToInputType 2017-11-17 15:56:53 -05:00
Ben Roberts
cf4cccf4d6 testShouldDetectInputFieldChanges 2017-11-17 15:43:16 -05:00
Ben Roberts
68dbcc9ca3 testShouldDetectFieldChangesAndDeletions test 2017-11-17 14:29:47 -05:00
Ben Roberts
b2b5d6f080 findTypesThatChangedKind test 2017-11-17 13:04:01 -05:00
Ben Roberts
d9ce567cc8 findRemovedTypes test 2017-11-17 11:21:05 -05:00
Ben Roberts
4207adc098 change fns to static 2017-11-17 10:54:18 -05:00
Ben Roberts
6bdb7b7f80 improve docstrings 2017-11-16 17:44:08 -05:00
Ben Roberts
af60f1ee4d finish mechanical conversions 2017-11-16 17:42:38 -05:00
Ben Roberts
a1325eeb3f top level API functions, docstrings 2017-11-16 15:53:20 -05:00
Ben Roberts
55f6d6cf47 interfaces and enums 2017-11-16 15:25:25 -05:00
Ben Roberts
6e95b81aee dangerous changes consts 2017-11-16 14:17:21 -05:00
Ben Roberts
e649ef307a couple more functions 2017-11-16 14:15:39 -05:00
Ben Roberts
3811181f49 some functions converted over 2017-11-16 13:53:01 -05:00
Ben Roberts
7aebf2dbf7 initial porting 2017-11-15 16:12:56 -05:00
Vladimir Razuvaev
eaadae4a5b Merge pull request #187 from gabidj/patch-1
Fixed minor bug in example
2017-10-19 12:02:46 +07:00
Gabi DJ
9b449745ab Update graphql.php 2017-10-18 16:34:14 +03:00
Gabi DJ
7f54b1f7e3 removed redundant code and comments
removed redundant code and comments from PR
2017-10-18 16:32:43 +03:00
Gabi DJ
cf3ca86246 $data Unsupported operand types error fix
the operator `+=` only works like `array_merge ($left, $right) ;` if both variables are arrays
2017-10-17 16:47:26 +03:00
Vladimir Razuvaev
57f5ee3783 Fixed typo in docs, see #185 2017-10-16 23:06:21 +07:00
Vladimir Razuvaev
b17b1c3336 Merge remote-tracking branch 'origin/master' 2017-10-14 00:45:55 +07:00
Vladimir Razuvaev
1487741f37 Preserve description for custom scalars (#181) 2017-10-14 00:45:23 +07:00
Vladimir Razuvaev
0c3a657800 Merge pull request #177 from GabrielDeveloper/develop
Improve test coverage
2017-10-05 19:58:23 +07:00
Gabriel Goncalves
440d38d3bc Creating test to Language\Token 2017-10-04 12:06:25 -03:00
Vladimir Razuvaev
fb0ca607e2 Fixed a mistake in the type language documentation (#176) 2017-09-29 20:07:36 +07:00
Vladimir Razuvaev
cb40e111a3 v0.11.1 2017-09-22 23:16:34 +07:00
Vladimir Razuvaev
f7248dec76 Ability to override internal types (using types option of Schema class) #174 2017-09-22 23:08:51 +07:00
Vladimir Razuvaev
d46ad09108 v0.11.0 2017-09-20 19:50:23 +07:00
Vladimir Razuvaev
c5efd1d65b Additional checks for possible fragment spreads 2017-09-20 19:06:04 +07:00
Vladimir Razuvaev
1e34982bda Additional tests for variable coercion + use printSafeJson vs printSafe for input variables 2017-09-20 18:40:45 +07:00
Vladimir Razuvaev
a1e06b2e61 Account for query offset in files for errors 2017-09-20 18:36:19 +07:00
Vladimir Razuvaev
6050af4e67 Add support for directives applied on IDL & Schema 2017-09-20 17:43:06 +07:00
Vladimir Razuvaev
39f378ece7 Fixed type in tests: Testcase -> TestCase 2017-09-20 17:03:22 +07:00
Vladimir Razuvaev
5f5c8118c0 Fixed parseValue of StringType and IDType: (it should return null on invalid value, not throw) 2017-09-20 16:38:02 +07:00
Vladimir Razuvaev
2023b427ae Fixed failed integer test (only fails on some OSs and PHP versions) 2017-09-20 16:20:51 +07:00
Vladimir Razuvaev
c97438cd7d Merge branch 'master' of https://github.com/webonyx/graphql-php 2017-09-20 16:16:38 +07:00
Vladimir Razuvaev
537dbabe8f Merge pull request #171 from dereklavigne18/query_variable_coercion
Update query variable coercion to meet the rules outlined in the specification.
2017-09-20 15:57:29 +07:00
Derek Lavigne
d22385cc93 Update query variable coercion to meet the rules outlined in the
specification.

The framework currently coerces query variables similar to the way it
treats output values, which means it attempts to coerce the value into
the field's corresponding data type regardless of the received value.
According to items 3f and 3g in section 6.1.2
(http://facebook.github.io/graphql/#sec-Validating-Requests) of
Facebook's GraphQL specification query variables should be coerced
according to their type's input coercion rules laid out in section
3.1.1 (http://facebook.github.io/graphql/#sec-Scalars). If the value
can not be coerced into the correct type according the the input
coercion rules for the type a query error should be thrown. This
ensures that client provided query variables were of the correct format
and will be a valid format and type by the time they are passed into an
implementing resolver.

This patch fixes the above issue by updating the way query variables
are sanitized during the process of parsing the query. It directly
follows the rules for scalar input coercion laid out by the
specification and throws query errors when a value that cannot be
coerced to the correct type is given. Tests for isValidPHPValue will
also be updated to ensure that it is doing the correct type checks on
Values::isValidPHPValue for the given type and value provided. A new
test case will also be added to test Values::getVariableValues and make
sure it is also enforcing the scalar input coercion rules and throwing
errors for invalid values.
2017-09-18 12:14:09 -04:00
Vladimir Razuvaev
79ebc54538 Minor docs improvements 2017-09-05 15:29:46 +07:00
Vladimir Razuvaev
7cc863df37 v0.10.2 2017-08-30 23:33:58 +07:00
Vladimir Razuvaev
6ff427d241 Server: do not raise an error when variables are passed as empty string (#156) 2017-08-30 23:26:45 +07:00
Vladimir Razuvaev
46477c75c4 Added entry about graphql.org in the README + minor README tweaks 2017-08-22 17:24:17 +07:00
Vladimir Razuvaev
524a01a3a4 Fixed typos and mistakes in README 2017-08-22 17:11:48 +07:00
Vladimir Razuvaev
2ccc631ff3 Added CONTRIBUTING.md and updated README.md 2017-08-22 17:05:54 +07:00
Vladimir Razuvaev
1eb2ccac76 v0.10.1 2017-08-21 01:27:58 +07:00
Vladimir Razuvaev
d95fb461ee Server: fixed constructor screwed during rebasing + restored tests for the server 2017-08-21 01:18:23 +07:00
Vladimir Razuvaev
a1652468f0 Fixed minor error in docblock comment 2017-08-21 00:19:07 +07:00
402 changed files with 50909 additions and 31298 deletions

11
.gitattributes vendored
View file

@ -1,2 +1,13 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text eol=lf
/benchmarks export-ignore
/tests export-ignore
/examples export-ignore
/tools export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.travis.yml export-ignore
CONTRIBUTING.md export-ignore
mkdocs.yml export-ignore
phpbench.json export-ignore
phpunit.xml.dist export-ignore

8
.gitignore vendored
View file

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

29
.scrutinizer.yml Normal file
View file

@ -0,0 +1,29 @@
build:
nodes:
analysis:
environment:
php:
version: 7.1
cache:
disabled: false
directories:
- ~/.composer/cache
project_setup:
override: true
tests:
override:
- php-scrutinizer-run
dependencies:
override:
- composer install --ignore-platform-reqs --no-interaction
tools:
external_code_coverage:
timeout: 900
build_failure_conditions:
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
- 'issues.label("coding-style").new.exists' # No new coding style issues allowed
- 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity
- 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection

View file

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

View file

@ -1,5 +1,114 @@
# Changelog
## Unreleased
- Add schema validation: Input Objects must not contain non-nullable circular references (#492)
#### v0.13.5
- Fix coroutine executor when using with promise (#486)
#### v0.13.4
- Force int when setting max query depth (#477)
#### v0.13.3
- Reverted minor possible breaking change (#476)
#### v0.13.2
- Added QueryPlan support (#436)
- Fixed an issue with NodeList iteration over missing keys (#475)
#### v0.13.1
- Better validation of field/directive arguments
- Support for apollo client/server persisted queries
- Minor tweaks and fixes
## v0.13.0
This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details.
New features and notable changes:
- PHP version required: 7.1+
- Spec compliance: error `category` and extensions are displayed under `extensions` key when using default formatting (#389)
- New experimental executor with improved performance (#314).<br>
It is a one-line switch: `GraphQL::useExperimentalExecutor()`.<br>
<br>
**Please try it and post your feedback at https://github.com/webonyx/graphql-php/issues/397**
(as it may become the default one in future)
<br>
<br>
- Ported `extendSchema` from the reference implementation under `GraphQL\Utils\SchemaExtender` (#362)
- Added ability to override standard types via `GraphQL::overrideStandardTypes(array $types)` (#401)
- Added flag `Debug::RETHROW_UNSAFE_EXCEPTIONS` which would only rethrow app-specific exceptions (#337)
- Several classes were renamed (see [UPGRADE.md](UPGRADE.md))
- Schema Validation improvements
#### v0.12.6
- Bugfix: Call to a member function getLocation() on null (#336)
- Fixed several errors discovered by static analysis (#329)
#### v0.12.5
- Execution performance optimization for lists
#### v0.12.4
- Allow stringeable objects to be serialized by StringType (#303)
#### v0.12.3
- StandardServer: add support for the multipart/form-data content type (#300)
#### v0.12.2
- SchemaPrinter: Use multi-line block for trailing quote (#294)
#### v0.12.1
- Fixed bug in validation rule OverlappingFieldsCanBeMerged (#292)
- Added one more breaking change note in UPGRADE.md (#291)
- Spec compliance: remove `data` entry from response on top-level error (#281)
## v0.12.0
- RFC: Block String (multi-line strings via triple-quote """string""")
- GraphQL Schema SDL: Descriptions as strings (including multi-line)
- Changed minimum required PHP version to 5.6
Improvements:
- Allow extending GraphQL errors with additional properties
- Fixed parsing of default values in Schema SDL
- Handling several more cases in findBreakingChanges
- StandardServer: expect `operationName` (instead of `operation`) in input
#### v0.11.5
- Allow objects with __toString in IDType
#### v0.11.4
- findBreakingChanges utility (see #199)
#### v0.11.3
- StandardServer: Support non pre-parsed PSR-7 request body (see #202)
#### v0.11.2
- Bugfix: provide descriptions to custom scalars (see #181)
#### v0.11.1
- Ability to override internal types via `types` option of the schema (see #174).
## v0.11.0
This release brings little changes but there are two reasons why it is released as major version:
1. To follow reference implementation versions (it matches 0.11.x series of graphql-js)
2. It may break existing applications because scalar input coercion rules are stricter now:<br>
In previous versions sloppy client input could leak through with unexpected results.
For example string `"false"` accidentally sent in variables was converted to boolean `true`
and passed to field arguments. In the new version, such input will produce an error
(which is a spec-compliant behavior).
Improvements:
- Stricter input coercion (see #171)
- Types built with `BuildSchema` now have reference to AST node with corresponding AST definition (in $astNode property)
- Account for query offset for error locations (e.g. when query is stored in `.graphql` file)
#### v0.10.2
- StandardServer improvement: do not raise an error when variables are passed as empty string (see #156)
#### v0.10.1
- Fixed infinite loop in the server (see #153)
## v0.10.0
This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details.
@ -88,4 +197,4 @@ Improvements:
- New docs and examples
## Older versions
Look at [Github Releases Page](https://github.com/webonyx/graphql-php/releases).
Look at [GitHub Releases Page](https://github.com/webonyx/graphql-php/releases).

55
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,55 @@
# Contributing to GraphQL PHP
## Workflow
If your contribution requires significant or breaking changes, or if you plan to propose a major new feature,
we recommend you to create an issue on the [GitHub](https://github.com/webonyx/graphql-php/issues) with
a brief proposal and discuss it with us first.
For smaller contributions just use this workflow:
* Fork the project.
* Add your features and or bug fixes.
* Add tests. Tests are important for us.
* Check your changes using `composer check-all`.
* Add an entry to the [Changelog's Unreleases section](CHANGELOG.md#unreleased).
* Send a pull request.
## Setup the Development Environment
First, copy the URL of your fork and `git clone` it to your local machine.
```sh
cd graphql-php
composer install
```
## Running tests
```sh
./vendor/bin/phpunit
```
Some tests have annotation `@see it('<description>')`. It is used for reference to same tests in [graphql-js implementation](https://github.com/graphql/graphql-js) with the same description.
## Coding Standard
The coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard).
Run the inspections:
```sh
./vendor/bin/phpcs
```
Apply automatic code style fixes:
```sh
./vendor/bin/phpcbf
```
## Static analysis
Based on [PHPStan](https://github.com/phpstan/phpstan).
```sh
./vendor/bin/phpstan analyse --ansi --memory-limit 256M
```
## Running benchmarks
Benchmarks are run via [PHPBench](https://github.com/phpbench/phpbench).
```sh
./vendor/bin/phpbench run .
```

41
LICENSE
View file

@ -1,28 +1,21 @@
Copyright (c) 2015, webonyx
All rights reserved.
MIT License
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Copyright (c) 2015-present, Webonyx, LLC.
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of graphql-php nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

603
README.md
View file

@ -1,589 +1,52 @@
# graphql-php
This is a PHP port of GraphQL reference implementation based on the [specification](https://github.com/facebook/graphql)
and the [reference implementation in JavaScript](https://github.com/graphql/graphql-js).
This implementation will follow JavaScript version as close as possible until GraphQL itself stabilizes.
**Current status**: version 0.4+ supports all features described by specification.
[![Build Status](https://travis-ci.org/webonyx/graphql-php.svg?branch=master)](https://travis-ci.org/webonyx/graphql-php)
[![Coverage Status](https://coveralls.io/repos/github/webonyx/graphql-php/badge.svg)](https://coveralls.io/github/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)
[![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)
Work is in progress on new [Documentation site](http://webonyx.github.io/graphql-php/). It already
contains more information than this Readme, so try it first.
This is a PHP implementation of the GraphQL [specification](https://github.com/facebook/graphql)
based on the [reference implementation in JavaScript](https://github.com/graphql/graphql-js).
## Table of Contents
- [Overview](#overview)
- [Installation](#installing-graphql-php)
- [Learn by example](#learn-by-example)
- [Type System](#type-system)
- [Internal Types](#internal-types)
- [Enums](#enums)
- [Interfaces](#interfaces)
- [Objects](#objects)
- Unions (TODOC)
- [Fields](#fields)
- [Schema definition](#schema)
- [Query Resolution and Data Fetching](#query-resolution)
- [HTTP endpoint example](#http-endpoint)
- [More Examples](#more-examples)
- [Complementary Tools](#complementary-tools)
## Overview
GraphQL is intended to be a replacement for REST APIs. [Read more](http://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html) about rationale behind it.
Example usage:
```php
$result = GraphQL::execute(
StarWarsSchema::build(),
'query HeroNameAndFriendsQuery {
hero {
id
name
friends {
name
}
}
}'
)
## Installation
Via composer:
```
composer require webonyx/graphql-php
```
Result returned:
```php
[
'hero' => [
'id' => '2001',
'name' => 'R2-D2',
'friends' => [
['name' => 'Luke Skywalker'],
['name' => 'Han Solo'],
['name' => 'Leia Organa'],
]
]
]
```
(see also [schema definition](https://github.com/webonyx/graphql-php/blob/master/tests/StarWarsSchema.php#L22) for type system of this example).
## Documentation
Full documentation is available on the [Documentation site](https://webonyx.github.io/graphql-php/) as well
as in the [docs](docs/) folder of the distribution.
This PHP implementation is a thin wrapper around your existing data layer and business logic. It doesn't dictate how these layers are implemented or which storage engines are used. Instead it provides tools for creating API for your existing app. These tools include:
- Type system
- Schema validation and introspection
- Ability to parse and execute GraphQL queries against type system
If you don't know what GraphQL is, visit this [official website](http://graphql.org)
by the Facebook engineering team.
Actual data fetching has to be implemented on the user land.
## Examples
There are several ready examples in the [examples](examples/) folder of the distribution with specific
README file per example.
Check out single-file [hello world](https://gist.github.com/leocavalcante/9e61ca6065130e37737f24892d81fa40) example for quick introduction.
## Contributors
## Installing graphql-php
```
$> curl -sS https://getcomposer.org/installer | php
$> php composer.phar require webonyx/graphql-php="^0.9"
```
This project exists thanks to [all the people](https://github.com/webonyx/graphql-php/graphs/contributors) who contribute. [[Contribute](CONTRIBUTING.md)].
If you are upgrading, see [upgrade instructions](UPGRADE.md)
## Backers
## Requirements
PHP >=5.4
<a href="https://opencollective.com/webonyx-graphql-php#backers" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/backers.svg?width=890"></a>
## Learn by example
It is often easier to start with full-featured example and then get back to documentation
for your own work.
## Sponsors
Check out full-featured [example of GraphQL API](https://github.com/webonyx/graphql-php/tree/master/examples/01-blog).
Follow instructions and try it yourself in ~10 minutes.
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)]
## Getting Started
First, make sure to read [Getting Started](https://github.com/facebook/graphql#getting-started) section of GraphQL documentation.
Examples below implement the type system described in this document.
<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>
### Type System
To start using GraphQL you are expected to implement a Type system.
## License
GraphQL PHP provides several *kinds* of types to build a hierarchical type system:
`scalar`, `enum`, `object`, `interface`, `union`, `listOf`, `nonNull`.
#### Internal types
Only several `scalar` types are implemented out of the box:
`ID`, `String`, `Int`, `Float`, `Boolean`
As well as two internal modifier types: `ListOf` and `NonNull`.
All internal types are exposed as static methods of `GraphQL\Type\Definition\Type` class:
```php
use GraphQL\Type\Definition\Type;
// Internal Scalar types:
Type::string(); // String type
Type::int(); // Int type
Type::float(); // Float type
Type::boolean(); // Boolean type
Type::id(); // ID type
// Internal wrapping types:
Type::nonNull(Type::string()) // String! type
Type::listOf(Type::string()) // String[] type
```
Other types must be implemented by your application. Most often you will work with `enum`, `object`, `interface` and `union` type *kinds* to build a type system.
#### Enums
Enum types represent a set of allowed values for an object field. Let's define `enum` type describing the set of episodes of original Star Wars trilogy:
```php
use GraphQL\Type\Definition\EnumType;
/**
* The original trilogy consists of three movies.
*
* This implements the following type system shorthand:
* enum Episode { NEWHOPE, EMPIRE, JEDI }
*/
$episodeEnum = new EnumType([
'name' => 'Episode',
'description' => 'One of the films in the Star Wars Trilogy',
'values' => [
'NEWHOPE' => [
'value' => 4,
'description' => 'Released in 1977.'
],
'EMPIRE' => [
'value' => 5,
'description' => 'Released in 1980.'
],
'JEDI' => [
'value' => 6,
'description' => 'Released in 1983.'
],
]
]);
```
#### Interfaces
Next, let's define a `Character` interface, describing characters of original Star Wars trilogy:
```php
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\Type;
// Implementor types (will be defined in next examples):
$humanType = null;
$droidType = null;
/**
* Characters in the Star Wars trilogy are either humans or droids.
*
* This implements the following type system shorthand:
* interface Character {
* id: String!
* name: String
* friends: [Character]
* appearsIn: [Episode]
* }
*/
$characterInterface = new InterfaceType([
'name' => 'Character',
'description' => 'A character in the Star Wars Trilogy',
'fields' => [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the character.',
],
'name' => [
'type' => Type::string(),
'description' => 'The name of the character.'
],
'friends' => [
'type' => function () use (&$characterInterface) {
return Type::listOf($characterInterface);
},
'description' => 'The friends of the character.',
],
'appearsIn' => [
'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.'
]
],
'resolveType' => function ($obj) use (&$humanType, &$droidType) {
$humans = StarWarsData::humans();
$droids = StarWarsData::droids();
if (isset($humans[$obj['id']])) {
return $humanType;
}
if (isset($droids[$obj['id']])) {
return $droidType;
}
return null;
},
]);
```
As you can see `type` may be optionally defined as `callback` that returns actual type at runtime. (see [Fields](#fields) section for details)
In this example field `friends` represents a list of `characterInterface`. Since at the moment of type definition `characterInterface` is still not defined, we pass in `closure` that will return this type at runtime.
**Interface definition options:**
Option | Type | Notes
------ | ---- | -----
name | `string` | Required. Unique name of this interface type within Schema
fields | `array` | Required. List of fields required to be defined by interface implementors. See [Fields](#fields) section for available options.
description | `string` | Textual description of this interface for clients
resolveType | `callback($value, $context, ResolveInfo $info) => objectType` | Any `callable` that receives data from data layer of your application and returns concrete interface implementor for that data.
**Notes**:
1. If `resolveType` option is omitted, GraphQL PHP will loop through all interface implementers and use their `isTypeOf()` method to pick the first suitable one. This is obviously less efficient than single `resolveType` call. So it is recommended to define `resolveType` when possible.
2. Interface types do not participate in data fetching. They just resolve actual `object` type which will be asked for data when GraphQL query is executed.
#### Objects
Now let's define `Human` type that implements `CharacterInterface` from example above:
```php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
/**
* We define our human type, which implements the character interface.
*
* This implements the following type system shorthand:
* type Human : Character {
* id: String!
* name: String
* friends: [Character]
* appearsIn: [Episode]
* }
*/
$humanType = new ObjectType([
'name' => 'Human',
'description' => 'A humanoid creature in the Star Wars universe.',
'fields' => [
'id' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The id of the human.',
],
'name' => [
'type' => Type::string(),
'description' => 'The name of the human.',
],
'friends' => [
'type' => Type::listOf($characterInterface),
'description' => 'The friends of the human',
'resolve' => function ($human) {
return StarWarsData::getFriends($human);
},
],
'appearsIn' => [
'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.'
],
'homePlanet' => [
'type' => Type::string(),
'description' => 'The home planet of the human, or null if unknown.'
],
],
'interfaces' => [$characterInterface]
]);
```
**Object definition options**
Option | Type | Notes
------ | ---- | -----
name | `string` | Required. Unique name of this object type within Schema
fields | `array` | Required. List of fields describing object properties. See [Fields](#fields) section for available options.
description | `string` | Textual description of this type for clients
interfaces | `array` or `callback() => ObjectType[]` | List of interfaces implemented by this type (or callback returning list of interfaces)
isTypeOf | `callback($value, $context, GraphQL\Type\Definition\ResolveInfo $info)` | Callback that takes `$value` provided by your data layer and returns true if that `$value` qualifies for this type
**Notes:**
1. Both `object` types and `interface` types define set of fields which can have their own types. That's how type composition is implemented.
2. Object types are responsible for data fetching. Each of their fields may have optional `resolve` callback option. This callback takes `$value` that corresponds to instance of this type and returns `data` accepted by type of given field.
If `resolve` option is not set, GraphQL will try to get `data` from `$value[$fieldName]`.
3. `resolve` callback is a place where you can use your existing data fetching logic. `$context` is defined by your application on the top level of query execution (useful for storing current user, environment details, etc)
4. Other `ObjectType` mentioned in examples is `Droid`. Check out tests for this type: https://github.com/webonyx/graphql-php/blob/master/tests/StarWarsSchema.php
#### Unions
TODOC
#### Fields
Fields are parts of [Object](#objects) and [Interface](#interfaces) type definitions.
Allowed Field definition options:
Option | Type | Notes
------ | ---- | -----
name | `string` | Required. Name of the field. If not set - GraphQL will look use `key` of fields array on type definition.
type | `Type` or `callback() => Type` | Required. One of internal or custom types. Alternatively - callback that returns `type`.
args | `array` | Array of possible type arguments. Each entry is expected to be an array with following keys: **name** (`string`), **type** (`Type` or `callback() => Type`), **defaultValue** (`any`)
resolve | `callback($value, $args, $context, ResolveInfo $info) => $fieldValue` | Function that receives `$value` of parent type and returns value for this field. `$context` is also defined by your application in the root call to `GraphQL::execute()`
description | `string` | Field description for clients
deprecationReason | `string` | Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced)
### Schema
After all of your types are defined, you must define schema. Schema consists of two special root-level types: `Query` and `Mutation`
`Query` type is a surface of your *read* API. `Mutation` type exposes *write* API by declaring all possible mutations in your app.
Example schema:
```php
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Schema;
/**
* This is the type that will be the root of our query, and the
* entry point into our schema. It gives us the ability to fetch
* objects by their IDs, as well as to fetch the undisputed hero
* of the Star Wars trilogy, R2-D2, directly.
*
* This implements the following type system shorthand:
* type Query {
* hero(episode: Episode): Character
* human(id: String!): Human
* droid(id: String!): Droid
* }
*
*/
$queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'hero' => [
'type' => $characterInterface,
'args' => [
'episode' => [
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.',
'type' => $episodeEnum
]
],
'resolve' => function ($root, $args) {
return StarWarsData::getHero(isset($args['episode']) ? $args['episode'] : null);
},
],
'human' => [
'type' => $humanType,
'args' => [
'id' => [
'name' => 'id',
'description' => 'id of the human',
'type' => Type::nonNull(Type::string())
]
],
'resolve' => function ($root, $args) {
$humans = StarWarsData::humans();
return isset($humans[$args['id']]) ? $humans[$args['id']] : null;
}
],
'droid' => [
'type' => $droidType,
'args' => [
'id' => [
'name' => 'id',
'description' => 'id of the droid',
'type' => Type::nonNull(Type::string())
]
],
'resolve' => function ($root, $args) {
$droids = StarWarsData::droids();
return isset($droids[$args['id']]) ? $droids[$args['id']] : null;
}
]
]
]);
// TODOC
$mutationType = null;
$schema = new Schema([
'query' => $queryType,
'mutation' => $mutationType,
// We need to pass the types that implement interfaces in case the types are only created on demand.
// This ensures that they are available during query validation phase for interfaces.
'types' => [
$humanType,
$droidType
]
]);
```
**Notes:**
1. `Query` is a regular `object` type.
2. Fields of this type represent all possible root-level queries to your API.
3. Fields can have `args`, so that your queries could be dynamic (see [Fields](#fields) section).
### Query Resolution
Resolution is a cascading process that starts from root `Query` type.
In our example `Query` type exposes field `human` that expects `id` argument. Say we receive following GraphQL query that requests data for Luke Skywalker:
```
query FetchLukeQuery {
human(id: "1000") {
name
friends {
name
}
}
}
```
And that's how our data for Luke looks like (in some internal storage):
```
$lukeData = [
'id' => '1000',
'name' => 'Luke Skywalker',
'friends' => ['1002', '1003', '2000', '2001'],
'appearsIn' => [4, 5, 6],
'homePlanet' => 'Tatooine',
]
```
What happens:
1. GraphQL query is parsed and validated against schema (it happens in `GraphQL\GraphQL::execute()` method)
2. GraphQL executor detects that field `human` of `Human` type is requested at root `Query` level
3. It calls `resolve(null, ['id' => 1000])` on this field (note first argument is null at the root level)
4. `resolve` callback of `human` field fetches our data by id and returns it
5. Since field `human` is expected to return type `Human` GraphQL traverses all requested fields of type `Human` and matches them against `$lukeData`
6. Requested field `name` on `Human` type does not provide any `resolve` callback, so GraphQL simply resolves it as `$lukeData['name']`
7. Requested field `friend` has `resolve` callback, so it is called: `resolve($lukeData, /*args*/ [], ResolveInfo $info)`
8. Callback fetches data for all `$lukeData['friends']` and returns `[$friend1002, $friend1003, ...]` where each entry contains array with same structure as `$lukeData`
9. GraphQL executor repeats these steps until all requested leaf fields are reached
10. Final result is composed and returned:
```
[
'human' => [
'name' => 'Luke Skywalker',
'friends' => [
['name' => 'Han Solo'],
['name' => 'Leia Organa'],
['name' => 'C-3PO'],
['name' => 'R2-D2'],
]
]
]
```
### HTTP endpoint
Specification for GraphQL HTTP endpoint is still under development.
But you can use following naive example to build your own custom HTTP endpoint that is ready to accept GraphQL queries:
```php
use GraphQL\GraphQL;
use \Exception;
if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] === 'application/json') {
$rawBody = file_get_contents('php://input');
$data = json_decode($rawBody ?: '', true);
} else {
$data = $_POST;
}
$requestString = isset($data['query']) ? $data['query'] : null;
$operationName = isset($data['operation']) ? $data['operation'] : null;
$variableValues = isset($data['variables']) ? $data['variables'] : null;
try {
// Define your schema:
$schema = MyApp\Schema::build();
$result = GraphQL::execute(
$schema,
$requestString,
/* $rootValue */ null,
/* $context */ null, // A custom context that can be used to pass current User object etc to all resolvers.
$variableValues,
$operationName
);
} catch (Exception $exception) {
$result = [
'errors' => [
['message' => $exception->getMessage()]
]
];
}
header('Content-Type: application/json');
echo json_encode($result);
```
### Security
#### Query Complexity Analysis
This is a PHP port of [Query Complexity Analysis](http://sangria-graphql.org/learn/#query-complexity-analysis) in Sangria implementation.
Introspection query with description max complexity is **109**.
This document validator rule is disabled by default. Here an example to enabled it:
```php
use GraphQL\GraphQL;
/** @var \GraphQL\Validator\Rules\QueryComplexity $queryComplexity */
$queryComplexity = DocumentValidator::getRule('QueryComplexity');
$queryComplexity->setMaxQueryComplexity($maxQueryComplexity = 110);
GraphQL::execute(/*...*/);
```
#### Limiting Query Depth
This is a PHP port of [Limiting Query Depth](http://sangria-graphql.org/learn/#limiting-query-depth) in Sangria implementation.
Introspection query with description max depth is **7**.
This document validator rule is disabled by default. Here an example to enabled it:
```php
use GraphQL\GraphQL;
/** @var \GraphQL\Validator\Rules\QueryDepth $queryDepth */
$queryDepth = DocumentValidator::getRule('QueryDepth');
$queryDepth->setMaxQueryDepth($maxQueryDepth = 10);
GraphQL::execute(/*...*/);
```
#### Disabling Introspection
This is a PHP port of [graphql-disable-introspection](https://github.com/helfer/graphql-disable-introspection).
The rule prohibits queries that contain `__type` or `__schema` fields.
This document validator rule is disabled by default. Here an example to enable it:
```php
use GraphQL\GraphQL;
use GraphQL\Validator\Rules\DisableIntrospection;
/** @var \GraphQL\Validator\Rules\DisableIntrospection $disableIntrospection */
$disableIntrospection = DocumentValidator::getRule('DisableIntrospection');
$disableIntrospection->setEnabled(DisableIntrospection::ENABLED);
GraphQL::execute(/*...*/);
```
### More Examples
Make sure to check [tests](https://github.com/webonyx/graphql-php/tree/master/tests) for more usage examples.
### Complementary Tools
- [Integration with Relay](https://github.com/ivome/graphql-relay-php)
- [Use GraphQL with Laravel 5](https://github.com/Folkloreatelier/laravel-graphql)
- [Relay helpers for laravel-graphql](https://github.com/nuwave/laravel-graphql-relay)
- [GraphQL and Relay with Symfony2](https://github.com/overblog/GraphQLBundle)
Also check [Awesome GraphQL](https://github.com/chentsulin/awesome-graphql) for full picture of GraphQL ecosystem.
See [LICENSE](LICENSE).

View file

@ -1,3 +1,222 @@
## Master
### Breaking (major): dropped deprecations
- dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`.
## Upgrade v0.12.x > v0.13.x
### Breaking (major): minimum supported version of PHP
New minimum required version of PHP is **7.1+**
### Breaking (major): default errors formatting changed according to spec
**Category** and extensions assigned to errors are shown under `extensions` key
```php
$e = new Error(
'msg',
null,
null,
null,
null,
null,
['foo' => 'bar']
);
```
Formatting before the change:
```
'errors' => [
[
'message' => 'msg',
'category' => 'graphql',
'foo' => 'bar'
]
]
```
After the change:
```
'errors' => [
[
'message' => 'msg',
'extensions' => [
'category' => 'graphql',
'foo' => 'bar',
],
]
]
```
Note: if error extensions contain `category` key - it has a priority over default category.
You can always switch to [custom error formatting](https://webonyx.github.io/graphql-php/error-handling/#custom-error-handling-and-formatting) to revert to the old format.
### Try it: Experimental Executor with improved performance
It is disabled by default. To enable it, do the following
```php
<?php
use GraphQL\Executor\Executor;
use GraphQL\Experimental\Executor\CoroutineExecutor;
Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
```
**Please post your feedback about new executor at https://github.com/webonyx/graphql-php/issues/397
Especially if you had issues (because it may become the default in one of the next releases)**
### Breaking: multiple interfaces separated with & in SDL
Before the change:
```graphql
type Foo implements Bar, Baz { field: Type }
```
After the change:
```graphql
type Foo implements Bar & Baz { field: Type }
```
To allow for an adaptive migration, use `allowLegacySDLImplementsInterfaces` option of parser:
```php
Parser::parse($source, [ 'allowLegacySDLImplementsInterfaces' => true])
```
### Breaking: several classes renamed
- `AbstractValidationRule` renamed to `ValidationRule` (NS `GraphQL\Validator\Rules`)
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
- `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`)
### Breaking: new constructors
`GraphQL\Type\Definition\ResolveInfo` now takes 10 arguments instead of one array.
## Upgrade v0.11.x > v0.12.x
### Breaking: Minimum supported version is PHP5.6
Dropped support for PHP 5.5. This release still supports PHP 5.6 and PHP 7.0
**But the next major release will require PHP7.1+**
### Breaking: Custom scalar types need to throw on invalid value
As null might be a valid value custom types need to throw an
Exception inside `parseLiteral()`, `parseValue()` and `serialize()`.
Returning null from any of these methods will now be treated as valid result.
### Breaking: Custom scalar types parseLiteral() declaration changed
A new parameter was added to `parseLiteral()`, which also needs to be added to any custom scalar type extending from `ScalarType`
Before:
```php
class MyType extends ScalarType {
...
public function parseLiteral($valueNode) {
//custom implementation
}
}
```
After:
```php
class MyType extends ScalarType {
...
public function parseLiteral($valueNode, array $variables = null) {
//custom implementation
}
}
```
### Breaking: Descriptions in comments are not used as descriptions by default anymore
Descriptions now need to be inside Strings or BlockStrings in order to be picked up as
description. If you want to keep the old behaviour you can supply the option `commentDescriptions`
to BuildSchema::buildAST(), BuildSchema::build() or Printer::doPrint().
Here is the official way now to define descriptions in the graphQL language:
Old:
```graphql
# Description
type Dog {
...
}
```
New:
```graphql
"Description"
type Dog {
...
}
"""
Long Description
"""
type Dog {
...
}
```
### Breaking: Cached AST of version 0.11.x is not compatible with 0.12.x.
That's because description in AST is now a separate node, not just a string.
Make sure to renew caches.
### Breaking: Most of previously deprecated classes and methods were removed
See deprecation notices for previous versions in details.
### Breaking: Standard server expects `operationName` vs `operation` for multi-op queries
Before the change:
```json
{
"queryId": "persisted-query-id",
"operation": "QueryFromPersistedDocument",
"variables": {}
}
```
After the change:
```json
{
"queryId": "persisted-query-id",
"operationName": "QueryFromPersistedDocument",
"variables": {}
}
```
This naming is aligned with graphql-express version.
### Possibly Breaking: AST to array serialization excludes nulls
Most users won't be affected. It *may* affect you only if you do your own manipulations
with exported AST.
Example of json-serialized AST before the change:
```json
{
"kind": "Field",
"loc": null,
"name": {
"kind": "Name",
"loc": null,
"value": "id"
},
"alias": null,
"arguments": [],
"directives": [],
"selectionSet": null
}
```
After the change:
```json
{
"kind": "Field",
"name": {
"kind": "Name",
"value": "id"
},
"arguments": [],
"directives": []
}
```
## Upgrade v0.8.x, v0.9.x > v0.10.x
### Breaking: changed minimum PHP version from 5.4 to 5.5

View file

@ -1,11 +1,11 @@
<?php
namespace GraphQL\Benchmarks;
use GraphQL\GraphQL;
use GraphQL\Schema;
use GraphQL\Benchmarks\Utils\QueryGenerator;
use GraphQL\Benchmarks\Utils\SchemaGenerator;
use GraphQL\Type\LazyResolution;
use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Type\SchemaConfig;
/**
* @BeforeMethods({"setUp"})
@ -16,18 +16,12 @@ use GraphQL\Type\LazyResolution;
*/
class HugeSchemaBench
{
/**
* @var SchemaGenerator
*/
/** @var SchemaGenerator */
private $schemaBuilder;
private $schema;
private $lazySchema;
/**
* @var string
*/
/** @var string */
private $smallQuery;
public function setUp()
@ -36,12 +30,12 @@ class HugeSchemaBench
'totalTypes' => 600,
'fieldsPerType' => 8,
'listFieldsPerType' => 2,
'nestingLevel' => 10
'nestingLevel' => 10,
]);
$this->schema = $this->schemaBuilder->buildSchema();
$queryBuilder = new QueryGenerator($this->schema, 0.05);
$queryBuilder = new QueryGenerator($this->schema, 0.05);
$this->smallQuery = $queryBuilder->buildQuery();
}
@ -49,8 +43,7 @@ class HugeSchemaBench
{
$this->schemaBuilder
->buildSchema()
->getTypeMap()
;
->getTypeMap();
}
public function benchSchemaLazy()
@ -60,21 +53,21 @@ class HugeSchemaBench
public function benchSmallQuery()
{
$result = GraphQL::execute($this->schema, $this->smallQuery);
$result = GraphQL::executeQuery($this->schema, $this->smallQuery);
}
public function benchSmallQueryLazy()
{
$schema = $this->createLazySchema();
$result = GraphQL::execute($schema, $this->smallQuery);
$result = GraphQL::executeQuery($schema, $this->smallQuery);
}
private function createLazySchema()
{
return new Schema(
\GraphQL\Type\SchemaConfig::create()
SchemaConfig::create()
->setQuery($this->schemaBuilder->buildQueryType())
->setTypeLoader(function($name) {
->setTypeLoader(function ($name) {
return $this->schemaBuilder->loadType($name);
})
);

View file

@ -1,5 +1,6 @@
<?php
namespace GraphQL\Benchmarks;
use GraphQL\GraphQL;
use GraphQL\Tests\StarWarsSchema;
use GraphQL\Type\Introspection;
@ -35,7 +36,7 @@ class StarWarsBench
}
';
GraphQL::execute(
GraphQL::executeQuery(
StarWarsSchema::build(),
$q
);
@ -57,7 +58,7 @@ class StarWarsBench
}
}
';
GraphQL::execute(
GraphQL::executeQuery(
StarWarsSchema::build(),
$q
);
@ -81,7 +82,7 @@ class StarWarsBench
}
';
GraphQL::execute(
GraphQL::executeQuery(
StarWarsSchema::build(),
$q
);
@ -89,7 +90,7 @@ class StarWarsBench
public function benchStarWarsIntrospectionQuery()
{
GraphQL::execute(
GraphQL::executeQuery(
StarWarsSchema::build(),
$this->introQuery
);

View file

@ -7,12 +7,15 @@ use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\Printer;
use GraphQL\Schema;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\WrappingType;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use function count;
use function max;
use function round;
class QueryGenerator
{
@ -30,12 +33,14 @@ class QueryGenerator
$totalFields = 0;
foreach ($schema->getTypeMap() as $type) {
if ($type instanceof ObjectType) {
$totalFields += count($type->getFields());
if (! ($type instanceof ObjectType)) {
continue;
}
$totalFields += count($type->getFields());
}
$this->maxLeafFields = max(1, round($totalFields * $percentOfLeafFields));
$this->maxLeafFields = max(1, round($totalFields * $percentOfLeafFields));
$this->currentLeafFields = 0;
}
@ -44,13 +49,12 @@ class QueryGenerator
$qtype = $this->schema->getQueryType();
$ast = new DocumentNode([
'definitions' => [
new OperationDefinitionNode([
'name' => new NameNode(['value' => 'TestQuery']),
'operation' => 'query',
'selectionSet' => $this->buildSelectionSet($qtype->getFields())
])
]
'definitions' => [new OperationDefinitionNode([
'name' => new NameNode(['value' => 'TestQuery']),
'operation' => 'query',
'selectionSet' => $this->buildSelectionSet($qtype->getFields()),
]),
],
]);
return Printer::doPrint($ast);
@ -58,12 +62,13 @@ class QueryGenerator
/**
* @param FieldDefinition[] $fields
*
* @return SelectionSetNode
*/
public function buildSelectionSet($fields)
{
$selections[] = new FieldNode([
'name' => new NameNode(['value' => '__typename'])
'name' => new NameNode(['value' => '__typename']),
]);
$this->currentLeafFields++;
@ -87,12 +92,12 @@ class QueryGenerator
$selections[] = new FieldNode([
'name' => new NameNode(['value' => $field->name]),
'selectionSet' => $selectionSet
'selectionSet' => $selectionSet,
]);
}
$selectionSet = new SelectionSetNode([
'selections' => $selections
'selections' => $selections,
]);
return $selectionSet;

View file

@ -1,11 +1,11 @@
<?php
namespace GraphQL\Benchmarks\Utils;
use GraphQL\Schema;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
class SchemaGenerator
{
@ -152,7 +152,7 @@ class SchemaGenerator
];
}
public function resolveField($value, $args, $context, $resolveInfo)
public function resolveField($objectValue, $args, $context, $resolveInfo)
{
return $resolveInfo->fieldName . '-value';
}

View file

@ -2,25 +2,33 @@
"name": "webonyx/graphql-php",
"description": "A PHP port of GraphQL reference implementation",
"type": "library",
"license": "BSD",
"license": "MIT",
"homepage": "https://github.com/webonyx/graphql-php",
"keywords": [
"graphql",
"API"
],
"require": {
"php": ">=5.5,<8.0-DEV",
"php": "^7.1||^8.0",
"ext-json": "*",
"ext-mbstring": "*"
},
"require-dev": {
"phpunit/phpunit": "^4.8",
"psr/http-message": "^1.0"
"doctrine/coding-standard": "^6.0",
"phpbench/phpbench": "^0.14.0",
"phpstan/phpstan": "^0.11.12",
"phpstan/phpstan-phpunit": "^0.11.2",
"phpstan/phpstan-strict-rules": "^0.11.1",
"phpunit/phpcov": "^5.0",
"phpunit/phpunit": "^7.2",
"psr/http-message": "^1.0",
"react/promise": "2.*"
},
"config": {
"bin-dir": "bin"
"preferred-install": "dist",
"sort-packages": true
},
"autoload": {
"files": ["src/deprecated.php"],
"psr-4": {
"GraphQL\\": "src/"
}
@ -35,5 +43,14 @@
"suggest": {
"react/promise": "To leverage async resolving on React PHP platform",
"psr/http-message": "To use standard GraphQL server"
},
"scripts": {
"api-docs": "php tools/gendocs.php",
"bench": "phpbench run .",
"test": "phpunit",
"lint" : "phpcs",
"fix-style" : "phpcbf",
"static-analysis": "phpstan analyse --ansi --memory-limit 256M",
"check-all": "composer lint && composer static-analysis && composer test"
}
}

View file

@ -1,14 +1,26 @@
# Integrations
- [Integration with Relay](https://github.com/ivome/graphql-relay-php)
- [Integration with Laravel 5](https://github.com/Folkloreatelier/laravel-graphql) + [Relay Helpers for Laravel](https://github.com/nuwave/laravel-graphql-relay)
- [Symfony Bundle](https://github.com/overblog/GraphQLBundle) by Overblog
- 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))
- Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)) via [Standard Server](executing-queries.md/#using-server)
* [Standard Server](executing-queries.md/#using-server) Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)).
* [Relay Library for graphql-php](https://github.com/ivome/graphql-relay-php) Helps construct Relay related schema definitions.
* [Lighthouse](https://github.com/nuwave/lighthouse) Laravel based, uses Schema Definition Language
* [Laravel GraphQL](https://github.com/rebing/graphql-laravel) - Laravel wrapper for Facebook's GraphQL
* [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) Bundle for Symfony
* [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress
# Tools
- [GraphiQL](https://github.com/graphql/graphiql) - An in-browser IDE for exploring GraphQL
- [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) -
# GraphQL PHP Tools
* [GraphQLite](https://graphqlite.thecodingmachine.io) Define your complete schema with annotations
* [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) Define types with Doctrine ORM annotations
* [DataLoaderPHP](https://github.com/overblog/dataloader-php) as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) A PSR-15 middleware to support file uploads in GraphQL.
* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) Provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches.
* [GraphQL Utils](https://github.com/simPod/GraphQL-Utils) Objective schema definition builders (no need for arrays anymore) and `DateTime` scalar
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server _(experimental)_
# General GraphQL Tools
* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).
* [GraphiQL](https://github.com/graphql/graphiql) An in-browser IDE for exploring GraphQL
* [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp)
GraphiQL as Google Chrome extension
- [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)

View file

@ -103,23 +103,25 @@ for a field you simply override this default resolver.
**graphql-php** provides following default field resolver:
```php
<?php
function defaultFieldResolver($source, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
function defaultFieldResolver($objectValue, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
if (is_array($source) || $source instanceof \ArrayAccess) {
if (isset($source[$fieldName])) {
$property = $source[$fieldName];
}
} else if (is_object($source)) {
if (isset($source->{$fieldName})) {
$property = $source->{$fieldName};
if (is_array($objectValue) || $objectValue instanceof \ArrayAccess) {
if (isset($objectValue[$fieldName])) {
$property = $objectValue[$fieldName];
}
} elseif (is_object($objectValue)) {
if (isset($objectValue->{$fieldName})) {
$property = $objectValue->{$fieldName};
}
}
return $property instanceof Closure
? $property($objectValue, $args, $context, $info)
: $property;
}
return $property instanceof \Closure ? $property($source, $args, $context) : $property;
}
```
As you see it returns value by key (for arrays) or property (for objects).
@ -161,7 +163,6 @@ $userType = new ObjectType([
Keep in mind that **field resolver** has precedence over **default field resolver per type** which in turn
has precedence over **default field resolver**.
# Solving N+1 Problem
Since: 0.9.0
@ -270,4 +271,4 @@ Where **$promiseAdapter** is an instance of:
* Other platforms: write your own class implementing interface: <br>
[`GraphQL\Executor\Promise\PromiseAdapter`](reference.md#graphqlexecutorpromisepromiseadapter).
Then your **resolve** functions should return promises of your platform instead of `GraphQL\Deferred`s.
Then your **resolve** functions should return promises of your platform instead of `GraphQL\Deferred`s.

View file

@ -84,7 +84,7 @@ To change default **"Internal server error"** message to something else, use:
GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error");
```
#Debugging tools
# Debugging tools
During development or debugging use `$result->toArray(true)` to add **debugMessage** key to
each formatted error entry. If you also want to add exception trace - pass flags instead:
@ -116,7 +116,7 @@ This will make each error entry to look like this:
];
```
If you prefer first resolver exception to be re-thrown, use following flags:
If you prefer the first resolver exception to be re-thrown, use following flags:
```php
<?php
use GraphQL\GraphQL;
@ -127,6 +127,9 @@ $debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::RETHROW_INTERNAL_EXCEPTIONS;
$result = GraphQL::executeQuery(/*args*/)->toArray($debug);
```
If you only want to re-throw Exceptions that are not marked as safe through the `ClientAware` interface, use
the flag `Debug::RETHROW_UNSAFE_EXCEPTIONS`.
# Custom Error Handling and Formatting
It is possible to define custom **formatter** and **handler** for result errors.
@ -151,8 +154,8 @@ $myErrorHandler = function(array $errors, callable $formatter) {
$result = GraphQL::executeQuery(/* $args */)
->setErrorFormatter($myErrorFormatter)
->setErrorHandler($myErrorHandler)
->toArray();
->setErrorsHandler($myErrorHandler)
->toArray();
```
Note that when you pass [debug flags](#debugging-tools) to **toArray()** your custom formatter will still be

View file

@ -87,7 +87,7 @@ $psrResponse = new SomePsr7ResponseImplementation(json_encode($result));
PSR-7 is useful when you want to integrate the server into existing framework:
- [PSR-7 for Laravel](https://laravel.com/docs/5.1/requests#psr7-requests)
- [Symfony PSR-7 Bridge](https://symfony.com/doc/current/request/psr7.html)
- [Symfony PSR-7 Bridge](https://symfony.com/doc/current/components/psr7.html)
- [Slim](https://www.slimframework.com/docs/concepts/value-objects.html)
- [Zend Expressive](http://zendframework.github.io/zend-expressive/)
@ -136,13 +136,13 @@ So for example following batch will require single DB request (if user field is
```json
[
{
"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
Using [composer](https://getcomposer.org/doc/00-intro.md), simply run:
Using [composer](https://getcomposer.org/doc/00-intro.md), run:
```sh
composer require webonyx/graphql-php
@ -54,8 +54,8 @@ $queryType = new ObjectType([
'args' => [
'message' => Type::nonNull(Type::string()),
],
'resolve' => function ($root, $args) {
return $root['prefix'] . $args['message'];
'resolve' => function ($rootValue, $args) {
return $rootValue['prefix'] . $args['message'];
}
],
],
@ -106,7 +106,7 @@ echo json_encode($output);
Our example is finished. Try it by running:
```sh
php -S localhost:8000 graphql.php
php -S localhost:8080 graphql.php
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
```

View file

@ -7,7 +7,7 @@
# About GraphQL
GraphQL is a modern way to build HTTP APIs consumed by the web and mobile clients.
It is intended to be a replacement for REST and SOAP APIs (even for **existing applications**).
It is intended to be an alternative to REST and SOAP APIs (even for **existing applications**).
GraphQL itself is a [specification](https://github.com/facebook/graphql) designed by Facebook
engineers. Various implementations of this specification were written
@ -34,9 +34,9 @@ Library features include:
- Parsing, validating and [executing GraphQL queries](executing-queries.md) against this Type System
- Rich [error reporting](error-handling.md), including query validation and execution errors
- Optional tools for [parsing GraphQL Type language](type-system/type-language.md)
- Tools for [batching requests](data-fetching.md/#solving-n1-problem) to backend storage
- [Async PHP platforms support](data-fetching.md/#async-php) via promises
- [Standard HTTP server](executing-queries.md/#using-server)
- Tools for [batching requests](data-fetching.md#solving-n1-problem) to backend storage
- [Async PHP platforms support](data-fetching.md#async-php) via promises
- [Standard HTTP server](executing-queries.md#using-server)
Also, several [complementary tools](complementary-tools.md) are available which provide integrations with
existing PHP frameworks, add support for Relay, etc.
@ -44,12 +44,12 @@ existing PHP frameworks, add support for Relay, etc.
## Current Status
The first version of this library (v0.1) was released on August 10th 2015.
The current version (v0.10) supports all features described by GraphQL specification
(including April 2016 add-ons) as well as some experimental features like
The current version supports all features described by GraphQL specification
as well as some experimental features like
[Schema Language parser](type-system/type-language.md) and
[Schema printer](reference.md#graphqlutilsschemaprinter).
Ready for real-world usage.
## Github
## GitHub
Project source code is [hosted on GitHub](https://github.com/webonyx/graphql-php).

File diff suppressed because it is too large Load diff

View file

@ -35,9 +35,9 @@ In **graphql-php** custom directive is an instance of `GraphQL\Type\Definition\D
```php
<?php
use GraphQL\Language\DirectiveLocation;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\DirectiveLocation;
use GraphQL\Type\Definition\FieldArgument;
$trackDirective = new Directive([
@ -58,4 +58,4 @@ $trackDirective = new Directive([
```
See possible directive locations in
[`GraphQL\Type\Definition\DirectiveLocation`](../reference.md#graphqltypedefinitiondirectivelocation).
[`GraphQL\Language\DirectiveLocation`](../reference.md#graphqllanguagedirectivelocation).

View file

@ -158,7 +158,7 @@ $heroType = new ObjectType([
'args' => [
'episode' => Type::nonNull($enumType)
],
'resolve' => function($_value, $args) {
'resolve' => function($hero, $args) {
return $args['episode'] === 5 ? true : false;
}
]

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
type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**Scalar**, **Enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers)
description | `string` | Plain-text description of this input field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
defaultValue | `scalar` | Default value of this input field
defaultValue | `scalar` | Default value of this input field. Use the internal value if specifying a default for an **enum** type
# Using Input Object Type
In the example above we defined our InputObjectType. Now let's use it in one of field arguments:
@ -131,7 +131,7 @@ $queryType = new ObjectType([
'type' => Type::listOf($storyType),
'args' => [
'filters' => [
'type' => Type::nonNull($filters),
'type' => $filters,
'defaultValue' => [
'popular' => true
]

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)
type | `Type` | **Required.** An instance of internal or custom type. Note: type must be represented by a single instance within one schema (see also [Type Registry](index.md#type-registry))
args | `array` | An array of possible type arguments. Each entry is expected to be an array with keys: **name**, **type**, **description**, **defaultValue**. See [Field Arguments](#field-arguments) section below.
resolve | `callable` | **function($value, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$value** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
resolve | `callable` | **function($objectValue, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$objectValue** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
complexity | `callable` | **function($childrenComplexity, $args)**<br> Used to restrict query complexity. The feature is disabled by default, read about [Security](../security.md#query-complexity-analysis) to use it.
description | `string` | Plain-text description of this field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
deprecationReason | `string` | Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced)
@ -94,7 +94,7 @@ Option | Type | Notes
name | `string` | **Required.** Name of the argument. When not set - inferred from **args** array key
type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**scalar**, **enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers)
description | `string` | Plain-text description of this argument for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
defaultValue | `scalar` | Default value for this argument
defaultValue | `scalar` | Default value for this argument. Use the internal value if specifying a default for an **enum** type
# Shorthand field definitions
Fields can be also defined in **shorthand** notation (with only **name** and **type** options):

View file

@ -96,10 +96,11 @@ class EmailType extends ScalarType
* }
*
* @param \GraphQL\Language\AST\Node $valueNode
* @param array|null $variables
* @return string
* @throws Error
*/
public function parseLiteral($valueNode)
public function parseLiteral($valueNode, array $variables = null)
{
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
// error location in query:
@ -124,6 +125,6 @@ $emailType = new CustomScalarType([
'name' => 'Email',
'serialize' => function($value) {/* See function body above */},
'parseValue' => function($value) {/* See function body above */},
'parseLiteral' => function($valueNode) {/* See function body above */},
'parseLiteral' => function($valueNode, array $variables = null) {/* See function body above */},
]);
```

View file

@ -62,7 +62,7 @@ $mutationType = new ObjectType([
'episode' => $episodeEnum,
'review' => $reviewInputObject
],
'resolve' => function($val, $args) {
'resolve' => function($rootValue, $args) {
// TODOC
}
]

View file

@ -1,4 +1,5 @@
# Defining your schema
Since 0.9.0
[Type language](http://graphql.org/learn/schema/#type-language) is a convenient way to define your schema,
especially with IDE autocompletion and syntax validation.
@ -32,18 +33,19 @@ $contents = file_get_contents('schema.graphql');
$schema = BuildSchema::build($contents);
```
By default, such schema is created without any resolvers. As a result, it doesn't support **Interfaces** and **Unions**
because it is impossible to resolve actual implementations during execution.
By default, such schema is created without any resolvers.
Also, 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.
# Defining resolvers
In order to enable **Interfaces**, **Unions** and custom field resolvers you can pass the second argument:
**type config decorator** to schema builder.
Since 0.10.0
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
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
@ -61,6 +63,8 @@ $schema = BuildSchema::build($contents, $typeConfigDecorator);
```
# Performance considerations
Since 0.10.0
Method **build()** produces a [lazy schema](schema.md#lazy-loading-of-types)
automatically, so it works efficiently even with very large schemas.
@ -77,11 +81,11 @@ $cacheFilename = 'cached_schema.php';
if (!file_exists($cacheFilename)) {
$document = Parser::parse(file_get_contents('./schema.graphql'));
file_put_contents($cacheFilename, "<?php\nreturn " . var_export($document->toArray(), true));
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true) . ";\n");
} else {
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
}
$typeConfigDecorator = function () {};
$schema = BuildSchema::build($document, $typeConfigDecorator);
```
```

View file

@ -19,8 +19,8 @@ try {
'args' => [
'message' => ['type' => Type::string()],
],
'resolve' => function ($root, $args) {
return $root['prefix'] . $args['message'];
'resolve' => function ($rootValue, $args) {
return $rootValue['prefix'] . $args['message'];
}
],
],
@ -35,7 +35,7 @@ try {
'x' => ['type' => Type::int()],
'y' => ['type' => Type::int()],
],
'resolve' => function ($root, $args) {
'resolve' => function ($calc, $args) {
return $args['x'] + $args['y'];
},
],

View file

@ -35,12 +35,12 @@ class CommentType extends ObjectType
Types::htmlField('body')
];
},
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
'resolveField' => function($comment, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) {
return $this->{$method}($value, $args, $context, $info);
return $this->{$method}($comment, $args, $context, $info);
} else {
return $value->{$info->fieldName};
return $comment->{$info->fieldName};
}
}
];

View file

@ -57,8 +57,8 @@ class QueryType extends ObjectType
],
'hello' => Type::string()
],
'resolveField' => function($val, $args, $context, ResolveInfo $info) {
return $this->{$info->fieldName}($val, $args, $context, $info);
'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) {
return $this->{$info->fieldName}($rootValue, $args, $context, $info);
}
];
parent::__construct($config);

View file

@ -30,11 +30,12 @@ class UrlType extends ScalarType
*
* @param mixed $value
* @return mixed
* @throws Error
*/
public function parseValue($value)
{
if (!is_string($value) || !filter_var($value, FILTER_VALIDATE_URL)) { // quite naive, but after all this is example
throw new \UnexpectedValueException("Cannot represent value as URL: " . Utils::printSafe($value));
throw new Error("Cannot represent value as URL: " . Utils::printSafe($value));
}
return $value;
}
@ -42,20 +43,21 @@ class UrlType extends ScalarType
/**
* Parses an externally provided literal value to use as an input (e.g. in Query AST)
*
* @param $ast Node
* @param Node $valueNode
* @param array|null $variables
* @return null|string
* @throws Error
*/
public function parseLiteral($ast)
public function parseLiteral($valueNode, array $variables = null)
{
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
// error location in query:
if (!($ast instanceof StringValueNode)) {
throw new Error('Query error: Can only parse strings got: ' . $ast->kind, [$ast]);
if (!($valueNode instanceof StringValueNode)) {
throw new Error('Query error: Can only parse strings got: ' . $valueNode->kind, [$valueNode]);
}
if (!is_string($ast->value) || !filter_var($ast->value, FILTER_VALIDATE_URL)) {
throw new Error('Query error: Not a valid URL', [$ast]);
if (!is_string($valueNode->value) || !filter_var($valueNode->value, FILTER_VALIDATE_URL)) {
throw new Error('Query error: Not a valid URL', [$valueNode]);
}
return $ast->value;
return $valueNode->value;
}
}

View file

@ -75,12 +75,12 @@ class StoryType extends ObjectType
'interfaces' => [
Types::node()
],
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
'resolveField' => function($story, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) {
return $this->{$method}($value, $args, $context, $info);
return $this->{$method}($story, $args, $context, $info);
} else {
return $value->{$info->fieldName};
return $story->{$info->fieldName};
}
}
];

View file

@ -44,12 +44,12 @@ class UserType extends ObjectType
'interfaces' => [
Types::node()
],
'resolveField' => function($value, $args, $context, ResolveInfo $info) {
'resolveField' => function($user, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) {
return $this->{$method}($value, $args, $context, $info);
return $this->{$method}($user, $args, $context, $info);
} else {
return $value->{$info->fieldName};
return $user->{$info->fieldName};
}
}
];

View file

@ -35,10 +35,11 @@ try {
// Parse incoming query and variables
if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) {
$raw = file_get_contents('php://input') ?: '';
$data = json_decode($raw, true);
$data = json_decode($raw, true) ?: [];
} else {
$data = $_REQUEST;
}
$data += ['query' => null, 'variables' => null];
if (null === $data['query']) {

View file

@ -18,7 +18,7 @@ try {
$query = $input['query'];
$variableValues = isset($input['variables']) ? $input['variables'] : null;
$result = GraphQL::execute($schema, $query, $rootValue, null, $variableValues);
$result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variableValues);
} catch (\Exception $e) {
$result = [
'error' => [

View file

@ -1,12 +1,12 @@
<?php
interface Resolver {
public function resolve($root, $args, $context);
public function resolve($rootValue, $args, $context);
}
class Addition implements Resolver
{
public function resolve($root, $args, $context)
public function resolve($rootValue, $args, $context)
{
return $args['x'] + $args['y'];
}
@ -14,22 +14,22 @@ class Addition implements Resolver
class Echoer implements Resolver
{
public function resolve($root, $args, $context)
public function resolve($rootValue, $args, $context)
{
return $root['prefix'].$args['message'];
return $rootValue['prefix'].$args['message'];
}
}
return [
'sum' => function($root, $args, $context) {
'sum' => function($rootValue, $args, $context) {
$sum = new Addition();
return $sum->resolve($root, $args, $context);
return $sum->resolve($rootValue, $args, $context);
},
'echo' => function($root, $args, $context) {
'echo' => function($rootValue, $args, $context) {
$echo = new Echoer();
return $echo->resolve($root, $args, $context);
return $echo->resolve($rootValue, $args, $context);
},
'prefix' => 'You said: ',
];

View file

@ -10,10 +10,10 @@ php -S localhost:8080 ./graphql.php
### Try query
```
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
curl -d '{"query": "query { echo(message: \"Hello World\") }" }' -H "Content-Type: application/json" http://localhost:8080
```
### Try mutation
```
curl http://localhost:8080 -d '{"query": "mutation { sum(x: 2, y: 2) }" }'
curl -d '{"query": "mutation { sum(x: 2, y: 2) }" }' -H "Content-Type: application/json" http://localhost:8080
```

View file

@ -19,8 +19,8 @@ try {
'args' => [
'message' => ['type' => Type::string()],
],
'resolve' => function ($root, $args) {
return $root['prefix'] . $args['message'];
'resolve' => function ($rootValue, $args) {
return $rootValue['prefix'] . $args['message'];
}
],
],
@ -35,7 +35,7 @@ try {
'x' => ['type' => Type::int()],
'y' => ['type' => Type::int()],
],
'resolve' => function ($root, $args) {
'resolve' => function ($calc, $args) {
return $args['x'] + $args['y'];
},
],

102
phpcs.xml.dist Normal file
View file

@ -0,0 +1,102 @@
<?xml version="1.0"?>
<ruleset>
<arg name="basepath" value="." />
<arg name="extensions" value="php" />
<arg name="parallel" value="80" />
<arg name="cache" value=".phpcs-cache" />
<arg name="colors" />
<!-- Ignore warnings, show progress of the run and show sniff names -->
<arg value="nps" />
<file>src</file>
<file>tests</file>
<rule ref="Doctrine">
<!-- Disable PHP7+ features that might cause BC breaks for now -->
<exclude name="SlevomatCodingStandard.Classes.ClassConstantVisibility.MissingConstantVisibility" />
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint" />
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint" />
<!-- Enable when Slevomat starts supporting variadics, see https://github.com/slevomat/coding-standard/issues/251 -->
<exclude name="SlevomatCodingStandard.Namespaces.UnusedUses.UnusedUse" />
</rule>
<!--@api annotation is required for now -->
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration">
<properties>
<property
name="usefulAnnotations"
type="array"
value="
@after,
@afterClass,
@AfterMethods,
@api,
@Attribute,
@Attributes,
@before,
@beforeClass,
@BeforeMethods,
@covers,
@coversDefaultClass,
@coversNothing,
@dataProvider,
@depends,
@deprecated,
@doesNotPerformAssertions,
@Enum,
@expectedDeprecation,
@expectedException,
@expectedExceptionCode,
@expectedExceptionMessage,
@expectedExceptionMessageRegExp,
@group,
@Groups,
@IgnoreAnnotation,
@internal,
@Iterations,
@link,
@ODM\,
@ORM\,
@requires,
@Required,
@Revs,
@runInSeparateProcess,
@runTestsInSeparateProcesses,
@see,
@Target,
@test,
@throws,
@uses
"
/>
</properties>
</rule>
<rule ref="SlevomatCodingStandard.Commenting.ForbiddenAnnotations">
<properties>
<property
name="forbiddenAnnotations"
type="array"
value="
@author,
@category,
@copyright,
@created,
@license,
@package,
@since,
@subpackage,
@version
"
/>
</properties>
</rule>
<!-- IDEs sort by PSR12, Slevomat coding standard uses old sorting for BC -->
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
<properties>
<property name="psr12Compatible" type="bool" value="true" />
</properties>
</rule>
</ruleset>

17
phpstan.neon.dist Normal file
View file

@ -0,0 +1,17 @@
parameters:
level: 1
paths:
- %currentWorkingDirectory%/src
- %currentWorkingDirectory%/tests
ignoreErrors:
- "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~"
- "~(Method|Property) .+::.+(\\(\\))? (has parameter \\$\\w+ with no|has no return|has no) typehint specified~"
- "~Variable property access on .+~"
- "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" # TODO get rid of
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-strict-rules/rules.neon

View file

@ -1,16 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="utf-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
>
<php>
<ini name="error_reporting" value="E_ALL"/>
</php>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="webonyx/graphql-php Test Suite">
<directory>./tests/</directory>
@ -26,21 +23,6 @@
<filter>
<whitelist>
<directory suffix=".php">./src</directory>
<exclude>
<directory>./bin</directory>
<directory>./docs</directory>
<directory>./build</directory>
<directory>./tests</directory>
<directory>./vendor</directory>
<directory>./examples</directory>
<directory>./benchmarks</directory>
<file>./src/deprecated.php</file>
</exclude>
</whitelist>
</filter>
<php>
<ini name="error_reporting" value="E_ALL"/>
</php>
</phpunit>

View file

@ -1,60 +1,64 @@
<?php
declare(strict_types=1);
namespace GraphQL;
use Exception;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use SplQueue;
use Throwable;
class Deferred
{
/**
* @var \SplQueue
*/
/** @var SplQueue|null */
private static $queue;
/**
* @var callable
*/
/** @var callable */
private $callback;
/**
* @var SyncPromise
*/
/** @var SyncPromise */
public $promise;
public static function getQueue()
{
return self::$queue ?: self::$queue = new \SplQueue();
}
public static function runQueue()
{
$q = self::$queue;
while ($q && !$q->isEmpty()) {
/** @var self $dfd */
$dfd = $q->dequeue();
$dfd->run();
}
}
public function __construct(callable $callback)
{
$this->callback = $callback;
$this->promise = new SyncPromise();
$this->promise = new SyncPromise();
self::getQueue()->enqueue($this);
}
public static function getQueue() : SplQueue
{
if (self::$queue === null) {
self::$queue = new SplQueue();
}
return self::$queue;
}
public static function runQueue() : void
{
$queue = self::getQueue();
while (! $queue->isEmpty()) {
/** @var self $dequeuedNodeValue */
$dequeuedNodeValue = $queue->dequeue();
$dequeuedNodeValue->run();
}
}
public function then($onFulfilled = null, $onRejected = null)
{
return $this->promise->then($onFulfilled, $onRejected);
}
private function run()
public function run() : void
{
try {
$cb = $this->callback;
$this->promise->resolve($cb());
} catch (\Exception $e) {
} catch (Exception $e) {
$this->promise->reject($e);
} catch (\Throwable $e) {
} catch (Throwable $e) {
$this->promise->reject($e);
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace GraphQL;
trigger_error(
'GraphQL\Error was moved to GraphQL\Error\Error and will be deleted on next release',
E_USER_DEPRECATED
);
/**
* Class Error
*
* @deprecated as of 8.0 in favor of GraphQL\Error\Error
* @package GraphQL
*/
class Error extends \GraphQL\Error\Error
{
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
/**
@ -14,8 +17,9 @@ interface ClientAware
/**
* Returns true when exception message is safe to be displayed to a client.
*
* @api
* @return bool
*
* @api
*/
public function isClientSafe();
@ -24,8 +28,9 @@ interface ClientAware
*
* Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.
*
* @api
* @return string
*
* @api
*/
public function getCategory();
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
/**
@ -6,7 +9,8 @@ namespace GraphQL\Error;
*/
class Debug
{
const INCLUDE_DEBUG_MESSAGE = 1;
const INCLUDE_TRACE = 2;
const INCLUDE_DEBUG_MESSAGE = 1;
const INCLUDE_TRACE = 2;
const RETHROW_INTERNAL_EXCEPTIONS = 4;
const RETHROW_UNSAFE_EXCEPTIONS = 8;
}

View file

@ -1,9 +1,22 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use Exception;
use GraphQL\Language\AST\Node;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
use GraphQL\Utils\Utils;
use JsonSerializable;
use Throwable;
use Traversable;
use function array_filter;
use function array_map;
use function array_values;
use function is_array;
use function iterator_to_array;
/**
* Describes an Error found during the parse, validate, or
@ -19,9 +32,9 @@ use GraphQL\Utils\Utils;
* Class extends standard PHP `\Exception`, so all standard methods of base `\Exception` class
* are available in addition to those listed below.
*/
class Error extends \Exception implements \JsonSerializable, ClientAware
class Error extends Exception implements JsonSerializable, ClientAware
{
const CATEGORY_GRAPHQL = 'graphql';
const CATEGORY_GRAPHQL = 'graphql';
const CATEGORY_INTERNAL = 'internal';
/**
@ -31,56 +44,103 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
*/
public $message;
/**
* @var SourceLocation[]
*/
/** @var SourceLocation[] */
private $locations;
/**
* An array describing the JSON-path into the execution response which
* corresponds to this error. Only included for errors during execution.
*
* @var array
* @var mixed[]|null
*/
public $path;
/**
* An array of GraphQL AST Nodes corresponding to this error.
*
* @var array
* @var Node[]|null
*/
public $nodes;
/**
* The source GraphQL document corresponding to this error.
* The source GraphQL document for the first location of this error.
*
* Note that if this Error represents more than one node, the source may not
* represent nodes after the first node.
*
* @var Source|null
*/
private $source;
/**
* @var array
*/
/** @var int[]|null */
private $positions;
/**
* @var bool
*/
/** @var bool */
private $isClientSafe;
/**
* @var string
*/
/** @var string */
protected $category;
/** @var mixed[]|null */
protected $extensions;
/**
* @param string $message
* @param Node|Node[]|Traversable|null $nodes
* @param mixed[]|null $positions
* @param mixed[]|null $path
* @param Throwable $previous
* @param mixed[] $extensions
*/
public function __construct(
$message,
$nodes = null,
?Source $source = null,
$positions = null,
$path = null,
$previous = null,
array $extensions = []
) {
parent::__construct($message, 0, $previous);
// Compute list of blame nodes.
if ($nodes instanceof Traversable) {
$nodes = iterator_to_array($nodes);
} elseif ($nodes && ! is_array($nodes)) {
$nodes = [$nodes];
}
$this->nodes = $nodes;
$this->source = $source;
$this->positions = $positions;
$this->path = $path;
$this->extensions = $extensions ?: (
$previous && $previous instanceof self
? $previous->extensions
: []
);
if ($previous instanceof ClientAware) {
$this->isClientSafe = $previous->isClientSafe();
$this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL;
} elseif ($previous) {
$this->isClientSafe = false;
$this->category = self::CATEGORY_INTERNAL;
} else {
$this->isClientSafe = true;
$this->category = self::CATEGORY_GRAPHQL;
}
}
/**
* Given an arbitrary Error, presumably thrown while attempting to execute a
* GraphQL operation, produce a new GraphQLError aware of the location in the
* document responsible for the original Error.
*
* @param $error
* @param array|null $nodes
* @param array|null $path
* @param mixed $error
* @param Node[]|null $nodes
* @param mixed[]|null $path
*
* @return Error
*/
public static function createLocatedError($error, $nodes = null, $path = null)
@ -88,22 +148,24 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
if ($error instanceof self) {
if ($error->path && $error->nodes) {
return $error;
} else {
$nodes = $nodes ?: $error->nodes;
$path = $path ?: $error->path;
}
$nodes = $nodes ?: $error->nodes;
$path = $path ?: $error->path;
}
$source = $positions = $originalError = null;
$source = $positions = $originalError = null;
$extensions = [];
if ($error instanceof self) {
$message = $error->getMessage();
$message = $error->getMessage();
$originalError = $error;
$nodes = $error->nodes ?: $nodes;
$source = $error->source;
$positions = $error->positions;
} else if ($error instanceof \Exception || $error instanceof \Throwable) {
$message = $error->getMessage();
$nodes = $error->nodes ?: $nodes;
$source = $error->source;
$positions = $error->positions;
$extensions = $error->extensions;
} elseif ($error instanceof Exception || $error instanceof Throwable) {
$message = $error->getMessage();
$originalError = $error;
} else {
$message = (string) $error;
@ -115,60 +177,19 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
$source,
$positions,
$path,
$originalError
$originalError,
$extensions
);
}
/**
* @param Error $error
* @return array
* @return mixed[]
*/
public static function formatError(Error $error)
{
return $error->toSerializableArray();
}
/**
* @param string $message
* @param array|null $nodes
* @param Source $source
* @param array|null $positions
* @param array|null $path
* @param \Throwable $previous
*/
public function __construct(
$message,
$nodes = null,
Source $source = null,
$positions = null,
$path = null,
$previous = null
)
{
parent::__construct($message, 0, $previous);
if ($nodes instanceof \Traversable) {
$nodes = iterator_to_array($nodes);
}
$this->nodes = $nodes;
$this->source = $source;
$this->positions = $positions;
$this->path = $path;
if ($previous instanceof ClientAware) {
$this->isClientSafe = $previous->isClientSafe();
$this->category = $previous->getCategory() ?: static::CATEGORY_INTERNAL;
} else if ($previous) {
$this->isClientSafe = false;
$this->category = static::CATEGORY_INTERNAL;
} else {
$this->isClientSafe = true;
$this->category = static::CATEGORY_GRAPHQL;
}
}
/**
* @inheritdoc
*/
@ -190,29 +211,38 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
*/
public function getSource()
{
if (null === $this->source) {
if (!empty($this->nodes[0]) && !empty($this->nodes[0]->loc)) {
if ($this->source === null) {
if (! empty($this->nodes[0]) && ! empty($this->nodes[0]->loc)) {
$this->source = $this->nodes[0]->loc->source;
}
}
return $this->source;
}
/**
* @return array
* @return int[]
*/
public function getPositions()
{
if (null === $this->positions) {
if (!empty($this->nodes)) {
$positions = array_map(function($node) {
if ($this->positions === null && ! empty($this->nodes)) {
$positions = array_map(
static function ($node) {
return isset($node->loc) ? $node->loc->start : null;
}, $this->nodes);
$this->positions = array_filter($positions, function($p) {
},
$this->nodes
);
$positions = array_filter(
$positions,
static function ($p) {
return $p !== null;
});
}
}
);
$this->positions = array_values($positions);
}
return $this->positions;
}
@ -227,19 +257,36 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
* point out to field mentioned in multiple fragments. Errors during execution include a
* single location, the field which produced the error.
*
* @api
* @return SourceLocation[]
*
* @api
*/
public function getLocations()
{
if (null === $this->locations) {
if ($this->locations === null) {
$positions = $this->getPositions();
$source = $this->getSource();
$source = $this->getSource();
$nodes = $this->nodes;
if ($positions && $source) {
$this->locations = array_map(function ($pos) use ($source) {
return $source->getLocation($pos);
}, $positions);
$this->locations = array_map(
static function ($pos) use ($source) {
return $source->getLocation($pos);
},
$positions
);
} elseif ($nodes) {
$locations = array_filter(
array_map(
static function ($node) {
if ($node->loc && $node->loc->source) {
return $node->loc->source->getLocation($node->loc->start);
}
},
$nodes
)
);
$this->locations = array_values($locations);
} else {
$this->locations = [];
}
@ -248,53 +295,86 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
return $this->locations;
}
/**
* @return Node[]|null
*/
public function getNodes()
{
return $this->nodes;
}
/**
* Returns an array describing the path from the root value to the field which produced this error.
* Only included for execution errors.
*
* @return mixed[]|null
*
* @api
* @return array|null
*/
public function getPath()
{
return $this->path;
}
/**
* @return mixed[]
*/
public function getExtensions()
{
return $this->extensions;
}
/**
* Returns array representation of error suitable for serialization
*
* @deprecated Use FormattedError::createFromException() instead
* @return array
*
* @return mixed[]
*/
public function toSerializableArray()
{
$arr = [
'message' => $this->getMessage()
'message' => $this->getMessage(),
];
$locations = Utils::map($this->getLocations(), function(SourceLocation $loc) {
return $loc->toSerializableArray();
});
$locations = Utils::map(
$this->getLocations(),
static function (SourceLocation $loc) {
return $loc->toSerializableArray();
}
);
if (!empty($locations)) {
if (! empty($locations)) {
$arr['locations'] = $locations;
}
if (!empty($this->path)) {
if (! empty($this->path)) {
$arr['path'] = $this->path;
}
if (! empty($this->extensions)) {
$arr['extensions'] = $this->extensions;
}
return $arr;
}
/**
* Specify data which should be serialized to JSON
*
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
function jsonSerialize()
public function jsonSerialize()
{
return $this->toSerializableArray();
}
/**
* @return string
*/
public function __toString()
{
return FormattedError::printError($this);
}
}

View file

@ -1,10 +1,39 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use Countable;
use ErrorException;
use Exception;
use GraphQL\Language\AST\Node;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\WrappingType;
use GraphQL\Utils\Utils;
use Throwable;
use function addcslashes;
use function array_filter;
use function array_intersect_key;
use function array_map;
use function array_merge;
use function array_shift;
use function count;
use function get_class;
use function gettype;
use function implode;
use function is_array;
use function is_bool;
use function is_object;
use function is_scalar;
use function is_string;
use function mb_strlen;
use function preg_split;
use function sprintf;
use function str_repeat;
use function strlen;
/**
* This class is used for [default error formatting](error-handling.md).
@ -13,20 +42,119 @@ use GraphQL\Utils\Utils;
*/
class FormattedError
{
/** @var string */
private static $internalErrorMessage = 'Internal server error';
/**
* Set default error message for internal errors formatted using createFormattedError().
* This value can be overridden by passing 3rd argument to `createFormattedError()`.
*
* @api
* @param string $msg
*
* @api
*/
public static function setInternalErrorMessage($msg)
{
self::$internalErrorMessage = $msg;
}
/**
* Prints a GraphQLError to a string, representing useful location information
* about the error's position in the source.
*
* @return string
*/
public static function printError(Error $error)
{
$printedLocations = [];
if ($error->nodes) {
/** @var Node $node */
foreach ($error->nodes as $node) {
if (! $node->loc) {
continue;
}
if ($node->loc->source === null) {
continue;
}
$printedLocations[] = self::highlightSourceAtLocation(
$node->loc->source,
$node->loc->source->getLocation($node->loc->start)
);
}
} elseif ($error->getSource() && $error->getLocations()) {
$source = $error->getSource();
foreach ($error->getLocations() as $location) {
$printedLocations[] = self::highlightSourceAtLocation($source, $location);
}
}
return ! $printedLocations
? $error->getMessage()
: implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
}
/**
* Render a helpful description of the location of the error in the GraphQL
* Source document.
*
* @return string
*/
private static function highlightSourceAtLocation(Source $source, SourceLocation $location)
{
$line = $location->line;
$lineOffset = $source->locationOffset->line - 1;
$columnOffset = self::getColumnOffset($source, $location);
$contextLine = $line + $lineOffset;
$contextColumn = $location->column + $columnOffset;
$prevLineNum = (string) ($contextLine - 1);
$lineNum = (string) $contextLine;
$nextLineNum = (string) ($contextLine + 1);
$padLen = strlen($nextLineNum);
$lines = preg_split('/\r\n|[\n\r]/', $source->body);
$lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0];
$outputLines = [
sprintf('%s (%s:%s)', $source->name, $contextLine, $contextColumn),
$line >= 2 ? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null,
self::lpad($padLen, $lineNum) . ': ' . $lines[$line - 1],
self::whitespace(2 + $padLen + $contextColumn - 1) . '^',
$line < count($lines) ? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null,
];
return implode("\n", array_filter($outputLines));
}
/**
* @return int
*/
private static function getColumnOffset(Source $source, SourceLocation $location)
{
return $location->line === 1 ? $source->locationOffset->column - 1 : 0;
}
/**
* @param int $len
*
* @return string
*/
private static function whitespace($len)
{
return str_repeat(' ', $len);
}
/**
* @param int $len
*
* @return string
*/
private static function lpad($len, $str)
{
return self::whitespace($len - mb_strlen($str)) . $str;
}
/**
* Standard GraphQL error formatter. Converts any exception to array
* conforming to GraphQL spec.
@ -36,18 +164,21 @@ class FormattedError
*
* For a list of available debug flags see GraphQL\Error\Debug constants.
*
* @param Throwable $e
* @param bool|int $debug
* @param string $internalErrorMessage
*
* @return mixed[]
*
* @throws Throwable
*
* @api
* @param \Throwable $e
* @param bool|int $debug
* @param string $internalErrorMessage
* @return array
* @throws \Throwable
*/
public static function createFromException($e, $debug = false, $internalErrorMessage = null)
{
Utils::invariant(
$e instanceof \Exception || $e instanceof \Throwable,
"Expected exception, got %s",
$e instanceof Exception || $e instanceof Throwable,
'Expected exception, got %s',
Utils::getVariableType($e)
);
@ -55,27 +186,36 @@ class FormattedError
if ($e instanceof ClientAware) {
$formattedError = [
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
'category' => $e->getCategory()
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
'extensions' => [
'category' => $e->getCategory(),
],
];
} else {
$formattedError = [
'message' => $internalErrorMessage,
'category' => Error::CATEGORY_INTERNAL
'message' => $internalErrorMessage,
'extensions' => [
'category' => Error::CATEGORY_INTERNAL,
],
];
}
if ($e instanceof Error) {
$locations = Utils::map($e->getLocations(), function(SourceLocation $loc) {
return $loc->toSerializableArray();
});
if (!empty($locations)) {
$locations = Utils::map(
$e->getLocations(),
static function (SourceLocation $loc) {
return $loc->toSerializableArray();
}
);
if (! empty($locations)) {
$formattedError['locations'] = $locations;
}
if (!empty($e->path)) {
if (! empty($e->path)) {
$formattedError['path'] = $e->path;
}
if (! empty($e->getExtensions())) {
$formattedError['extensions'] = $e->getExtensions() + $formattedError['extensions'];
}
}
if ($debug) {
@ -89,56 +229,67 @@ class FormattedError
* Decorates spec-compliant $formattedError with debug entries according to $debug flags
* (see GraphQL\Error\Debug for available flags)
*
* @param array $formattedError
* @param \Throwable $e
* @param bool $debug
* @return array
* @throws \Throwable
* @param mixed[] $formattedError
* @param Throwable $e
* @param bool|int $debug
*
* @return mixed[]
*
* @throws Throwable
*/
public static function addDebugEntries(array $formattedError, $e, $debug)
{
if (!$debug) {
if (! $debug) {
return $formattedError;
}
Utils::invariant(
$e instanceof \Exception || $e instanceof \Throwable,
"Expected exception, got %s",
$e instanceof Exception || $e instanceof Throwable,
'Expected exception, got %s',
Utils::getVariableType($e)
);
$debug = (int) $debug;
if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
if (!$e instanceof Error) {
if (! $e instanceof Error) {
throw $e;
} else if ($e->getPrevious()) {
}
if ($e->getPrevious()) {
throw $e->getPrevious();
}
}
$isInternal = !$e instanceof ClientAware || !$e->isClientSafe();
$isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isInternal) {
if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) && $isUnsafe) {
if ($e->getPrevious()) {
throw $e->getPrevious();
}
}
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isUnsafe) {
// Displaying debugMessage as a first entry:
$formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
}
if ($debug & Debug::INCLUDE_TRACE) {
if ($e instanceof \ErrorException || $e instanceof \Error) {
if ($e instanceof ErrorException || $e instanceof \Error) {
$formattedError += [
'file' => $e->getFile(),
'line' => $e->getLine(),
];
}
$isTrivial = $e instanceof Error && !$e->getPrevious();
$isTrivial = $e instanceof Error && ! $e->getPrevious();
if (!$isTrivial) {
$debugging = $e->getPrevious() ?: $e;
if (! $isTrivial) {
$debugging = $e->getPrevious() ?: $e;
$formattedError['trace'] = static::toSafeTrace($debugging);
}
}
return $formattedError;
}
@ -146,67 +297,71 @@ class FormattedError
* Prepares final error formatter taking in account $debug flags.
* If initial formatter is not set, FormattedError::createFromException is used
*
* @param callable|null $formatter
* @param $debug
* @return callable|\Closure
* @param bool|int $debug
*
* @return callable|callable
*/
public static function prepareFormatter(callable $formatter = null, $debug)
public static function prepareFormatter(?callable $formatter = null, $debug)
{
$formatter = $formatter ?: function($e) {
$formatter = $formatter ?: static function ($e) {
return FormattedError::createFromException($e);
};
if ($debug) {
$formatter = function($e) use ($formatter, $debug) {
$formatter = static function ($e) use ($formatter, $debug) {
return FormattedError::addDebugEntries($formatter($e), $e, $debug);
};
}
return $formatter;
}
/**
* Returns error trace as serializable array
*
* @param Throwable $error
*
* @return mixed[]
*
* @api
* @param \Throwable $error
* @return array
*/
public static function toSafeTrace($error)
{
$trace = $error->getTrace();
// Remove invariant entries as they don't provide much value:
if (
isset($trace[0]['function']) && isset($trace[0]['class']) &&
('GraphQL\Utils\Utils::invariant' === $trace[0]['class'].'::'.$trace[0]['function'])) {
if (isset($trace[0]['function']) && isset($trace[0]['class']) &&
// Remove invariant entries as they don't provide much value:
($trace[0]['class'] . '::' . $trace[0]['function'] === 'GraphQL\Utils\Utils::invariant')) {
array_shift($trace);
} elseif (! isset($trace[0]['file'])) {
// Remove root call as it's likely error handler trace:
array_shift($trace);
}
// Remove root call as it's likely error handler trace:
else if (!isset($trace[0]['file'])) {
array_shift($trace);
}
return array_map(
static function ($err) {
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
return array_map(function($err) {
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
if (isset($err['function'])) {
$func = $err['function'];
$args = ! empty($err['args']) ? array_map([self::class, 'printVar'], $err['args']) : [];
$funcStr = $func . '(' . implode(', ', $args) . ')';
if (isset($err['function'])) {
$func = $err['function'];
$args = !empty($err['args']) ? array_map([__CLASS__, 'printVar'], $err['args']) : [];
$funcStr = $func . '(' . implode(", ", $args) . ')';
if (isset($err['class'])) {
$safeErr['call'] = $err['class'] . '::' . $funcStr;
} else {
$safeErr['function'] = $funcStr;
if (isset($err['class'])) {
$safeErr['call'] = $err['class'] . '::' . $funcStr;
} else {
$safeErr['function'] = $funcStr;
}
}
}
return $safeErr;
}, $trace);
return $safeErr;
},
$trace
);
}
/**
* @param $var
* @param mixed $var
*
* @return string
*/
public static function printVar($var)
@ -216,16 +371,17 @@ class FormattedError
if ($var instanceof WrappingType) {
$var = $var->getWrappedType(true);
}
return 'GraphQLType: ' . $var->name;
}
if (is_object($var)) {
return 'instance of ' . get_class($var) . ($var instanceof \Countable ? '(' . count($var) . ')' : '');
return 'instance of ' . get_class($var) . ($var instanceof Countable ? '(' . count($var) . ')' : '');
}
if (is_array($var)) {
return 'array(' . count($var) . ')';
}
if ('' === $var) {
if ($var === '') {
return '(empty string)';
}
if (is_string($var)) {
@ -237,42 +393,48 @@ class FormattedError
if (is_scalar($var)) {
return $var;
}
if (null === $var) {
if ($var === null) {
return 'null';
}
return gettype($var);
}
/**
* @deprecated as of v0.8.0
* @param $error
*
* @param string $error
* @param SourceLocation[] $locations
* @return array
*
* @return mixed[]
*/
public static function create($error, array $locations = [])
{
$formatted = [
'message' => $error
];
$formatted = ['message' => $error];
if (!empty($locations)) {
$formatted['locations'] = array_map(function($loc) { return $loc->toArray();}, $locations);
if (! empty($locations)) {
$formatted['locations'] = array_map(
static function ($loc) {
return $loc->toArray();
},
$locations
);
}
return $formatted;
}
/**
* @param \ErrorException $e
* @deprecated as of v0.10.0, use general purpose method createFromException() instead
* @return array
*
* @return mixed[]
*/
public static function createFromPHPError(\ErrorException $e)
public static function createFromPHPError(ErrorException $e)
{
return [
'message' => $e->getMessage(),
'message' => $e->getMessage(),
'severity' => $e->getSeverity(),
'trace' => self::toSafeTrace($e)
'trace' => self::toSafeTrace($e),
];
}
}

View file

@ -1,15 +1,16 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use LogicException;
/**
* Class InvariantVoilation
*
* Note:
* This exception should not inherit base Error exception as it is raised when there is an error somewhere in
* user-land code
*
* @package GraphQL\Error
*/
class InvariantViolation extends \LogicException
class InvariantViolation extends LogicException
{
}

View file

@ -1,50 +1,25 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
use function sprintf;
class SyntaxError extends Error
{
/**
* @param Source $source
* @param int $position
* @param int $position
* @param string $description
*/
public function __construct(Source $source, $position, $description)
{
$location = $source->getLocation($position);
$syntaxError =
"Syntax Error {$source->name} ({$location->line}:{$location->column}) $description\n\n" .
self::highlightSourceAtLocation($source, $location);
parent::__construct($syntaxError, null, $source, [$position]);
}
/**
* @param Source $source
* @param SourceLocation $location
* @return string
*/
public static function highlightSourceAtLocation(Source $source, SourceLocation $location)
{
$line = $location->line;
$prevLineNum = (string) ($line - 1);
$lineNum = (string) $line;
$nextLineNum = (string) ($line + 1);
$padLen = mb_strlen($nextLineNum, 'UTF-8');
$unicodeChars = json_decode('"\u2028\u2029"'); // Quick hack to get js-compatible representation of these chars
$lines = preg_split('/\r\n|[\n\r' . $unicodeChars . ']/su', $source->body);
$lpad = function($len, $str) {
return str_pad($str, $len - mb_strlen($str, 'UTF-8') + 1, ' ', STR_PAD_LEFT);
};
return
($line >= 2 ? $lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2] . "\n" : '') .
($lpad($padLen, $lineNum) . ': ' . $lines[$line - 1] . "\n") .
(str_repeat(' ', 1 + $padLen + $location->column) . "^\n") .
($line < count($lines) ? $lpad($padLen, $nextLineNum) . ': ' . $lines[$line] . "\n" : '');
parent::__construct(
sprintf('Syntax Error: %s', $description),
null,
$source,
[$position]
);
}
}

View file

@ -1,14 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use RuntimeException;
/**
* Class UserError
*
* Error caused by actions of GraphQL clients. Can be safely displayed to a client...
*
* @package GraphQL\Error
*/
class UserError extends \RuntimeException implements ClientAware
class UserError extends RuntimeException implements ClientAware
{
/**
* @return bool

View file

@ -1,6 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use GraphQL\Exception\InvalidArgument;
use function is_int;
use function trigger_error;
use const E_USER_WARNING;
/**
* Encapsulates warnings produced by the library.
*
@ -9,28 +17,29 @@ namespace GraphQL\Error;
*/
final class Warning
{
const WARNING_NAME = 1;
const WARNING_ASSIGN = 2;
const WARNING_CONFIG = 4;
const WARNING_FULL_SCHEMA_SCAN = 8;
const WARNING_CONFIG_DEPRECATION = 16;
const WARNING_NOT_A_TYPE = 32;
const ALL = 63;
public const WARNING_ASSIGN = 2;
public const WARNING_CONFIG = 4;
public const WARNING_FULL_SCHEMA_SCAN = 8;
public const WARNING_CONFIG_DEPRECATION = 16;
public const WARNING_NOT_A_TYPE = 32;
public const ALL = 63;
static $enableWarnings = self::ALL;
/** @var int */
private static $enableWarnings = self::ALL;
static $warned = [];
/** @var mixed[] */
private static $warned = [];
static private $warningHandler;
/** @var callable|null */
private static $warningHandler;
/**
* Sets warning handler which can intercept all system warnings.
* When not set, trigger_error() is used to notify about warnings.
*
* @api
* @param callable|null $warningHandler
*/
public static function setWarningHandler(callable $warningHandler = null)
public static function setWarningHandler(?callable $warningHandler = null) : void
{
self::$warningHandler = $warningHandler;
}
@ -43,18 +52,20 @@ final class Warning
*
* When passing true - suppresses all warnings.
*
* @api
* @param bool|int $suppress
*
* @api
*/
static function suppress($suppress = true)
public static function suppress($suppress = true) : void
{
if (true === $suppress) {
if ($suppress === true) {
self::$enableWarnings = 0;
} else if (false === $suppress) {
} elseif ($suppress === false) {
self::$enableWarnings = self::ALL;
} else {
$suppress = (int) $suppress;
} elseif (is_int($suppress)) {
self::$enableWarnings &= ~$suppress;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress);
}
}
@ -66,38 +77,40 @@ final class Warning
*
* When passing true - re-enables all warnings.
*
* @api
* @param bool|int $enable
*
* @api
*/
public static function enable($enable = true)
public static function enable($enable = true) : void
{
if (true === $enable) {
if ($enable === true) {
self::$enableWarnings = self::ALL;
} else if (false === $enable) {
} elseif ($enable === false) {
self::$enableWarnings = 0;
} else {
$enable = (int) $enable;
} elseif (is_int($enable)) {
self::$enableWarnings |= $enable;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable);
}
}
static function warnOnce($errorMessage, $warningId, $messageLevel = null)
public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
{
if (self::$warningHandler) {
if (self::$warningHandler !== null) {
$fn = self::$warningHandler;
$fn($errorMessage, $warningId);
} else if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
} elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) {
self::$warned[$warningId] = true;
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
}
}
static function warn($errorMessage, $warningId, $messageLevel = null)
public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
{
if (self::$warningHandler) {
if (self::$warningHandler !== null) {
$fn = self::$warningHandler;
$fn($errorMessage, $warningId);
} else if ((self::$enableWarnings & $warningId) > 0) {
} elseif ((self::$enableWarnings & $warningId) > 0) {
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
}
}

View file

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

View file

@ -1,7 +1,11 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor;
use GraphQL\Error\Error;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Type\Schema;
@ -10,78 +14,65 @@ use GraphQL\Type\Schema;
* Data that must be available at all points during query execution.
*
* Namely, schema of the type system that is currently executing,
* and the fragments defined in the query document
* and the fragments defined in the query document.
*
* @internal
*/
class ExecutionContext
{
/**
* @var Schema
*/
/** @var Schema */
public $schema;
/**
* @var FragmentDefinitionNode[]
*/
/** @var FragmentDefinitionNode[] */
public $fragments;
/**
* @var mixed
*/
/** @var mixed */
public $rootValue;
/**
* @var mixed
*/
/** @var mixed */
public $contextValue;
/**
* @var OperationDefinitionNode
*/
/** @var OperationDefinitionNode */
public $operation;
/**
* @var array
*/
/** @var mixed[] */
public $variableValues;
/**
* @var callable
*/
/** @var callable */
public $fieldResolver;
/**
* @var array
*/
/** @var Error[] */
public $errors;
/** @var PromiseAdapter */
public $promiseAdapter;
public function __construct(
$schema,
$fragments,
$root,
$rootValue,
$contextValue,
$operation,
$variables,
$variableValues,
$errors,
$fieldResolver,
$promiseAdapter
)
{
$this->schema = $schema;
$this->fragments = $fragments;
$this->rootValue = $root;
$this->contextValue = $contextValue;
$this->operation = $operation;
$this->variableValues = $variables;
$this->errors = $errors ?: [];
$this->fieldResolver = $fieldResolver;
$this->promises = $promiseAdapter;
) {
$this->schema = $schema;
$this->fragments = $fragments;
$this->rootValue = $rootValue;
$this->contextValue = $contextValue;
$this->operation = $operation;
$this->variableValues = $variableValues;
$this->errors = $errors ?: [];
$this->fieldResolver = $fieldResolver;
$this->promiseAdapter = $promiseAdapter;
}
public function addError(Error $error)
{
$this->errors[] = $error;
return $this;
}
}

View file

@ -1,8 +1,13 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use JsonSerializable;
use function array_map;
/**
* Returned after [query execution](executing-queries.md).
@ -12,13 +17,13 @@ use GraphQL\Error\FormattedError;
* Could be converted to [spec-compliant](https://facebook.github.io/graphql/#sec-Response-Format)
* serializable array using `toArray()`
*/
class ExecutionResult implements \JsonSerializable
class ExecutionResult implements JsonSerializable
{
/**
* Data collected from resolvers during query execution
*
* @api
* @var array
* @var mixed[]
*/
public $data;
@ -29,38 +34,34 @@ class ExecutionResult implements \JsonSerializable
* contain original exception.
*
* @api
* @var \GraphQL\Error\Error[]
* @var Error[]
*/
public $errors;
/**
* User-defined serializable array of extensions included in serialized result.
* Conforms to
*
* @api
* @var array
* @var mixed[]
*/
public $extensions;
/**
* @var callable
*/
/** @var callable */
private $errorFormatter;
/**
* @var callable
*/
/** @var callable */
private $errorsHandler;
/**
* @param array $data
* @param array $errors
* @param array $extensions
* @param mixed[] $data
* @param Error[] $errors
* @param mixed[] $extensions
*/
public function __construct(array $data = null, array $errors = [], array $extensions = [])
public function __construct($data = null, array $errors = [], array $extensions = [])
{
$this->data = $data;
$this->errors = $errors;
$this->data = $data;
$this->errors = $errors;
$this->extensions = $extensions;
}
@ -77,13 +78,14 @@ class ExecutionResult implements \JsonSerializable
* // ... other keys
* );
*
* @return self
*
* @api
* @param callable $errorFormatter
* @return $this
*/
public function setErrorFormatter(callable $errorFormatter)
{
$this->errorFormatter = $errorFormatter;
return $this;
}
@ -97,16 +99,25 @@ class ExecutionResult implements \JsonSerializable
* return array_map($formatter, $errors);
* }
*
* @return self
*
* @api
* @param callable $handler
* @return $this
*/
public function setErrorsHandler(callable $handler)
{
$this->errorsHandler = $handler;
return $this;
}
/**
* @return mixed[]
*/
public function jsonSerialize()
{
return $this->toArray();
}
/**
* Converts GraphQL query result to spec-compliant serializable array using provided
* errors handler and formatter.
@ -117,42 +128,35 @@ class ExecutionResult implements \JsonSerializable
* $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from
* GraphQL\Error\Debug
*
* @api
* @param bool|int $debug
* @return array
*
* @return mixed[]
*
* @api
*/
public function toArray($debug = false)
{
$result = [];
if (!empty($this->errors)) {
$errorsHandler = $this->errorsHandler ?: function(array $errors, callable $formatter) {
if (! empty($this->errors)) {
$errorsHandler = $this->errorsHandler ?: static function (array $errors, callable $formatter) {
return array_map($formatter, $errors);
};
$result['errors'] = $errorsHandler(
$this->errors,
FormattedError::prepareFormatter($this->errorFormatter, $debug)
);
}
if (null !== $this->data) {
if ($this->data !== null) {
$result['data'] = $this->data;
}
if (!empty($this->extensions)) {
$result['extensions'] = (array) $this->extensions;
if (! empty($this->extensions)) {
$result['extensions'] = $this->extensions;
}
return $result;
}
/**
* Part of \JsonSerializable interface
*
* @return array
*/
public function jsonSerialize()
{
return $this->toArray();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor;
use GraphQL\Executor\Promise\Promise;
interface ExecutorImplementation
{
/**
* Returns promise of {@link ExecutionResult}. Promise should always resolve, never reject.
*/
public function doExecute() : Promise;
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use GraphQL\Executor\Promise\Promise;
@ -6,6 +9,9 @@ use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Utils\Utils;
use React\Promise\Promise as ReactPromise;
use React\Promise\PromiseInterface as ReactPromiseInterface;
use function React\Promise\all;
use function React\Promise\reject;
use function React\Promise\resolve;
class ReactPromiseAdapter implements PromiseAdapter
{
@ -28,10 +34,11 @@ class ReactPromiseAdapter implements PromiseAdapter
/**
* @inheritdoc
*/
public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null)
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null)
{
/** @var $adoptedPromise ReactPromiseInterface */
/** @var ReactPromiseInterface $adoptedPromise */
$adoptedPromise = $promise->adoptedPromise;
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
}
@ -41,6 +48,7 @@ class ReactPromiseAdapter implements PromiseAdapter
public function create(callable $resolver)
{
$promise = new ReactPromise($resolver);
return new Promise($promise, $this);
}
@ -49,7 +57,8 @@ class ReactPromiseAdapter implements PromiseAdapter
*/
public function createFulfilled($value = null)
{
$promise = \React\Promise\resolve($value);
$promise = resolve($value);
return new Promise($promise, $this);
}
@ -58,7 +67,8 @@ class ReactPromiseAdapter implements PromiseAdapter
*/
public function createRejected($reason)
{
$promise = \React\Promise\reject($reason);
$promise = reject($reason);
return new Promise($promise, $this);
}
@ -68,11 +78,14 @@ class ReactPromiseAdapter implements PromiseAdapter
public function all(array $promisesOrValues)
{
// TODO: rework with generators when PHP minimum required version is changed to 5.5+
$promisesOrValues = Utils::map($promisesOrValues, function ($item) {
return $item instanceof Promise ? $item->adoptedPromise : $item;
});
$promisesOrValues = Utils::map(
$promisesOrValues,
static function ($item) {
return $item instanceof Promise ? $item->adoptedPromise : $item;
}
);
$promise = \React\Promise\all($promisesOrValues)->then(function($values) use ($promisesOrValues) {
$promise = all($promisesOrValues)->then(static function ($values) use ($promisesOrValues) {
$orderedResults = [];
foreach ($promisesOrValues as $key => $value) {
@ -81,6 +94,7 @@ class ReactPromiseAdapter implements PromiseAdapter
return $orderedResults;
});
return new Promise($promise, $this);
}
}

View file

@ -1,117 +1,164 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use Exception;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Utils\Utils;
use SplQueue;
use Throwable;
use function is_object;
use function method_exists;
/**
* Class SyncPromise
*
* Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
* (using queue to defer promises execution)
*
* @package GraphQL\Executor\Promise\Adapter
*/
class SyncPromise
{
const PENDING = 'pending';
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
const REJECTED = 'rejected';
/**
* @var \SplQueue
*/
/** @var SplQueue */
public static $queue;
public static function getQueue()
{
return self::$queue ?: self::$queue = new \SplQueue();
}
/** @var string */
public $state = self::PENDING;
public static function runQueue()
/** @var ExecutionResult|Throwable */
public $result;
/**
* Promises created in `then` method of this promise and awaiting for resolution of this promise
*
* @var mixed[][]
*/
private $waiting = [];
public static function runQueue() : void
{
$q = self::$queue;
while ($q && !$q->isEmpty()) {
while ($q !== null && ! $q->isEmpty()) {
$task = $q->dequeue();
$task();
}
}
public $state = self::PENDING;
public $result;
/**
* Promises created in `then` method of this promise and awaiting for resolution of this promise
* @var array
*/
private $waiting = [];
public function reject($reason)
{
if (!$reason instanceof \Exception && !$reason instanceof \Throwable) {
throw new \Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
}
switch ($this->state) {
case self::PENDING:
$this->state = self::REJECTED;
$this->result = $reason;
$this->enqueueWaitingPromises();
break;
case self::REJECTED:
if ($reason !== $this->result) {
throw new \Exception("Cannot change rejection reason");
}
break;
case self::FULFILLED:
throw new \Exception("Cannot reject fulfilled promise");
}
return $this;
}
public function resolve($value)
public function resolve($value) : self
{
switch ($this->state) {
case self::PENDING:
if ($value === $this) {
throw new \Exception("Cannot resolve promise with self");
throw new Exception('Cannot resolve promise with self');
}
if (is_object($value) && method_exists($value, 'then')) {
$value->then(
function($resolvedValue) {
function ($resolvedValue) {
$this->resolve($resolvedValue);
},
function($reason) {
function ($reason) {
$this->reject($reason);
}
);
return $this;
}
$this->state = self::FULFILLED;
$this->state = self::FULFILLED;
$this->result = $value;
$this->enqueueWaitingPromises();
break;
case self::FULFILLED:
if ($this->result !== $value) {
throw new \Exception("Cannot change value of fulfilled promise");
throw new Exception('Cannot change value of fulfilled promise');
}
break;
case self::REJECTED:
throw new \Exception("Cannot resolve rejected promise");
throw new Exception('Cannot resolve rejected promise');
}
return $this;
}
public function then(callable $onFulfilled = null, callable $onRejected = null)
public function reject($reason) : self
{
if ($this->state === self::REJECTED && !$onRejected) {
if (! $reason instanceof Exception && ! $reason instanceof Throwable) {
throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
}
switch ($this->state) {
case self::PENDING:
$this->state = self::REJECTED;
$this->result = $reason;
$this->enqueueWaitingPromises();
break;
case self::REJECTED:
if ($reason !== $this->result) {
throw new Exception('Cannot change rejection reason');
}
break;
case self::FULFILLED:
throw new Exception('Cannot reject fulfilled promise');
}
return $this;
}
private function enqueueWaitingPromises() : void
{
Utils::invariant(
$this->state !== self::PENDING,
'Cannot enqueue derived promises when parent is still pending'
);
foreach ($this->waiting as $descriptor) {
self::getQueue()->enqueue(function () use ($descriptor) {
/** @var self $promise */
[$promise, $onFulfilled, $onRejected] = $descriptor;
if ($this->state === self::FULFILLED) {
try {
$promise->resolve($onFulfilled === null ? $this->result : $onFulfilled($this->result));
} catch (Exception $e) {
$promise->reject($e);
} catch (Throwable $e) {
$promise->reject($e);
}
} elseif ($this->state === self::REJECTED) {
try {
if ($onRejected === null) {
$promise->reject($this->result);
} else {
$promise->resolve($onRejected($this->result));
}
} catch (Exception $e) {
$promise->reject($e);
} catch (Throwable $e) {
$promise->reject($e);
}
}
});
}
$this->waiting = [];
}
public static function getQueue() : SplQueue
{
return self::$queue ?: self::$queue = new SplQueue();
}
public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
{
if ($this->state === self::REJECTED && $onRejected === null) {
return $this;
}
if ($this->state === self::FULFILLED && !$onFulfilled) {
if ($this->state === self::FULFILLED && $onFulfilled === null) {
return $this;
}
$tmp = new self();
$tmp = new self();
$this->waiting[] = [$tmp, $onFulfilled, $onRejected];
if ($this->state !== self::PENDING) {
@ -120,39 +167,4 @@ class SyncPromise
return $tmp;
}
private function enqueueWaitingPromises()
{
Utils::invariant($this->state !== self::PENDING, 'Cannot enqueue derived promises when parent is still pending');
foreach ($this->waiting as $descriptor) {
self::getQueue()->enqueue(function () use ($descriptor) {
/** @var $promise self */
list($promise, $onFulfilled, $onRejected) = $descriptor;
if ($this->state === self::FULFILLED) {
try {
$promise->resolve($onFulfilled ? $onFulfilled($this->result) : $this->result);
} catch (\Exception $e) {
$promise->reject($e);
} catch (\Throwable $e) {
$promise->reject($e);
}
} else if ($this->state === self::REJECTED) {
try {
if ($onRejected) {
$promise->resolve($onRejected($this->result));
} else {
$promise->reject($this->result);
}
} catch (\Exception $e) {
$promise->reject($e);
} catch (\Throwable $e) {
$promise->reject($e);
}
}
});
}
$this->waiting = [];
}
}

View file

@ -1,19 +1,22 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use Exception;
use GraphQL\Deferred;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Utils\Utils;
use Throwable;
use function count;
/**
* Class SyncPromiseAdapter
*
* Allows changing order of field resolution even in sync environments
* (by leveraging queue of deferreds and promises)
*
* @package GraphQL\Executor\Promise\Adapter
*/
class SyncPromiseAdapter implements PromiseAdapter
{
@ -30,20 +33,22 @@ class SyncPromiseAdapter implements PromiseAdapter
*/
public function convertThenable($thenable)
{
if (!$thenable instanceof Deferred) {
if (! $thenable instanceof Deferred) {
throw new InvariantViolation('Expected instance of GraphQL\Deferred, got ' . Utils::printSafe($thenable));
}
return new Promise($thenable->promise, $this);
}
/**
* @inheritdoc
*/
public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null)
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null)
{
/** @var SyncPromise $promise */
$promise = $promise->adoptedPromise;
return new Promise($promise->then($onFulfilled, $onRejected), $this);
/** @var SyncPromise $adoptedPromise */
$adoptedPromise = $promise->adoptedPromise;
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
}
/**
@ -55,12 +60,18 @@ class SyncPromiseAdapter implements PromiseAdapter
try {
$resolver(
[$promise, 'resolve'],
[$promise, 'reject']
[
$promise,
'resolve',
],
[
$promise,
'reject',
]
);
} catch (\Exception $e) {
} catch (Exception $e) {
$promise->reject($e);
} catch (\Throwable $e) {
} catch (Throwable $e) {
$promise->reject($e);
}
@ -73,6 +84,7 @@ class SyncPromiseAdapter implements PromiseAdapter
public function createFulfilled($value = null)
{
$promise = new SyncPromise();
return new Promise($promise->resolve($value), $this);
}
@ -82,6 +94,7 @@ class SyncPromiseAdapter implements PromiseAdapter
public function createRejected($reason)
{
$promise = new SyncPromise();
return new Promise($promise->reject($reason), $this);
}
@ -92,20 +105,22 @@ class SyncPromiseAdapter implements PromiseAdapter
{
$all = new SyncPromise();
$total = count($promisesOrValues);
$count = 0;
$total = count($promisesOrValues);
$count = 0;
$result = [];
foreach ($promisesOrValues as $index => $promiseOrValue) {
if ($promiseOrValue instanceof Promise) {
$result[$index] = null;
$promiseOrValue->then(
function($value) use ($index, &$count, $total, &$result, $all) {
static function ($value) use ($index, &$count, $total, &$result, $all) {
$result[$index] = $value;
$count++;
if ($count >= $total) {
$all->resolve($result);
if ($count < $total) {
return;
}
$all->resolve($result);
},
[$all, 'reject']
);
@ -117,24 +132,23 @@ class SyncPromiseAdapter implements PromiseAdapter
if ($count === $total) {
$all->resolve($result);
}
return new Promise($all, $this);
}
/**
* Synchronously wait when promise completes
*
* @param Promise $promise
* @return mixed
* @return ExecutionResult
*/
public function wait(Promise $promise)
{
$this->beforeWait($promise);
$dfdQueue = Deferred::getQueue();
$dfdQueue = Deferred::getQueue();
$promiseQueue = SyncPromise::getQueue();
while (
$promise->adoptedPromise->state === SyncPromise::PENDING &&
!($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
while ($promise->adoptedPromise->state === SyncPromise::PENDING &&
! ($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
) {
Deferred::runQueue();
SyncPromise::runQueue();
@ -146,17 +160,17 @@ class SyncPromiseAdapter implements PromiseAdapter
if ($syncPromise->state === SyncPromise::FULFILLED) {
return $syncPromise->result;
} else if ($syncPromise->state === SyncPromise::REJECTED) {
}
if ($syncPromise->state === SyncPromise::REJECTED) {
throw $syncPromise->result;
}
throw new InvariantViolation("Could not resolve promise");
throw new InvariantViolation('Could not resolve promise');
}
/**
* Execute just before starting to run promise completion
*
* @param Promise $promise
*/
protected function beforeWait(Promise $promise)
{
@ -164,8 +178,6 @@ class SyncPromiseAdapter implements PromiseAdapter
/**
* Execute while running promise completion
*
* @param Promise $promise
*/
protected function onWait(Promise $promise)
{

View file

@ -1,38 +1,39 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use GraphQL\Utils\Utils;
use React\Promise\Promise as ReactPromise;
/**
* Convenience wrapper for promises represented by Promise Adapter
*/
class Promise
{
private $adapter;
/** @var SyncPromise|ReactPromise */
public $adoptedPromise;
/** @var PromiseAdapter */
private $adapter;
/**
* Promise constructor.
*
* @param mixed $adoptedPromise
* @param PromiseAdapter $adapter
*/
public function __construct($adoptedPromise, PromiseAdapter $adapter)
{
Utils::invariant(!$adoptedPromise instanceof self, 'Expecting promise from adapted system, got ' . __CLASS__);
Utils::invariant(! $adoptedPromise instanceof self, 'Expecting promise from adapted system, got ' . self::class);
$this->adapter = $adapter;
$this->adapter = $adapter;
$this->adoptedPromise = $adoptedPromise;
}
/**
* @param callable|null $onFulfilled
* @param callable|null $onRejected
*
* @return Promise
*/
public function then(callable $onFulfilled = null, callable $onRejected = null)
public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
{
return $this->adapter->then($this, $onFulfilled, $onRejected);
}

View file

@ -1,6 +1,11 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise;
use Throwable;
/**
* Provides a means for integration of async PHP platforms ([related docs](data-fetching.md#async-php))
*/
@ -9,18 +14,22 @@ interface PromiseAdapter
/**
* Return true if the value is a promise or a deferred of the underlying platform
*
* @api
* @param mixed $value
*
* @return bool
*
* @api
*/
public function isThenable($value);
/**
* Converts thenable of the underlying platform into GraphQL\Executor\Promise\Promise instance
*
* @api
* @param object $thenable
*
* @return Promise
*
* @api
*/
public function convertThenable($thenable);
@ -28,14 +37,11 @@ interface PromiseAdapter
* Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described
* in Promises/A+ specs. Then returns new wrapped instance of GraphQL\Executor\Promise\Promise.
*
* @api
* @param Promise $promise
* @param callable|null $onFulfilled
* @param callable|null $onRejected
*
* @return Promise
*
* @api
*/
public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null);
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null);
/**
* Creates a Promise
@ -43,18 +49,20 @@ interface PromiseAdapter
* Expected resolver signature:
* function(callable $resolve, callable $reject)
*
* @api
* @param callable $resolver
* @return Promise
*
* @api
*/
public function create(callable $resolver);
/**
* Creates a fulfilled Promise for a value if the value is not a promise.
*
* @api
* @param mixed $value
*
* @return Promise
*
* @api
*/
public function createFulfilled($value = null);
@ -62,9 +70,11 @@ interface PromiseAdapter
* Creates a rejected promise for a reason if the reason is not a promise. If
* the provided reason is a promise, then it is returned as-is.
*
* @api
* @param \Throwable $reason
* @param Throwable $reason
*
* @return Promise
*
* @api
*/
public function createRejected($reason);
@ -72,9 +82,11 @@ interface PromiseAdapter
* Given an array of promises (or values), returns a promise that is fulfilled when all the
* items in the array are fulfilled.
*
* @api
* @param array $promisesOrValues Promises or values.
* @param Promise[]|mixed[] $promisesOrValues Promises or values.
*
* @return Promise
*
* @api
*/
public function all(array $promisesOrValues);
}

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,10 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\EnumValueDefinitionNode;
@ -11,23 +12,27 @@ use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\Printer;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\LeafType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\AST;
use GraphQL\Utils\TypeInfo;
use GraphQL\Utils\Utils;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Utils\Value;
use stdClass;
use Throwable;
use function array_key_exists;
use function array_map;
use function sprintf;
class Values
{
@ -36,134 +41,78 @@ class Values
* variable definitions and arbitrary input. If the input cannot be coerced
* to match the variable definitions, a Error will be thrown.
*
* @param Schema $schema
* @param VariableDefinitionNode[] $definitionNodes
* @param array $inputs
* @return array
* @throws Error
* @param VariableDefinitionNode[] $varDefNodes
* @param mixed[] $inputs
*
* @return mixed[]
*/
public static function getVariableValues(Schema $schema, $definitionNodes, array $inputs)
public static function getVariableValues(Schema $schema, $varDefNodes, array $inputs)
{
$errors = [];
$coercedValues = [];
foreach ($definitionNodes as $definitionNode) {
$varName = $definitionNode->variable->name->value;
$varType = TypeInfo::typeFromAST($schema, $definitionNode->type);
foreach ($varDefNodes as $varDefNode) {
$varName = $varDefNode->variable->name->value;
/** @var InputType|Type $varType */
$varType = TypeInfo::typeFromAST($schema, $varDefNode->type);
if (!Type::isInputType($varType)) {
throw new Error(
'Variable "$'.$varName.'" expected value of type ' .
'"' . Printer::doPrint($definitionNode->type) . '" which cannot be used as an input type.',
[$definitionNode->type]
if (Type::isInputType($varType)) {
if (array_key_exists($varName, $inputs)) {
$value = $inputs[$varName];
$coerced = Value::coerceValue($value, $varType, $varDefNode);
/** @var Error[] $coercionErrors */
$coercionErrors = $coerced['errors'];
if (empty($coercionErrors)) {
$coercedValues[$varName] = $coerced['value'];
} else {
$messagePrelude = sprintf(
'Variable "$%s" got invalid value %s; ',
$varName,
Utils::printSafeJson($value)
);
foreach ($coercionErrors as $error) {
$errors[] = new Error(
$messagePrelude . $error->getMessage(),
$error->getNodes(),
$error->getSource(),
$error->getPositions(),
$error->getPath(),
$error,
$error->getExtensions()
);
}
}
} else {
if ($varType instanceof NonNull) {
$errors[] = new Error(
sprintf(
'Variable "$%s" of required type "%s" was not provided.',
$varName,
$varType
),
[$varDefNode]
);
} elseif ($varDefNode->defaultValue !== 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]
);
}
if (!array_key_exists($varName, $inputs)) {
$defaultValue = $definitionNode->defaultValue;
if ($defaultValue) {
$coercedValues[$varName] = AST::valueFromAST($defaultValue, $varType);
}
if ($varType instanceof NonNull) {
throw new Error(
'Variable "$'.$varName .'" of required type ' .
'"'. Utils::printSafe($varType) . '" was not provided.',
[$definitionNode]
);
}
} else {
$value = $inputs[$varName];
$errors = self::isValidPHPValue($value, $varType);
if (!empty($errors)) {
$message = "\n" . implode("\n", $errors);
throw new Error(
'Variable "$' . $varName . '" got invalid value ' .
json_encode($value) . '.' . $message,
[$definitionNode]
);
}
$coercedValue = self::coerceValue($varType, $value);
Utils::invariant($coercedValue !== Utils::undefined(), 'Should have reported error.');
$coercedValues[$varName] = $coercedValue;
}
}
return $coercedValues;
}
/**
* Prepares an object map of argument values given a list of argument
* definitions and list of argument AST nodes.
*
* @param FieldDefinition|Directive $def
* @param FieldNode|\GraphQL\Language\AST\DirectiveNode $node
* @param $variableValues
* @return array
* @throws Error
*/
public static function getArgumentValues($def, $node, $variableValues = null)
{
$argDefs = $def->args;
$argNodes = $node->arguments;
if (!$argDefs || null === $argNodes) {
return [];
}
$coercedValues = [];
$undefined = Utils::undefined();
/** @var ArgumentNode[] $argNodeMap */
$argNodeMap = $argNodes ? Utils::keyMap($argNodes, function (ArgumentNode $arg) {
return $arg->name->value;
}) : [];
foreach ($argDefs as $argDef) {
$name = $argDef->name;
$argType = $argDef->getType();
$argumentNode = isset($argNodeMap[$name]) ? $argNodeMap[$name] : null;
if (!$argumentNode) {
if ($argDef->defaultValueExists()) {
$coercedValues[$name] = $argDef->defaultValue;
} else if ($argType instanceof NonNull) {
throw new Error(
'Argument "' . $name . '" of required type ' .
'"' . Utils::printSafe($argType) . '" was not provided.',
[$node]
);
}
} else if ($argumentNode->value instanceof VariableNode) {
$variableName = $argumentNode->value->name->value;
if ($variableValues && array_key_exists($variableName, $variableValues)) {
// Note: this does not check that this variable value is correct.
// This assumes that this query has been validated and the variable
// usage here is of the correct type.
$coercedValues[$name] = $variableValues[$variableName];
} else if ($argDef->defaultValueExists()) {
$coercedValues[$name] = $argDef->defaultValue;
} else if ($argType instanceof NonNull) {
throw new Error(
'Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' .
'provided the variable "$' . $variableName . '" which was not provided ' .
'a runtime value.',
[ $argumentNode->value ]
);
}
} else {
$valueNode = $argumentNode->value;
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
if ($coercedValue === $undefined) {
$errors = DocumentValidator::isValidLiteralValue($argType, $valueNode);
$message = !empty($errors) ? ("\n" . implode("\n", $errors)) : '';
throw new Error(
'Argument "' . $name . '" got invalid value ' . Printer::doPrint($valueNode) . '.' . $message,
[ $argumentNode->value ]
);
}
$coercedValues[$name] = $coercedValue;
}
if (! empty($errors)) {
return [$errors, null];
}
return $coercedValues;
return [null, $coercedValues];
}
/**
@ -173,213 +122,158 @@ class Values
*
* If the directive does not exist on the node, returns undefined.
*
* @param Directive $directiveDef
* @param FragmentSpreadNode | FieldNode | InlineFragmentNode | EnumValueDefinitionNode | FieldDefinitionNode $node
* @param array|null $variableValues
* @param FragmentSpreadNode|FieldNode|InlineFragmentNode|EnumValueDefinitionNode|FieldDefinitionNode $node
* @param mixed[]|null $variableValues
*
* @return array|null
* @return mixed[]|null
*/
public static function getDirectiveValues(Directive $directiveDef, $node, $variableValues = null)
{
if (isset($node->directives) && $node->directives instanceof NodeList) {
$directiveNode = Utils::find($node->directives, function(DirectiveNode $directive) use ($directiveDef) {
return $directive->name->value === $directiveDef->name;
});
$directiveNode = Utils::find(
$node->directives,
static function (DirectiveNode $directive) use ($directiveDef) {
return $directive->name->value === $directiveDef->name;
}
);
if ($directiveNode) {
if ($directiveNode !== null) {
return self::getArgumentValues($directiveDef, $directiveNode, $variableValues);
}
}
return null;
}
/**
* Prepares an object map of argument values given a list of argument
* definitions and list of argument AST nodes.
*
* @param FieldDefinition|Directive $def
* @param FieldNode|DirectiveNode $node
* @param mixed[] $variableValues
*
* @return mixed[]
*
* @throws Error
*/
public static function getArgumentValues($def, $node, $variableValues = null)
{
if (empty($def->args)) {
return [];
}
$argumentNodes = $node->arguments;
if (empty($argumentNodes)) {
return [];
}
$argumentValueMap = [];
foreach ($argumentNodes as $argumentNode) {
$argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
}
return static::getArgumentValuesForMap($def, $argumentValueMap, $variableValues, $node);
}
/**
* @param FieldDefinition|Directive $fieldDefinition
* @param ArgumentNode[] $argumentValueMap
* @param mixed[] $variableValues
* @param Node|null $referenceNode
*
* @return mixed[]
*
* @throws Error
*/
public static function getArgumentValuesForMap($fieldDefinition, $argumentValueMap, $variableValues = null, $referenceNode = null)
{
$argumentDefinitions = $fieldDefinition->args;
$coercedValues = [];
foreach ($argumentDefinitions as $argumentDefinition) {
$name = $argumentDefinition->name;
$argType = $argumentDefinition->getType();
$argumentValueNode = $argumentValueMap[$name] ?? null;
if ($argumentValueNode === null) {
if ($argumentDefinition->defaultValueExists()) {
$coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ($argType instanceof NonNull) {
throw new Error(
'Argument "' . $name . '" of required type ' .
'"' . Utils::printSafe($argType) . '" was not provided.',
$referenceNode
);
}
} elseif ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value;
if ($variableValues !== null && array_key_exists($variableName, $variableValues)) {
// Note: this does not check that this variable value is correct.
// This assumes that this query has been validated and the variable
// usage here is of the correct type.
$coercedValues[$name] = $variableValues[$variableName];
} elseif ($argumentDefinition->defaultValueExists()) {
$coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ($argType instanceof NonNull) {
throw new Error(
'Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' .
'provided the variable "$' . $variableName . '" which was not provided ' .
'a runtime value.',
[$argumentValueNode]
);
}
} else {
$valueNode = $argumentValueNode;
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
if (Utils::isInvalid($coercedValue)) {
// Note: ValuesOfCorrectType validation should catch this before
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new Error(
'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.',
[$argumentValueNode]
);
}
$coercedValues[$name] = $coercedValue;
}
}
return $coercedValues;
}
/**
* @deprecated as of 8.0 (Moved to \GraphQL\Utils\AST::valueFromAST)
*
* @param $valueNode
* @param InputType $type
* @param null $variables
* @return array|null|\stdClass
* @param ValueNode $valueNode
* @param mixed[]|null $variables
*
* @return mixed[]|stdClass|null
*/
public static function valueFromAST($valueNode, InputType $type, $variables = null)
public static function valueFromAST($valueNode, InputType $type, ?array $variables = null)
{
return AST::valueFromAST($valueNode, $type, $variables);
}
/**
* Given a PHP value and a GraphQL type, determine if the value will be
* accepted for that type. This is primarily useful for validating the
* runtime values of query variables.
* @deprecated as of 0.12 (Use coerceValue() directly for richer information)
*
* @param $value
* @param InputType $type
* @return array
* @param mixed[] $value
*
* @return string[]
*/
public static function isValidPHPValue($value, InputType $type)
{
// A value must be provided if the type is non-null.
if ($type instanceof NonNull) {
if (null === $value) {
return ['Expected "' . Utils::printSafe($type) . '", found null.'];
}
return self::isValidPHPValue($value, $type->getWrappedType());
}
$errors = Value::coerceValue($value, $type)['errors'];
if (null === $value) {
return [];
}
// Lists accept a non-list value as a list of one.
if ($type instanceof ListOfType) {
$itemType = $type->getWrappedType();
if (is_array($value)) {
$tmp = [];
foreach ($value as $index => $item) {
$errors = self::isValidPHPValue($item, $itemType);
$tmp = array_merge($tmp, Utils::map($errors, function ($error) use ($index) {
return "In element #$index: $error";
}));
}
return $tmp;
}
return self::isValidPHPValue($value, $itemType);
}
// Input objects check each defined field.
if ($type instanceof InputObjectType) {
if (!is_object($value) && !is_array($value)) {
return ["Expected \"{$type->name}\", found not an object."];
}
$fields = $type->getFields();
$errors = [];
// Ensure every provided field is defined.
$props = is_object($value) ? get_object_vars($value) : $value;
foreach ($props as $providedField => $tmp) {
if (!isset($fields[$providedField])) {
$errors[] = "In field \"{$providedField}\": Unknown field.";
}
}
// Ensure every defined field is valid.
foreach ($fields as $fieldName => $tmp) {
$newErrors = self::isValidPHPValue(isset($value[$fieldName]) ? $value[$fieldName] : null, $fields[$fieldName]->getType());
$errors = array_merge(
$errors,
Utils::map($newErrors, function ($error) use ($fieldName) {
return "In field \"{$fieldName}\": {$error}";
})
);
}
return $errors;
}
if ($type instanceof LeafType) {
try {
// Scalar/Enum input checks to ensure the type can parse the value to
// a non-null value.
$parseResult = $type->parseValue($value);
if (null === $parseResult && !$type->isValidValue($value)) {
$v = Utils::printSafe($value);
return [
"Expected type \"{$type->name}\", found $v."
];
}
return [];
} catch (\Exception $e) {
return [
"Expected type \"{$type->name}\", found " . Utils::printSafe($value) . ': ' .
$e->getMessage()
];
} catch (\Throwable $e) {
return [
"Expected type \"{$type->name}\", found " . Utils::printSafe($value) . ': ' .
$e->getMessage()
];
}
}
throw new InvariantViolation('Must be input type');
}
/**
* Given a type and any value, return a runtime value coerced to match the type.
*/
private static function coerceValue(Type $type, $value)
{
$undefined = Utils::undefined();
if ($value === $undefined) {
return $undefined;
}
if ($type instanceof NonNull) {
if ($value === null) {
// Intentionally return no value.
return $undefined;
}
return self::coerceValue($type->getWrappedType(), $value);
}
if (null === $value) {
return null;
}
if ($type instanceof ListOfType) {
$itemType = $type->getWrappedType();
if (is_array($value) || $value instanceof \Traversable) {
$coercedValues = [];
foreach ($value as $item) {
$itemValue = self::coerceValue($itemType, $item);
if ($undefined === $itemValue) {
// Intentionally return no value.
return $undefined;
}
$coercedValues[] = $itemValue;
}
return $coercedValues;
} else {
$coercedValue = self::coerceValue($itemType, $value);
if ($coercedValue === $undefined) {
// Intentionally return no value.
return $undefined;
}
return [$coercedValue];
}
}
if ($type instanceof InputObjectType) {
$coercedObj = [];
$fields = $type->getFields();
foreach ($fields as $fieldName => $field) {
if (!array_key_exists($fieldName, $value)) {
if ($field->defaultValueExists()) {
$coercedObj[$fieldName] = $field->defaultValue;
} else if ($field->getType() instanceof NonNull) {
// Intentionally return no value.
return $undefined;
}
continue;
}
$fieldValue = self::coerceValue($field->getType(), $value[$fieldName]);
if ($fieldValue === $undefined) {
// Intentionally return no value.
return $undefined;
}
$coercedObj[$fieldName] = $fieldValue;
}
return $coercedObj;
}
if ($type instanceof LeafType) {
$parsed = $type->parseValue($value);
if (null === $parsed) {
// null or invalid values represent a failure to parse correctly,
// in which case no value is returned.
return $undefined;
}
return $parsed;
}
throw new InvariantViolation('Must be input type');
return $errors
? array_map(
static function (Throwable $error) {
return $error->getMessage();
},
$errors
)
: [];
}
}

View file

@ -0,0 +1,283 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use Generator;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DefinitionNode;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use function sprintf;
/**
* @internal
*/
class Collector
{
/** @var Schema */
private $schema;
/** @var Runtime */
private $runtime;
/** @var OperationDefinitionNode|null */
public $operation = null;
/** @var FragmentDefinitionNode[] */
public $fragments = [];
/** @var ObjectType|null */
public $rootType;
/** @var FieldNode[][] */
private $fields;
/** @var string[] */
private $visitedFragments;
public function __construct(Schema $schema, Runtime $runtime)
{
$this->schema = $schema;
$this->runtime = $runtime;
}
public function initialize(DocumentNode $documentNode, ?string $operationName = null)
{
$hasMultipleAssumedOperations = false;
foreach ($documentNode->definitions as $definitionNode) {
/** @var DefinitionNode|Node $definitionNode */
if ($definitionNode instanceof OperationDefinitionNode) {
/** @var OperationDefinitionNode $definitionNode */
if ($operationName === null && $this->operation !== null) {
$hasMultipleAssumedOperations = true;
}
if ($operationName === null ||
(isset($definitionNode->name) && $definitionNode->name->value === $operationName)
) {
$this->operation = $definitionNode;
}
} elseif ($definitionNode instanceof FragmentDefinitionNode) {
/** @var FragmentDefinitionNode $definitionNode */
$this->fragments[$definitionNode->name->value] = $definitionNode;
}
}
if ($this->operation === null) {
if ($operationName !== null) {
$this->runtime->addError(new Error(sprintf('Unknown operation named "%s".', $operationName)));
} else {
$this->runtime->addError(new Error('Must provide an operation.'));
}
return;
}
if ($hasMultipleAssumedOperations) {
$this->runtime->addError(new Error('Must provide operation name if query contains multiple operations.'));
return;
}
if ($this->operation->operation === 'query') {
$this->rootType = $this->schema->getQueryType();
} elseif ($this->operation->operation === 'mutation') {
$this->rootType = $this->schema->getMutationType();
} elseif ($this->operation->operation === 'subscription') {
$this->rootType = $this->schema->getSubscriptionType();
} else {
$this->runtime->addError(new Error(sprintf('Cannot initialize collector with operation type "%s".', $this->operation->operation)));
}
}
/**
* @return Generator
*/
public function collectFields(ObjectType $runtimeType, ?SelectionSetNode $selectionSet)
{
$this->fields = [];
$this->visitedFragments = [];
$this->doCollectFields($runtimeType, $selectionSet);
foreach ($this->fields as $resultName => $fieldNodes) {
$fieldNode = $fieldNodes[0];
$fieldName = $fieldNode->name->value;
$argumentValueMap = null;
if (! empty($fieldNode->arguments)) {
foreach ($fieldNode->arguments as $argumentNode) {
$argumentValueMap = $argumentValueMap ?? [];
$argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
}
}
if ($fieldName !== Introspection::TYPE_NAME_FIELD_NAME &&
! ($runtimeType === $this->schema->getQueryType() && ($fieldName === Introspection::SCHEMA_FIELD_NAME || $fieldName === Introspection::TYPE_FIELD_NAME)) &&
! $runtimeType->hasField($fieldName)
) {
// do not emit error
continue;
}
yield new CoroutineContextShared($fieldNodes, $fieldName, $resultName, $argumentValueMap);
}
}
private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $selectionSet)
{
if ($selectionSet === null) {
return;
}
foreach ($selectionSet->selections as $selection) {
/** @var FieldNode|FragmentSpreadNode|InlineFragmentNode $selection */
if (! empty($selection->directives)) {
foreach ($selection->directives as $directiveNode) {
if ($directiveNode->name->value === Directive::SKIP_NAME) {
/** @var ValueNode|null $condition */
$condition = null;
foreach ($directiveNode->arguments as $argumentNode) {
if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
$condition = $argumentNode->value;
break;
}
}
if ($condition === null) {
$this->runtime->addError(new Error(
sprintf('@%s directive is missing "%s" argument.', Directive::SKIP_NAME, Directive::IF_ARGUMENT_NAME),
$selection
));
} else {
if ($this->runtime->evaluate($condition, Type::boolean()) === true) {
continue 2; // !!! advances outer loop
}
}
} elseif ($directiveNode->name->value === Directive::INCLUDE_NAME) {
/** @var ValueNode|null $condition */
$condition = null;
foreach ($directiveNode->arguments as $argumentNode) {
if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
$condition = $argumentNode->value;
break;
}
}
if ($condition === null) {
$this->runtime->addError(new Error(
sprintf('@%s directive is missing "%s" argument.', Directive::INCLUDE_NAME, Directive::IF_ARGUMENT_NAME),
$selection
));
} else {
if ($this->runtime->evaluate($condition, Type::boolean()) !== true) {
continue 2; // !!! advances outer loop
}
}
}
}
}
if ($selection instanceof FieldNode) {
/** @var FieldNode $selection */
$resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value;
if (! isset($this->fields[$resultName])) {
$this->fields[$resultName] = [];
}
$this->fields[$resultName][] = $selection;
} elseif ($selection instanceof FragmentSpreadNode) {
/** @var FragmentSpreadNode $selection */
$fragmentName = $selection->name->value;
if (isset($this->visitedFragments[$fragmentName])) {
continue;
}
if (! isset($this->fragments[$fragmentName])) {
$this->runtime->addError(new Error(
sprintf('Fragment "%s" does not exist.', $fragmentName),
$selection
));
continue;
}
$this->visitedFragments[$fragmentName] = true;
$fragmentDefinition = $this->fragments[$fragmentName];
$conditionTypeName = $fragmentDefinition->typeCondition->name->value;
if (! $this->schema->hasType($conditionTypeName)) {
$this->runtime->addError(new Error(
sprintf('Cannot spread fragment "%s", type "%s" does not exist.', $fragmentName, $conditionTypeName),
$selection
));
continue;
}
$conditionType = $this->schema->getType($conditionTypeName);
if ($conditionType instanceof ObjectType) {
if ($runtimeType->name !== $conditionType->name) {
continue;
}
} elseif ($conditionType instanceof AbstractType) {
if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
continue;
}
}
$this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet);
} elseif ($selection instanceof InlineFragmentNode) {
/** @var InlineFragmentNode $selection */
if ($selection->typeCondition !== null) {
$conditionTypeName = $selection->typeCondition->name->value;
if (! $this->schema->hasType($conditionTypeName)) {
$this->runtime->addError(new Error(
sprintf('Cannot spread inline fragment, type "%s" does not exist.', $conditionTypeName),
$selection
));
continue;
}
$conditionType = $this->schema->getType($conditionTypeName);
if ($conditionType instanceof ObjectType) {
if ($runtimeType->name !== $conditionType->name) {
continue;
}
} elseif ($conditionType instanceof AbstractType) {
if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
continue;
}
}
}
$this->doCollectFields($runtimeType, $selection->selectionSet);
}
}
}
}

View file

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
/**
* @internal
*/
class CoroutineContext
{
/** @var CoroutineContextShared */
public $shared;
/** @var ObjectType */
public $type;
/** @var mixed */
public $value;
/** @var object */
public $result;
/** @var string[] */
public $path;
/** @var ResolveInfo|null */
public $resolveInfo;
/** @var string[]|null */
public $nullFence;
/**
* @param mixed $value
* @param object $result
* @param string[] $path
* @param string[]|null $nullFence
*/
public function __construct(
CoroutineContextShared $shared,
ObjectType $type,
$value,
$result,
array $path,
?array $nullFence = null
) {
$this->shared = $shared;
$this->type = $type;
$this->value = $value;
$this->result = $result;
$this->path = $path;
$this->nullFence = $nullFence;
}
}

View file

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
/**
* @internal
*/
class CoroutineContextShared
{
/** @var FieldNode[] */
public $fieldNodes;
/** @var string */
public $fieldName;
/** @var string */
public $resultName;
/** @var ValueNode[]|null */
public $argumentValueMap;
/** @var SelectionSetNode|null */
public $mergedSelectionSet;
/** @var ObjectType|null */
public $typeGuard1;
/** @var callable|null */
public $resolveIfType1;
/** @var mixed */
public $argumentsIfType1;
/** @var ResolveInfo|null */
public $resolveInfoIfType1;
/** @var ObjectType|null */
public $typeGuard2;
/** @var CoroutineContext[]|null */
public $childContextsIfType2;
/**
* @param FieldNode[] $fieldNodes
* @param mixed[]|null $argumentValueMap
*/
public function __construct(array $fieldNodes, string $fieldName, string $resultName, ?array $argumentValueMap)
{
$this->fieldNodes = $fieldNodes;
$this->fieldName = $fieldName;
$this->resultName = $resultName;
$this->argumentValueMap = $argumentValueMap;
}
}

View file

@ -0,0 +1,952 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use Generator;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Error\Warning;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\ExecutorImplementation;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Executor\Values;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\LeafType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use GraphQL\Utils\AST;
use GraphQL\Utils\Utils;
use SplQueue;
use stdClass;
use Throwable;
use function is_array;
use function is_string;
use function sprintf;
class CoroutineExecutor implements Runtime, ExecutorImplementation
{
/** @var object */
private static $undefined;
/** @var Schema */
private $schema;
/** @var callable */
private $fieldResolver;
/** @var PromiseAdapter */
private $promiseAdapter;
/** @var mixed|null */
private $rootValue;
/** @var mixed|null */
private $contextValue;
/** @var mixed|null */
private $rawVariableValues;
/** @var mixed|null */
private $variableValues;
/** @var DocumentNode */
private $documentNode;
/** @var string|null */
private $operationName;
/** @var Collector */
private $collector;
/** @var Error[] */
private $errors;
/** @var SplQueue */
private $queue;
/** @var SplQueue */
private $schedule;
/** @var stdClass */
private $rootResult;
/** @var int */
private $pending;
/** @var callable */
private $doResolve;
public function __construct(
PromiseAdapter $promiseAdapter,
Schema $schema,
DocumentNode $documentNode,
$rootValue,
$contextValue,
$rawVariableValues,
?string $operationName,
callable $fieldResolver
) {
if (self::$undefined === null) {
self::$undefined = Utils::undefined();
}
$this->schema = $schema;
$this->fieldResolver = $fieldResolver;
$this->promiseAdapter = $promiseAdapter;
$this->rootValue = $rootValue;
$this->contextValue = $contextValue;
$this->rawVariableValues = $rawVariableValues;
$this->documentNode = $documentNode;
$this->operationName = $operationName;
}
public static function create(
PromiseAdapter $promiseAdapter,
Schema $schema,
DocumentNode $documentNode,
$rootValue,
$contextValue,
$variableValues,
?string $operationName,
callable $fieldResolver
) {
return new static(
$promiseAdapter,
$schema,
$documentNode,
$rootValue,
$contextValue,
$variableValues,
$operationName,
$fieldResolver
);
}
private static function resultToArray($value, $emptyObjectAsStdClass = true)
{
if ($value instanceof stdClass) {
$array = [];
foreach ($value as $propertyName => $propertyValue) {
$array[$propertyName] = self::resultToArray($propertyValue);
}
if ($emptyObjectAsStdClass && empty($array)) {
return new stdClass();
}
return $array;
}
if (is_array($value)) {
$array = [];
foreach ($value as $key => $item) {
$array[$key] = self::resultToArray($item);
}
return $array;
}
return $value;
}
public function doExecute() : Promise
{
$this->rootResult = new stdClass();
$this->errors = [];
$this->queue = new SplQueue();
$this->schedule = new SplQueue();
$this->pending = 0;
$this->collector = new Collector($this->schema, $this);
$this->collector->initialize($this->documentNode, $this->operationName);
if (! empty($this->errors)) {
return $this->promiseAdapter->createFulfilled($this->finishExecute(null, $this->errors));
}
[$errors, $coercedVariableValues] = Values::getVariableValues(
$this->schema,
$this->collector->operation->variableDefinitions ?: [],
$this->rawVariableValues ?: []
);
if (! empty($errors)) {
return $this->promiseAdapter->createFulfilled($this->finishExecute(null, $errors));
}
$this->variableValues = $coercedVariableValues;
foreach ($this->collector->collectFields($this->collector->rootType, $this->collector->operation->selectionSet) as $shared) {
/** @var CoroutineContextShared $shared */
// !!! assign to keep object keys sorted
$this->rootResult->{$shared->resultName} = null;
$ctx = new CoroutineContext(
$shared,
$this->collector->rootType,
$this->rootValue,
$this->rootResult,
[$shared->resultName]
);
$fieldDefinition = $this->findFieldDefinition($ctx);
if (! $fieldDefinition->getType() instanceof NonNull) {
$ctx->nullFence = [$shared->resultName];
}
if ($this->collector->operation->operation === 'mutation' && ! $this->queue->isEmpty()) {
$this->schedule->enqueue($ctx);
} else {
$this->queue->enqueue(new Strand($this->spawn($ctx)));
}
}
$this->run();
if ($this->pending > 0) {
return $this->promiseAdapter->create(function (callable $resolve) {
$this->doResolve = $resolve;
});
}
return $this->promiseAdapter->createFulfilled($this->finishExecute($this->rootResult, $this->errors));
}
/**
* @param object|null $value
* @param Error[] $errors
*/
private function finishExecute($value, array $errors) : ExecutionResult
{
$this->rootResult = null;
$this->errors = null;
$this->queue = null;
$this->schedule = null;
$this->pending = null;
$this->collector = null;
$this->variableValues = null;
if ($value !== null) {
$value = self::resultToArray($value, false);
}
return new ExecutionResult($value, $errors);
}
/**
* @internal
*/
public function evaluate(ValueNode $valueNode, InputType $type)
{
return AST::valueFromAST($valueNode, $type, $this->variableValues);
}
/**
* @internal
*/
public function addError($error)
{
$this->errors[] = $error;
}
private function run()
{
RUN:
while (! $this->queue->isEmpty()) {
/** @var Strand $strand */
$strand = $this->queue->dequeue();
try {
if ($strand->success !== null) {
RESUME:
if ($strand->success) {
$strand->current->send($strand->value);
} else {
$strand->current->throw($strand->value);
}
$strand->success = null;
$strand->value = null;
}
START:
if ($strand->current->valid()) {
$value = $strand->current->current();
if ($value instanceof Generator) {
$strand->stack[$strand->depth++] = $strand->current;
$strand->current = $value;
goto START;
} elseif ($this->isPromise($value)) {
// !!! increment pending before calling ->then() as it may invoke the callback right away
++$this->pending;
if (! $value instanceof Promise) {
$value = $this->promiseAdapter->convertThenable($value);
}
$this->promiseAdapter
->then(
$value,
function ($value) use ($strand) {
$strand->success = true;
$strand->value = $value;
$this->queue->enqueue($strand);
$this->done();
},
function (Throwable $throwable) use ($strand) {
$strand->success = false;
$strand->value = $throwable;
$this->queue->enqueue($strand);
$this->done();
}
);
continue;
} else {
$strand->success = true;
$strand->value = $value;
goto RESUME;
}
}
$strand->success = true;
$strand->value = $strand->current->getReturn();
} catch (Throwable $reason) {
$strand->success = false;
$strand->value = $reason;
}
if ($strand->depth <= 0) {
continue;
}
$current = &$strand->stack[--$strand->depth];
$strand->current = $current;
$current = null;
goto RESUME;
}
if ($this->pending > 0 || $this->schedule->isEmpty()) {
return;
}
/** @var CoroutineContext $ctx */
$ctx = $this->schedule->dequeue();
$this->queue->enqueue(new Strand($this->spawn($ctx)));
goto RUN;
}
private function done()
{
--$this->pending;
$this->run();
if ($this->pending > 0) {
return;
}
$doResolve = $this->doResolve;
$doResolve($this->finishExecute($this->rootResult, $this->errors));
}
private function spawn(CoroutineContext $ctx)
{
// short-circuit evaluation for __typename
if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
$ctx->result->{$ctx->shared->resultName} = $ctx->type->name;
return;
}
try {
if ($ctx->shared->typeGuard1 === $ctx->type) {
$resolve = $ctx->shared->resolveIfType1;
$ctx->resolveInfo = clone $ctx->shared->resolveInfoIfType1;
$ctx->resolveInfo->path = $ctx->path;
$arguments = $ctx->shared->argumentsIfType1;
$returnType = $ctx->resolveInfo->returnType;
} else {
$fieldDefinition = $this->findFieldDefinition($ctx);
if ($fieldDefinition->resolveFn !== null) {
$resolve = $fieldDefinition->resolveFn;
} elseif ($ctx->type->resolveFieldFn !== null) {
$resolve = $ctx->type->resolveFieldFn;
} else {
$resolve = $this->fieldResolver;
}
$returnType = $fieldDefinition->getType();
$ctx->resolveInfo = new ResolveInfo(
$ctx->shared->fieldName,
$ctx->shared->fieldNodes,
$returnType,
$ctx->type,
$ctx->path,
$this->schema,
$this->collector->fragments,
$this->rootValue,
$this->collector->operation,
$this->variableValues
);
$arguments = Values::getArgumentValuesForMap(
$fieldDefinition,
$ctx->shared->argumentValueMap,
$this->variableValues
);
// !!! assign only in batch when no exception can be thrown in-between
$ctx->shared->typeGuard1 = $ctx->type;
$ctx->shared->resolveIfType1 = $resolve;
$ctx->shared->argumentsIfType1 = $arguments;
$ctx->shared->resolveInfoIfType1 = $ctx->resolveInfo;
}
$value = $resolve($ctx->value, $arguments, $this->contextValue, $ctx->resolveInfo);
if (! $this->completeValueFast($ctx, $returnType, $value, $ctx->path, $returnValue)) {
$returnValue = yield $this->completeValue(
$ctx,
$returnType,
$value,
$ctx->path,
$ctx->nullFence
);
}
} catch (Throwable $reason) {
$this->addError(Error::createLocatedError(
$reason,
$ctx->shared->fieldNodes,
$ctx->path
));
$returnValue = self::$undefined;
}
if ($returnValue !== self::$undefined) {
$ctx->result->{$ctx->shared->resultName} = $returnValue;
} elseif ($ctx->resolveInfo !== null && $ctx->resolveInfo->returnType instanceof NonNull) { // !!! $ctx->resolveInfo might not have been initialized yet
$result =& $this->rootResult;
foreach ($ctx->nullFence ?? [] as $key) {
if (is_string($key)) {
$result =& $result->{$key};
} else {
$result =& $result[$key];
}
}
$result = null;
}
}
private function findFieldDefinition(CoroutineContext $ctx)
{
if ($ctx->shared->fieldName === Introspection::SCHEMA_FIELD_NAME && $ctx->type === $this->schema->getQueryType()) {
return Introspection::schemaMetaFieldDef();
}
if ($ctx->shared->fieldName === Introspection::TYPE_FIELD_NAME && $ctx->type === $this->schema->getQueryType()) {
return Introspection::typeMetaFieldDef();
}
if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
return Introspection::typeNameMetaFieldDef();
}
return $ctx->type->getField($ctx->shared->fieldName);
}
/**
* @param mixed $value
* @param string[] $path
* @param mixed $returnValue
*/
private function completeValueFast(CoroutineContext $ctx, Type $type, $value, array $path, &$returnValue) : bool
{
// special handling of Throwable inherited from JS reference implementation, but makes no sense in this PHP
if ($this->isPromise($value) || $value instanceof Throwable) {
return false;
}
$nonNull = false;
if ($type instanceof NonNull) {
$nonNull = true;
$type = $type->getWrappedType();
}
if (! $type instanceof LeafType) {
return false;
}
if ($type !== $this->schema->getType($type->name)) {
$hint = '';
if ($this->schema->getConfig()->typeLoader !== null) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type,
$ctx->shared->fieldName
);
}
$this->addError(Error::createLocatedError(
new InvariantViolation(
sprintf(
'Schema must contain unique named types but contains multiple types named "%s". %s ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$type->name,
$hint
)
),
$ctx->shared->fieldNodes,
$path
));
$value = null;
}
if ($value === null) {
$returnValue = null;
} else {
try {
$returnValue = $type->serialize($value);
} catch (Throwable $error) {
$this->addError(Error::createLocatedError(
new InvariantViolation(
'Expected a value of type "' . Utils::printSafe($type) . '" but received: ' . Utils::printSafe($value),
0,
$error
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
}
}
if ($nonNull && $returnValue === null) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Cannot return null for non-nullable field %s.%s.',
$ctx->type->name,
$ctx->shared->fieldName
)),
$ctx->shared->fieldNodes,
$path
));
$returnValue = self::$undefined;
}
return true;
}
/**
* @param mixed $value
* @param string[] $path
* @param string[]|null $nullFence
*
* @return mixed
*/
private function completeValue(CoroutineContext $ctx, Type $type, $value, array $path, ?array $nullFence)
{
$nonNull = false;
$returnValue = null;
if ($type instanceof NonNull) {
$nonNull = true;
$type = $type->getWrappedType();
} else {
$nullFence = $path;
}
// !!! $value might be promise, yield to resolve
try {
if ($this->isPromise($value)) {
$value = yield $value;
}
} catch (Throwable $reason) {
$this->addError(Error::createLocatedError(
$reason,
$ctx->shared->fieldNodes,
$path
));
if ($nonNull) {
$returnValue = self::$undefined;
} else {
$returnValue = null;
}
goto CHECKED_RETURN;
}
if ($value === null) {
$returnValue = $value;
goto CHECKED_RETURN;
} elseif ($value instanceof Throwable) {
// special handling of Throwable inherited from JS reference implementation, but makes no sense in this PHP
$this->addError(Error::createLocatedError(
$value,
$ctx->shared->fieldNodes,
$path
));
if ($nonNull) {
$returnValue = self::$undefined;
} else {
$returnValue = null;
}
goto CHECKED_RETURN;
}
if ($type instanceof ListOfType) {
$returnValue = [];
$index = -1;
$itemType = $type->getWrappedType();
foreach ($value as $itemValue) {
++$index;
$itemPath = $path;
$itemPath[] = $index; // !!! use arrays COW semantics
$ctx->resolveInfo->path = $itemPath;
try {
if (! $this->completeValueFast($ctx, $itemType, $itemValue, $itemPath, $itemReturnValue)) {
$itemReturnValue = yield $this->completeValue($ctx, $itemType, $itemValue, $itemPath, $nullFence);
}
} catch (Throwable $reason) {
$this->addError(Error::createLocatedError(
$reason,
$ctx->shared->fieldNodes,
$itemPath
));
$itemReturnValue = null;
}
if ($itemReturnValue === self::$undefined) {
$returnValue = self::$undefined;
goto CHECKED_RETURN;
}
$returnValue[$index] = $itemReturnValue;
}
goto CHECKED_RETURN;
} else {
if ($type !== $this->schema->getType($type->name)) {
$hint = '';
if ($this->schema->getConfig()->typeLoader !== null) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type,
$ctx->shared->fieldName
);
}
$this->addError(Error::createLocatedError(
new InvariantViolation(
sprintf(
'Schema must contain unique named types but contains multiple types named "%s". %s ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$type->name,
$hint
)
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
}
if ($type instanceof LeafType) {
try {
$returnValue = $type->serialize($value);
} catch (Throwable $error) {
$this->addError(Error::createLocatedError(
new InvariantViolation(
'Expected a value of type "' . Utils::printSafe($type) . '" but received: ' . Utils::printSafe($value),
0,
$error
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
}
goto CHECKED_RETURN;
} elseif ($type instanceof CompositeType) {
/** @var ObjectType|null $objectType */
$objectType = null;
if ($type instanceof InterfaceType || $type instanceof UnionType) {
$objectType = $type->resolveType($value, $this->contextValue, $ctx->resolveInfo);
if ($objectType === null) {
$objectType = yield $this->resolveTypeSlow($ctx, $value, $type);
}
// !!! $objectType->resolveType() might return promise, yield to resolve
$objectType = yield $objectType;
if (is_string($objectType)) {
$objectType = $this->schema->getType($objectType);
}
if ($objectType === null) {
$this->addError(Error::createLocatedError(
sprintf(
'Composite type "%s" did not resolve concrete object type for value: %s.',
$type->name,
Utils::printSafe($value)
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = self::$undefined;
goto CHECKED_RETURN;
} elseif (! $objectType instanceof ObjectType) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Abstract type %s must resolve to an Object type at ' .
'runtime for field %s.%s with value "%s", received "%s". ' .
'Either the %s type should provide a "resolveType" ' .
'function or each possible type should provide an "isTypeOf" function.',
$type,
$ctx->resolveInfo->parentType,
$ctx->resolveInfo->fieldName,
Utils::printSafe($value),
Utils::printSafe($objectType),
$type
)),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
} elseif (! $this->schema->isPossibleType($type, $objectType)) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Runtime Object type "%s" is not a possible type for "%s".',
$objectType,
$type
)),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
} elseif ($objectType !== $this->schema->getType($objectType->name)) {
$this->addError(Error::createLocatedError(
new InvariantViolation(
sprintf(
'Schema must contain unique named types but contains multiple types named "%s". ' .
'Make sure that `resolveType` function of abstract type "%s" returns the same ' .
'type instance as referenced anywhere else within the schema ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$objectType,
$type
)
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
}
} elseif ($type instanceof ObjectType) {
$objectType = $type;
} else {
$this->addError(Error::createLocatedError(
sprintf(
'Unexpected field type "%s".',
Utils::printSafe($type)
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = self::$undefined;
goto CHECKED_RETURN;
}
$typeCheck = $objectType->isTypeOf($value, $this->contextValue, $ctx->resolveInfo);
if ($typeCheck !== null) {
// !!! $objectType->isTypeOf() might return promise, yield to resolve
$typeCheck = yield $typeCheck;
if (! $typeCheck) {
$this->addError(Error::createLocatedError(
sprintf('Expected value of type "%s" but got: %s.', $type->name, Utils::printSafe($value)),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
}
}
$returnValue = new stdClass();
if ($ctx->shared->typeGuard2 === $objectType) {
foreach ($ctx->shared->childContextsIfType2 as $childCtx) {
$childCtx = clone $childCtx;
$childCtx->type = $objectType;
$childCtx->value = $value;
$childCtx->result = $returnValue;
$childCtx->path = $path;
$childCtx->path[] = $childCtx->shared->resultName; // !!! uses array COW semantics
$childCtx->nullFence = $nullFence;
$childCtx->resolveInfo = null;
$this->queue->enqueue(new Strand($this->spawn($childCtx)));
// !!! assign null to keep object keys sorted
$returnValue->{$childCtx->shared->resultName} = null;
}
} else {
$childContexts = [];
foreach ($this->collector->collectFields(
$objectType,
$ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)
) as $childShared) {
/** @var CoroutineContextShared $childShared */
$childPath = $path;
$childPath[] = $childShared->resultName; // !!! uses array COW semantics
$childCtx = new CoroutineContext(
$childShared,
$objectType,
$value,
$returnValue,
$childPath,
$nullFence
);
$childContexts[] = $childCtx;
$this->queue->enqueue(new Strand($this->spawn($childCtx)));
// !!! assign null to keep object keys sorted
$returnValue->{$childShared->resultName} = null;
}
$ctx->shared->typeGuard2 = $objectType;
$ctx->shared->childContextsIfType2 = $childContexts;
}
goto CHECKED_RETURN;
} else {
$this->addError(Error::createLocatedError(
sprintf('Unhandled type "%s".', Utils::printSafe($type)),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
}
}
CHECKED_RETURN:
if ($nonNull && $returnValue === null) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Cannot return null for non-nullable field %s.%s.',
$ctx->type->name,
$ctx->shared->fieldName
)),
$ctx->shared->fieldNodes,
$path
));
return self::$undefined;
}
return $returnValue;
}
private function mergeSelectionSets(CoroutineContext $ctx)
{
$selections = [];
foreach ($ctx->shared->fieldNodes as $fieldNode) {
if ($fieldNode->selectionSet === null) {
continue;
}
foreach ($fieldNode->selectionSet->selections as $selection) {
$selections[] = $selection;
}
}
return $ctx->shared->mergedSelectionSet = new SelectionSetNode(['selections' => $selections]);
}
private function resolveTypeSlow(CoroutineContext $ctx, $value, AbstractType $abstractType)
{
if ($value !== null &&
is_array($value) &&
isset($value['__typename']) &&
is_string($value['__typename'])
) {
return $this->schema->getType($value['__typename']);
}
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader !== null) {
Warning::warnOnce(
sprintf(
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
'for value: %s. Switching to slow resolution method using `isTypeOf` ' .
'of all possible implementations. It requires full schema scan and degrades query performance significantly. ' .
' Make sure your `resolveType` always returns valid implementation or throws.',
$abstractType->name,
Utils::printSafe($value)
),
Warning::WARNING_FULL_SCHEMA_SCAN
);
}
$possibleTypes = $this->schema->getPossibleTypes($abstractType);
// to be backward-compatible with old executor, ->isTypeOf() is called for all possible types,
// it cannot short-circuit when the match is found
$selectedType = null;
foreach ($possibleTypes as $type) {
$typeCheck = yield $type->isTypeOf($value, $this->contextValue, $ctx->resolveInfo);
if ($selectedType !== null || $typeCheck !== true) {
continue;
}
$selectedType = $type;
}
return $selectedType;
}
/**
* @param mixed $value
*
* @return bool
*/
private function isPromise($value)
{
return $value instanceof Promise || $this->promiseAdapter->isThenable($value);
}
}

View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\InputType;
/**
* @internal
*/
interface Runtime
{
public function evaluate(ValueNode $valueNode, InputType $type);
public function addError($error);
}

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use Generator;
/**
* @internal
*/
class Strand
{
/** @var Generator */
public $current;
/** @var Generator[] */
public $stack;
/** @var int */
public $depth;
/** @var bool|null */
public $success;
/** @var mixed */
public $value;
public function __construct(Generator $coroutine)
{
$this->current = $coroutine;
$this->stack = [];
$this->depth = 0;
}
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL;
use GraphQL\Error\Error;
@ -6,15 +9,21 @@ use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Executor\ReferenceExecutor;
use GraphQL\Experimental\Executor\CoroutineExecutor;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema as SchemaType;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\AbstractValidationRule;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\ValidationRule;
use function array_values;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* This is the primary facade for fulfilling GraphQL operations.
@ -57,33 +66,37 @@ class GraphQL
* Empty array would allow to skip query validation (may be convenient for persisted
* queries which are validated before persisting and assumed valid during execution)
*
* @api
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param array|null $variableValues
* @param string|null $operationName
* @param callable $fieldResolver
* @param array $validationRules
* @param mixed $rootValue
* @param mixed $context
* @param mixed[]|null $variableValues
* @param ValidationRule[] $validationRules
*
* @return ExecutionResult
* @api
*/
public static function executeQuery(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$context = null,
$variableValues = null,
$operationName = null,
callable $fieldResolver = null,
array $validationRules = null
)
{
?string $operationName = null,
?callable $fieldResolver = null,
?array $validationRules = null
) : ExecutionResult {
$promiseAdapter = new SyncPromiseAdapter();
$promise = self::promiseToExecute($promiseAdapter, $schema, $source, $rootValue, $context,
$variableValues, $operationName, $fieldResolver, $validationRules);
$promise = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver,
$validationRules
);
return $promiseAdapter->wait($promise);
}
@ -92,31 +105,25 @@ class GraphQL
* Same as executeQuery(), but requires PromiseAdapter and always returns a Promise.
* Useful for Async PHP platforms.
*
* @api
* @param PromiseAdapter $promiseAdapter
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param array|null $variableValues
* @param string|null $operationName
* @param callable $fieldResolver
* @param array $validationRules
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param mixed[]|null $variableValues
* @param ValidationRule[]|null $validationRules
*
* @return Promise
* @api
*/
public static function promiseToExecute(
PromiseAdapter $promiseAdapter,
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$context = null,
$variableValues = null,
$operationName = null,
callable $fieldResolver = null,
array $validationRules = null
)
{
?string $operationName = null,
?callable $fieldResolver = null,
?array $validationRules = null
) : Promise {
try {
if ($source instanceof DocumentNode) {
$documentNode = $source;
@ -125,36 +132,38 @@ class GraphQL
}
// FIXME
if (!empty($validationRules)) {
foreach ($validationRules as $rule) {
if ($rule instanceof QueryComplexity) {
$rule->setRawVariableValues($variableValues);
}
}
} else {
if (empty($validationRules)) {
/** @var QueryComplexity $queryComplexity */
$queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
$queryComplexity->setRawVariableValues($variableValues);
} else {
foreach ($validationRules as $rule) {
if (! ($rule instanceof QueryComplexity)) {
continue;
}
$rule->setRawVariableValues($variableValues);
}
}
$validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);
if (!empty($validationErrors)) {
if (! empty($validationErrors)) {
return $promiseAdapter->createFulfilled(
new ExecutionResult(null, $validationErrors)
);
} else {
return Executor::promiseToExecute(
$promiseAdapter,
$schema,
$documentNode,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver
);
}
return Executor::promiseToExecute(
$promiseAdapter,
$schema,
$documentNode,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver
);
} catch (Error $e) {
return $promiseAdapter->createFulfilled(
new ExecutionResult(null, [$e])
@ -165,25 +174,29 @@ class GraphQL
/**
* @deprecated Use executeQuery()->toArray() instead
*
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $contextValue
* @param array|null $variableValues
* @param string|null $operationName
* @return Promise|array
* @param mixed $rootValue
* @param mixed $contextValue
* @param mixed[]|null $variableValues
*
* @return Promise|mixed[]
*/
public static function execute(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null
)
{
$result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(),
?string $operationName = null
) {
trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement',
E_USER_DEPRECATED
);
$promiseAdapter = Executor::getPromiseAdapter();
$result = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
@ -195,36 +208,40 @@ class GraphQL
if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result)->toArray();
} else {
$result = $result->then(function(ExecutionResult $r) {
$result = $result->then(static function (ExecutionResult $r) {
return $r->toArray();
});
}
return $result;
}
/**
* @deprecated renamed to executeQuery()
*
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $contextValue
* @param array|null $variableValues
* @param string|null $operationName
* @param mixed $rootValue
* @param mixed $contextValue
* @param mixed[]|null $variableValues
*
* @return ExecutionResult|Promise
*/
public static function executeAndReturnResult(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null
)
{
$result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(),
?string $operationName = null
) {
trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement',
E_USER_DEPRECATED
);
$promiseAdapter = Executor::getPromiseAdapter();
$result = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
@ -232,19 +249,22 @@ class GraphQL
$variableValues,
$operationName
);
if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result);
}
return $result;
}
/**
* Returns directives defined in GraphQL spec
*
* @api
* @return Directive[]
*
* @api
*/
public static function getStandardDirectives()
public static function getStandardDirectives() : array
{
return array_values(Directive::getInternalDirectives());
}
@ -252,48 +272,79 @@ class GraphQL
/**
* Returns types defined in GraphQL spec
*
* @api
* @return Type[]
*
* @api
*/
public static function getStandardTypes()
public static function getStandardTypes() : array
{
return array_values(Type::getInternalTypes());
return array_values(Type::getStandardTypes());
}
/**
* Replaces standard types with types from this list (matching by name)
* Standard types not listed here remain untouched.
*
* @param Type[] $types
*
* @api
*/
public static function overrideStandardTypes(array $types)
{
Type::overrideStandardTypes($types);
}
/**
* Returns standard validation rules implementing GraphQL spec
*
* @return ValidationRule[]
*
* @api
* @return AbstractValidationRule[]
*/
public static function getStandardValidationRules()
public static function getStandardValidationRules() : array
{
return array_values(DocumentValidator::defaultRules());
}
/**
* @param callable $fn
* Set default resolver implementation
*
* @api
*/
public static function setDefaultFieldResolver(callable $fn)
public static function setDefaultFieldResolver(callable $fn) : void
{
Executor::setDefaultFieldResolver($fn);
}
/**
* @param PromiseAdapter|null $promiseAdapter
*/
public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
public static function setPromiseAdapter(?PromiseAdapter $promiseAdapter = null) : void
{
Executor::setPromiseAdapter($promiseAdapter);
}
/**
* Experimental: Switch to the new executor
*/
public static function useExperimentalExecutor()
{
Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
}
/**
* Experimental: Switch back to the default executor
*/
public static function useReferenceExecutor()
{
Executor::setImplementationFactory([ReferenceExecutor::class, 'create']);
}
/**
* Returns directives defined in GraphQL spec
*
* @deprecated Renamed to getStandardDirectives
*
* @return Directive[]
*/
public static function getInternalDirectives()
public static function getInternalDirectives() : array
{
return self::getStandardDirectives();
}

View file

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

View file

@ -1,13 +1,14 @@
<?php
namespace GraphQL\Language\AST;
declare(strict_types=1);
namespace GraphQL\Language\AST;
class BooleanValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::BOOLEAN;
/**
* @var string
*/
/** @var bool */
public $value;
}

View file

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

View file

@ -1,25 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class DirectiveDefinitionNode extends Node implements TypeSystemDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::DIRECTIVE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var ArgumentNode[]
*/
/** @var ArgumentNode[] */
public $arguments;
/**
* @var NameNode[]
*/
/** @var NameNode[] */
public $locations;
/** @var StringValueNode|null */
public $description;
}

View file

@ -1,17 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class DirectiveNode extends Node
{
/** @var string */
public $kind = NodeKind::DIRECTIVE;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var ArgumentNode[]
*/
/** @var ArgumentNode[] */
public $arguments;
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class DocumentNode extends Node
{
/** @var string */
public $kind = NodeKind::DOCUMENT;
/**
* @var DefinitionNode[]
*/
/** @var NodeList|DefinitionNode[] */
public $definitions;
}

View file

@ -1,30 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class EnumTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::ENUM_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var EnumValueDefinitionNode[]
*/
/** @var EnumValueDefinitionNode[]|NodeList|null */
public $values;
/**
* @var string
*/
/** @var StringValueNode|null */
public $description;
}

View file

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

View file

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

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class EnumValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::ENUM;
/**
* @var string
*/
/** @var string */
public $value;
}

View file

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

View file

@ -1,35 +1,26 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FieldDefinitionNode extends Node
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::FIELD_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var InputValueDefinitionNode[]
*/
/** @var InputValueDefinitionNode[]|NodeList */
public $arguments;
/**
* @var TypeNode
*/
/** @var TypeNode */
public $type;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[]|NodeList */
public $directives;
/**
* @var string
*/
/** @var StringValueNode|null */
public $description;
}

View file

@ -1,32 +1,26 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FieldNode extends Node implements SelectionNode
{
/** @var string */
public $kind = NodeKind::FIELD;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var NameNode|null
*/
/** @var NameNode|null */
public $alias;
/**
* @var ArgumentNode[]|null
*/
/** @var ArgumentNode[]|null */
public $arguments;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var SelectionSetNode|null
*/
/** @var SelectionSetNode|null */
public $selectionSet;
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FloatValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::FLOAT;
/**
* @var string
*/
/** @var string */
public $value;
}

View file

@ -1,27 +1,31 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FragmentDefinitionNode extends Node implements DefinitionNode, HasSelectionSet
class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, HasSelectionSet
{
/** @var string */
public $kind = NodeKind::FRAGMENT_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var NamedTypeNode
* Note: fragment variable definitions are experimental and may be changed
* or removed in the future.
*
* @var VariableDefinitionNode[]|NodeList
*/
public $variableDefinitions;
/** @var NamedTypeNode */
public $typeCondition;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[]|NodeList */
public $directives;
/**
* @var SelectionSetNode
*/
/** @var SelectionSetNode */
public $selectionSet;
}

View file

@ -1,17 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FragmentSpreadNode extends Node implements SelectionNode
{
/** @var string */
public $kind = NodeKind::FRAGMENT_SPREAD;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
}

View file

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

View file

@ -1,22 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InlineFragmentNode extends Node implements SelectionNode
{
/** @var string */
public $kind = NodeKind::INLINE_FRAGMENT;
/**
* @var NamedTypeNode
*/
/** @var NamedTypeNode */
public $typeCondition;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var SelectionSetNode
*/
/** @var SelectionSetNode */
public $selectionSet;
}

View file

@ -1,30 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InputObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INPUT_OBJECT_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var InputValueDefinitionNode[]
*/
/** @var InputValueDefinitionNode[]|null */
public $fields;
/**
* @var string
*/
/** @var StringValueNode|null */
public $description;
}

View file

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

View file

@ -1,35 +1,26 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InputValueDefinitionNode extends Node
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INPUT_VALUE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var TypeNode
*/
/** @var TypeNode */
public $type;
/**
* @var ValueNode
*/
/** @var ValueNode */
public $defaultValue;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var string
*/
/** @var StringValueNode|null */
public $description;
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class IntValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::INT;
/**
* @var string
*/
/** @var string */
public $value;
}

View file

@ -1,30 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InterfaceTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INTERFACE_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var FieldDefinitionNode[]
*/
public $fields = [];
/** @var FieldDefinitionNode[]|null */
public $fields;
/**
* @var string
*/
/** @var StringValueNode|null */
public $description;
}

View file

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

View file

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

View file

@ -1,13 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ListValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::LST;
/**
* @var ValueNode[]
*/
/** @var ValueNode[]|NodeList */
public $values;
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
use GraphQL\Language\Source;
@ -46,27 +49,31 @@ class Location
public $source;
/**
* @param $start
* @param $end
* @param int $start
* @param int $end
*
* @return static
*/
public static function create($start, $end)
{
$tmp = new static();
$tmp = new static();
$tmp->start = $start;
$tmp->end = $end;
$tmp->end = $end;
return $tmp;
}
public function __construct(Token $startToken = null, Token $endToken = null, Source $source = null)
public function __construct(?Token $startToken = null, ?Token $endToken = null, ?Source $source = null)
{
$this->startToken = $startToken;
$this->endToken = $endToken;
$this->source = $source;
$this->endToken = $endToken;
$this->source = $source;
if ($startToken && $endToken) {
$this->start = $startToken->start;
$this->end = $endToken->end;
if ($startToken === null || $endToken === null) {
return;
}
$this->start = $startToken->start;
$this->end = $endToken->end;
}
}

View file

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

View file

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

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