Compare commits

..

392 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
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
380 changed files with 42362 additions and 28392 deletions

1
.gitattributes vendored
View file

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

9
.gitignore vendored
View file

@ -1,6 +1,7 @@
.idea/
composer.phar
.phpcs-cache
composer.lock
composer.phar
phpcs.xml
phpstan.neon
vendor/
bin/
phpstan.phar
/.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,36 +1,66 @@
dist: trusty
language: php
dist: trusty
php:
- 5.6
- 7.0
- 7.1
- 7.2
- nightly
- 7.1
- 7.2
- 7.3
- 7.4snapshot
- nightly
env:
matrix:
- EXECUTOR= DEPENDENCIES=--prefer-lowest
- EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest
- EXECUTOR=
- EXECUTOR=coroutine
jobs:
include:
- stage: Code Quality
php: 7.2
env: STATIC_ANALYSIS
install: travis_retry composer install --prefer-dist
script: composer static-analysis
allow_failures:
- php: nightly
cache:
directories:
- $HOME/.composer/cache
directories:
- $HOME/.composer/cache
before_install:
- if [[ "$TRAVIS_PHP_VERSION" != "5.6" ]]; then phpenv config-rm xdebug.ini || true; fi
- phpenv config-rm xdebug.ini || true
- composer selfupdate
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
- travis_retry composer self-update
install: composer install --dev --prefer-dist
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,4 +1,45 @@
# 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)

View file

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

View file

@ -1,6 +1,6 @@
# graphql-php
[![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)
@ -14,7 +14,7 @@ composer require webonyx/graphql-php
```
## Documentation
Full documentation is available on the [Documentation site](http://webonyx.github.io/graphql-php/) as well
Full documentation is available on the [Documentation site](https://webonyx.github.io/graphql-php/) as well
as in the [docs](docs/) folder of the distribution.
If you don't know what GraphQL is, visit this [official website](http://graphql.org)
@ -24,11 +24,29 @@ by the Facebook engineering team.
There are several ready examples in the [examples](examples/) folder of the distribution with specific
README file per example.
## Contribute
Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute.
## Contributors
## Old README.md
Here is a [link to the old README.md](https://github.com/webonyx/graphql-php/blob/v0.9.14/README.md).
This project exists thanks to [all the people](https://github.com/webonyx/graphql-php/graphs/contributors) who contribute. [[Contribute](CONTRIBUTING.md)].
Keep in mind that it relates to the version 0.9.x. It may contain outdated information for
newer versions (even though we try to preserve backwards compatibility).
## Backers
<a href="https://opencollective.com/webonyx-graphql-php#backers" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/backers.svg?width=890"></a>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/webonyx-graphql-php#sponsor)]
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/0/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/1/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/2/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/3/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/4/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/5/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/6/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/7/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/8/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/9/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/9/avatar.svg"></a>
## License
See [LICENSE](LICENSE).

View file

@ -1,3 +1,92 @@
## Master
### Breaking (major): dropped deprecations
- dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`.
## Upgrade v0.12.x > v0.13.x
### Breaking (major): minimum supported version of PHP
New minimum required version of PHP is **7.1+**
### Breaking (major): default errors formatting changed according to spec
**Category** and extensions assigned to errors are shown under `extensions` key
```php
$e = new Error(
'msg',
null,
null,
null,
null,
null,
['foo' => 'bar']
);
```
Formatting before the change:
```
'errors' => [
[
'message' => 'msg',
'category' => 'graphql',
'foo' => 'bar'
]
]
```
After the change:
```
'errors' => [
[
'message' => 'msg',
'extensions' => [
'category' => 'graphql',
'foo' => 'bar',
],
]
]
```
Note: if error extensions contain `category` key - it has a priority over default category.
You can always switch to [custom error formatting](https://webonyx.github.io/graphql-php/error-handling/#custom-error-handling-and-formatting) to revert to the old format.
### Try it: Experimental Executor with improved performance
It is disabled by default. To enable it, do the following
```php
<?php
use GraphQL\Executor\Executor;
use GraphQL\Experimental\Executor\CoroutineExecutor;
Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
```
**Please post your feedback about new executor at https://github.com/webonyx/graphql-php/issues/397
Especially if you had issues (because it may become the default in one of the next releases)**
### Breaking: multiple interfaces separated with & in SDL
Before the change:
```graphql
type Foo implements Bar, Baz { field: Type }
```
After the change:
```graphql
type Foo implements Bar & Baz { field: Type }
```
To allow for an adaptive migration, use `allowLegacySDLImplementsInterfaces` option of parser:
```php
Parser::parse($source, [ 'allowLegacySDLImplementsInterfaces' => true])
```
### Breaking: several classes renamed
- `AbstractValidationRule` renamed to `ValidationRule` (NS `GraphQL\Validator\Rules`)
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
- `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`)
### Breaking: new constructors
`GraphQL\Type\Definition\ResolveInfo` now takes 10 arguments instead of one array.
## Upgrade v0.11.x > v0.12.x
### Breaking: Minimum supported version is PHP5.6
@ -69,6 +158,10 @@ type Dog {
}
```
### Breaking: Cached AST of version 0.11.x is not compatible with 0.12.x.
That's because description in AST is now a separate node, not just a string.
Make sure to renew caches.
### Breaking: Most of previously deprecated classes and methods were removed
See deprecation notices for previous versions in details.

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

@ -9,16 +9,22 @@
"API"
],
"require": {
"php": ">=5.6",
"php": "^7.1||^8.0",
"ext-json": "*",
"ext-mbstring": "*"
},
"require-dev": {
"phpunit/phpunit": "^4.8",
"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
},
@ -34,17 +40,17 @@
"GraphQL\\Examples\\Blog\\": "examples/01-blog/Blog/"
}
},
"scripts": {
"static-analysis": [
"rm phpstan.phar || true",
"@composer req --ansi --no-interaction --dev phpstan/phpstan-shim",
"cp -f vendor/phpstan/phpstan-shim/phpstan.phar .",
"@composer rem --ansi --dev phpstan/phpstan-shim",
"@php phpstan.phar analyse --ansi -l 1 -c phpstan.neon src"
]
},
"suggest": {
"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,21 +1,26 @@
# Integrations
* [Integration with Relay](https://github.com/ivome/graphql-relay-php)
* [Integration with Laravel 5](https://github.com/Folkloreatelier/laravel-graphql) + [Relay Helpers for Laravel](https://github.com/nuwave/laravel-graphql-relay) + [Nuwave Lighthouse](https://github.com/nuwave/lighthouse)
* [Symfony Bundle](https://github.com/overblog/GraphQLBundle) by Overblog
* Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)) via [Standard Server](executing-queries.md/#using-server)
* [Standard Server](executing-queries.md/#using-server) Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)).
* [Relay Library for graphql-php](https://github.com/ivome/graphql-relay-php) Helps construct Relay related schema definitions.
* [Lighthouse](https://github.com/nuwave/lighthouse) Laravel based, uses Schema Definition Language
* [Laravel GraphQL](https://github.com/rebing/graphql-laravel) - Laravel wrapper for Facebook's GraphQL
* [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) Bundle for Symfony
* [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress
# GraphQL PHP Tools
* Define types with Doctrine ORM annotations ([for PHP7.1](https://github.com/Ecodev/graphql-doctrine), for [earlier PHP versions](https://github.com/rahuljayaraman/doctrine-graphql))
* [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental)
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server
* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) - Simple library that provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches.
* [GraphQLite](https://graphqlite.thecodingmachine.io) Define your complete schema with annotations
* [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) Define types with Doctrine ORM annotations
* [DataLoaderPHP](https://github.com/overblog/dataloader-php) as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) A PSR-15 middleware to support file uploads in GraphQL.
* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) Provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches.
* [GraphQL Utils](https://github.com/simPod/GraphQL-Utils) Objective schema definition builders (no need for arrays anymore) and `DateTime` scalar
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server _(experimental)_
# General GraphQL Tools
* [GraphiQL](https://github.com/graphql/graphiql) - An in-browser IDE for exploring GraphQL
* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).
* [GraphiQL](https://github.com/graphql/graphiql) An in-browser IDE for exploring GraphQL
* [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) -
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp)
GraphiQL as Google Chrome extension

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.

View file

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

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
@ -44,8 +44,8 @@ existing PHP frameworks, add support for Relay, etc.
## Current Status
The first version of this library (v0.1) was released on August 10th 2015.
The current version (v0.10) supports all features described by GraphQL specification
(including April 2016 add-ons) as well as some experimental features like
The current version supports all features described by GraphQL specification
as well as some experimental features like
[Schema Language parser](type-system/type-language.md) and
[Schema printer](reference.md#graphqlutilsschemaprinter).

File diff suppressed because it is too large Load diff

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

@ -38,6 +38,30 @@ By default, such schema is created without any resolvers.
We have to rely on [default field resolver](../data-fetching.md#default-field-resolver) and **root value** in
order to execute a query against this schema.
# Defining resolvers
Since 0.10.0
In order to enable **Interfaces**, **Unions** and custom field resolvers you can pass the second argument:
**type config decorator** to schema builder.
It accepts default type config produced by the builder and is expected to add missing options like
[**resolveType**](interfaces.md#configuration-options) for interface types or
[**resolveField**](object-types.md#configuration-options) for object types.
```php
<?php
use GraphQL\Utils\BuildSchema;
$typeConfigDecorator = function($typeConfig, $typeDefinitionNode) {
$name = $typeConfig['name'];
// ... add missing options to $typeConfig based on type $name
return $typeConfig;
};
$contents = file_get_contents('schema.graphql');
$schema = BuildSchema::build($contents, $typeConfigDecorator);
```
# Performance considerations
Since 0.10.0
@ -57,7 +81,7 @@ $cacheFilename = 'cached_schema.php';
if (!file_exists($cacheFilename)) {
$document = Parser::parse(file_get_contents('./schema.graphql'));
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true));
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true) . ";\n");
} else {
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
}

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

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

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

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

View file

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

View file

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

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

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

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

File diff suppressed because it is too large Load diff

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

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,29 +174,29 @@ class GraphQL
/**
* @deprecated Use executeQuery()->toArray() instead
*
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $contextValue
* @param array|null $variableValues
* @param string|null $operationName
* @return Promise|array
* @param mixed $rootValue
* @param mixed $contextValue
* @param mixed[]|null $variableValues
*
* @return Promise|mixed[]
*/
public static function execute(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null
)
{
?string $operationName = null
) {
trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement',
E_USER_DEPRECATED
);
$result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(),
$promiseAdapter = Executor::getPromiseAdapter();
$result = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
@ -199,40 +208,40 @@ class GraphQL
if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result)->toArray();
} else {
$result = $result->then(function(ExecutionResult $r) {
$result = $result->then(static function (ExecutionResult $r) {
return $r->toArray();
});
}
return $result;
}
/**
* @deprecated renamed to executeQuery()
*
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $contextValue
* @param array|null $variableValues
* @param string|null $operationName
* @param mixed $rootValue
* @param mixed $contextValue
* @param mixed[]|null $variableValues
*
* @return ExecutionResult|Promise
*/
public static function executeAndReturnResult(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null
)
{
?string $operationName = null
) {
trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement',
E_USER_DEPRECATED
);
$result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(),
$promiseAdapter = Executor::getPromiseAdapter();
$result = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
@ -240,19 +249,22 @@ class GraphQL
$variableValues,
$operationName
);
if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result);
}
return $result;
}
/**
* Returns directives defined in GraphQL spec
*
* @api
* @return Directive[]
*
* @api
*/
public static function getStandardDirectives()
public static function getStandardDirectives() : array
{
return array_values(Directive::getInternalDirectives());
}
@ -260,48 +272,79 @@ class GraphQL
/**
* Returns types defined in GraphQL spec
*
* @api
* @return Type[]
*
* @api
*/
public static function getStandardTypes()
public static function getStandardTypes() : array
{
return array_values(Type::getInternalTypes());
return array_values(Type::getStandardTypes());
}
/**
* Replaces standard types with types from this list (matching by name)
* Standard types not listed here remain untouched.
*
* @param Type[] $types
*
* @api
*/
public static function overrideStandardTypes(array $types)
{
Type::overrideStandardTypes($types);
}
/**
* Returns standard validation rules implementing GraphQL spec
*
* @return ValidationRule[]
*
* @api
* @return AbstractValidationRule[]
*/
public static function getStandardValidationRules()
public static function getStandardValidationRules() : array
{
return array_values(DocumentValidator::defaultRules());
}
/**
* @param callable $fn
* Set default resolver implementation
*
* @api
*/
public static function setDefaultFieldResolver(callable $fn)
public static function setDefaultFieldResolver(callable $fn) : void
{
Executor::setDefaultFieldResolver($fn);
}
/**
* @param PromiseAdapter|null $promiseAdapter
*/
public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
public static function setPromiseAdapter(?PromiseAdapter $promiseAdapter = null) : void
{
Executor::setPromiseAdapter($promiseAdapter);
}
/**
* Experimental: Switch to the new executor
*/
public static function useExperimentalExecutor()
{
Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
}
/**
* Experimental: Switch back to the default executor
*/
public static function useReferenceExecutor()
{
Executor::setImplementationFactory([ReferenceExecutor::class, 'create']);
}
/**
* Returns directives defined in GraphQL spec
*
* @deprecated Renamed to getStandardDirectives
*
* @return Directive[]
*/
public static function getInternalDirectives()
public static function getInternalDirectives() : array
{
return self::getStandardDirectives();
}

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;
interface DefinitionNode
{
/**
* export type DefinitionNode =
* | ExecutableDefinitionNode
* | TypeSystemDefinitionNode; // experimental non-spec addition.
*/
interface DefinitionNode
{
}

View file

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

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

View file

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

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 StringValueNode|null
*/
/** @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

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

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

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

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

View file

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

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