├── .github └── workflows │ └── php.yml ├── .gitignore ├── Changelog.md ├── LICENSE ├── README.md ├── composer.json ├── examples ├── mutation_example.php ├── query_builder_example.php ├── query_example.php └── raw_query_example.php ├── src ├── Auth │ ├── AuthInterface.php │ └── AwsIamAuth.php ├── Client.php ├── Exception │ ├── ArgumentException.php │ ├── AwsRegionNotSetException.php │ ├── InvalidSelectionException.php │ ├── InvalidVariableException.php │ ├── MethodNotSupportedException.php │ ├── MissingAwsSdkPackageException.php │ └── QueryError.php ├── FieldTrait.php ├── InlineFragment.php ├── Mutation.php ├── NestableObject.php ├── Query.php ├── QueryBuilder │ ├── AbstractQueryBuilder.php │ ├── MutationBuilder.php │ ├── QueryBuilder.php │ └── QueryBuilderInterface.php ├── RawObject.php ├── Results.php ├── Util │ ├── GuzzleAdapter.php │ └── StringLiteralFormatter.php └── Variable.php └── tests ├── Auth └── AwsIamAuthTest.php ├── ClientTest.php ├── InlineFragmentTest.php ├── MutationBuilderTest.php ├── MutationTest.php ├── QueryBuilderTest.php ├── QueryErrorTest.php ├── QueryTest.php ├── RawObjectTest.php ├── ResultsTest.php ├── StringLiteralFormatterTest.php └── VariableTest.php /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | php: [7.3, 7.4, 8.0] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Validate composer.json and composer.lock 21 | run: composer validate --strict 22 | 23 | - name: Cache Composer packages 24 | id: composer-cache 25 | uses: actions/cache@v2 26 | with: 27 | path: vendor 28 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-php- 31 | 32 | - name: Install dependencies 33 | run: composer install --prefer-dist --no-progress 34 | 35 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" 36 | # Docs: https://getcomposer.org/doc/articles/scripts.md 37 | 38 | - name: Run test suite 39 | run: composer run test 40 | env: 41 | AWS_ACCESS_KEY_ID: key 42 | AWS_SECRET_ACCESS_KEY: secret 43 | AWS_SESSION_TOKEN: token 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .idea/ 3 | local_*/ 4 | .phpunit.result.cache 5 | /build/ 6 | composer.lock 7 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | The change log describes what is "Added", "Removed", "Changed" or "Fixed" 4 | between each release. 5 | 6 | ## Tech Debt 7 | 8 | - Refactor the query conversion to string to separate the process of 9 | constructing a new query and adding a nested subfield 10 | 11 | ## Unreleased 12 | 13 | ## 1.10 14 | 15 | ### Added 16 | 17 | - Added support for MutationBuilder 18 | 19 | ### Removed 20 | 21 | - Removed EmptySelectionSetException being thrown from QueryBuilder if selection set is empty 22 | 23 | ## 1.9.2: 24 | 25 | ### Removed 26 | 27 | - Removed support for GET requests by throwing exception when request type is set to GET 28 | 29 | ## 1.9.1: 30 | 31 | ### Changed 32 | 33 | - Modified variable identification logic in string literals 34 | 35 | ## 1.9: 36 | 37 | ### Added 38 | 39 | - Added PHP8 support 40 | 41 | ## 1.8: 42 | 43 | ### Changed 44 | 45 | - Updated Query class to allow for an alias 46 | - Updated QueryBuilder class to allow for an alias 47 | 48 | ## 1.7: 49 | 50 | ### Added 51 | 52 | - Added ability to set request http method type in client 53 | - Added Guzzle7 to composer.json 54 | - Added PHP7.4 to travis test environments 55 | 56 | ## 1.6.1: 57 | 58 | ### Changed: 59 | 60 | - Removed empty braces added in the selection set when no fields are selected 61 | 62 | ## 1.6: 63 | 64 | ### Added: 65 | 66 | - Added ability to inject and use PSR-18 compatible HTTP client in sending requests to GraphQL server 67 | 68 | ## 1.5: 69 | 70 | ### Added 71 | 72 | - Ability to create multiple queries in one object using Query class 73 | - Ability to create multiple queries in one object using QueryBuilder class 74 | 75 | ## 1.4: 76 | 77 | ### Added 78 | 79 | - Support for passing guzzle httpOptions that overrides the authorizationHeaders 80 | 81 | ### Changed 82 | 83 | - Replaced all new line characters "\n" with PHP_EOL 84 | 85 | ## 1.3: 2019-08-03 86 | 87 | ### Added 88 | 89 | - Support for inline fragments 90 | 91 | ## 1.2: 2019-07-24 92 | 93 | ### Added 94 | 95 | - Variables support for queries and mutations 96 | - Operation name during in queries 97 | 98 | ### Fixed 99 | 100 | - Issue in mutation string generation when no selection set is provided 101 | 102 | ## 1.1: 2019-04-26 103 | 104 | ### Added 105 | 106 | - Mutation class for running mutation operations 107 | - Examples in the README for running 108 | - Mutation operation 109 | - Raw string queries 110 | - New badges to README to show downloads count, license, and latest version 111 | - Changelog 112 | 113 | ### Changed 114 | 115 | - `Client::runQuery` method now accepts `QueryBuilderInterface` as well as 116 | `Query` 117 | 118 | 119 | ## 1.0: 2019-04-19 120 | 121 | ### Removed 122 | 123 | - Moved all schema generation logic to a separate repository 124 | 125 | 126 | ## 0.6: 2019-04-09 127 | 128 | ### Added 129 | 130 | - Generate ArgumentsObject classes for all argument lists on fields 131 | - Ability to override default schema writing director with a custom one 132 | - Ability to generate classes with custom namespaces 133 | - Pushed code coverage to 100% 134 | 135 | ### Changed 136 | 137 | - Refactored schema class generation mechanism to traverse the API schema from 138 | the root queryType 139 | - Refactored QueryObject class to accommodate changes in the schema generation 140 | - Removed arguments from QueryObject classes and moved them to 141 | ArgumentsObjects 142 | - Modified how generation works to accommodate ArgumentsObjects nested 143 | within QueryObjects 144 | - Added ArgumentsObject argument to all field selector methods 145 | - Refactored Query class 146 | - Added the ability to set Query object name to 'query' 147 | 148 | 149 | 150 | ## 0.5: 2019-03-25 151 | 152 | ### Added 153 | 154 | - Query builder functionality 155 | 156 | ### Changed 157 | 158 | - Minimum PHP version form 7.2 to 7.1 159 | 160 | ### Fixed 161 | 162 | - Throw QueryError on 400 response 163 | - Throw QueryError in missing scenario (specifying result as array) 164 | 165 | ### Removed 166 | 167 | - composer.lock from version control 168 | 169 | 170 | ## 0.4: 2019-03-25 171 | 172 | ### Added 173 | 174 | - Support for Travis CI 175 | - Installation section to README 176 | - Codacy support for analyzing code 177 | - Raised code coverage 178 | - Generator script to composer file to add it composer bin 179 | 180 | ### Changed 181 | 182 | - Upgraded to PHP7 (7.2 minimum version) 183 | 184 | 185 | ## 0.3.4: 2019-03-02 186 | 187 | ### Fixed 188 | 189 | - Issue in README examples 190 | 191 | 192 | ## 0.3.3: 2019-03-02 193 | 194 | ### Fixed: 195 | 196 | - Issue in package root directory name 197 | 198 | 199 | ## 0.3.2: 2019-03-02 200 | 201 | ### Fixed: 202 | 203 | - Autoload issue in schema classes generation 204 | 205 | 206 | ## 0.3.1: 2019-03-02 207 | 208 | ### Fixed: 209 | 210 | - Issue in input object generation 211 | 212 | 213 | ## 0.3: 2019-03-02 214 | 215 | ### Added: 216 | 217 | - Generation of filters and arguments depending on type 218 | - Input object setters for input object arguments 219 | 220 | 221 | ## 0.2: 2019-02-16 222 | 223 | ### Added 224 | 225 | - Auto schema generation using GraphQL inspection ability 226 | - Throw QueryError if syntax errors are detected by the GraphQL server 227 | - Unit tests to cover basic functionality 228 | 229 | 230 | ## 0.1.4: 2019-01-18 231 | 232 | ### Added 233 | 234 | - Ability to run raw string queries 235 | - Set default content type 236 | 237 | ### Fixed 238 | 239 | - String values do not get wrapped in quotations in the arguments construction 240 | 241 | 242 | ## 0.1.3: 2018-10-10 243 | 244 | ### Fixed 245 | 246 | - Fixed issue in generating query when no arguments are provided 247 | 248 | 249 | ## 0.1.2: 2018-10-07 250 | 251 | ### Fixed 252 | 253 | - Typo in namespace declaration 254 | 255 | ## 0.1.1: 2018-10-07 256 | 257 | 258 | ### Changed 259 | 260 | - Upgrade Guzzle from 5.x to 6.x 261 | 262 | 263 | ## 0.1: 2018-10-07 264 | 265 | - First release 266 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 mghoneimy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP GraphQL Client 2 | ![Build Status](https://github.com/mghoneimy/php-graphql-client/actions/workflows/php.yml/badge.svg) 3 | [![Total 4 | Downloads](https://poser.pugx.org/gmostafa/php-graphql-client/downloads)](https://packagist.org/packages/gmostafa/php-graphql-client) 5 | [![Latest Stable 6 | Version](https://poser.pugx.org/gmostafa/php-graphql-client/v/stable)](https://packagist.org/packages/gmostafa/php-graphql-client) 7 | [![License](https://poser.pugx.org/gmostafa/php-graphql-client/license)](https://packagist.org/packages/gmostafa/php-graphql-client) 8 | 9 | A GraphQL client written in PHP which provides very simple, yet powerful, query 10 | generator classes that make the process of interacting with a GraphQL server a 11 | very simple one. 12 | 13 | # Usage 14 | 15 | There are 3 primary ways to use this package to generate your GraphQL queries: 16 | 17 | 1. Query Class: Simple class that maps to GraphQL queries. It's designed to 18 | manipulate queries with ease and speed. 19 | 2. QueryBuilder Class: Builder class that can be used to generate `Query` 20 | objects dynamically. It's design to be used in cases where a query is being 21 | build in a dynamic fashion. 22 | 3. PHP GraphQL-OQM: An extension to this package. It Eliminates the need to 23 | write any GraphQL queries or refer to the API documentation or syntax. It 24 | generates query objects from the API schema, declaration exposed through 25 | GraphQL's introspection, which can then be simply interacted with. 26 | 27 | # Installation 28 | 29 | Run the following command to install the package using composer: 30 | 31 | ``` 32 | $ composer require gmostafa/php-graphql-client 33 | ``` 34 | 35 | # Object-to-Query-Mapper Extension 36 | 37 | To avoid the hassle of having to write _any_ queries and just interact with PHP 38 | objects generated from your API schema visit [PHP GraphQL OQM repository]( 39 | https://github.com/mghoneimy/php-graphql-oqm) 40 | 41 | # Query Examples 42 | 43 | ## Simple Query 44 | 45 | ```php 46 | $gql = (new Query('companies')) 47 | ->setSelectionSet( 48 | [ 49 | 'name', 50 | 'serialNumber' 51 | ] 52 | ); 53 | ``` 54 | 55 | This simple query will retrieve all companies displaying their names and serial 56 | numbers. 57 | 58 | ### The Full Form 59 | 60 | The query provided in the previous example is represented in the 61 | "shorthand form". The shorthand form involves writing a reduced number of code 62 | lines which speeds up the process of wriing querries. Below is an example of 63 | the full form for the exact same query written in the previous example. 64 | 65 | ```php 66 | $gql = (new Query()) 67 | ->setSelectionSet( 68 | [ 69 | (new Query('companies')) 70 | ->setSelectionSet( 71 | [ 72 | 'name', 73 | 'serialNumber' 74 | ] 75 | ) 76 | ] 77 | ); 78 | ``` 79 | 80 | As seen in the example, the shorthand form is simpler to read and write, it's 81 | generally preferred to use compared to the full form. 82 | 83 | The full form shouldn't be used unless the query can't be represented in the 84 | shorthand form, which has only one case, when we want to run multiple queries 85 | in the same object. 86 | 87 | 88 | ## Multiple Queries 89 | ```php 90 | $gql = (new Query()) 91 | ->setSelectionSet( 92 | [ 93 | (new Query('companies')) 94 | ->setSelectionSet( 95 | [ 96 | 'name', 97 | 'serialNumber' 98 | ] 99 | ), 100 | (new Query('countries')) 101 | ->setSelectionSet( 102 | [ 103 | 'name', 104 | 'code', 105 | ] 106 | ) 107 | ] 108 | ); 109 | ``` 110 | 111 | This query retrieves all companies and countries displaying some data fields 112 | for each. It basically runs two (or more if needed) independent queries in 113 | one query object envelop. 114 | 115 | Writing multiple queries requires writing the query object in the full form 116 | to represent each query as a subfield under the parent query object. 117 | 118 | ## Nested Queries 119 | ```php 120 | $gql = (new Query('companies')) 121 | ->setSelectionSet( 122 | [ 123 | 'name', 124 | 'serialNumber', 125 | (new Query('branches')) 126 | ->setSelectionSet( 127 | [ 128 | 'address', 129 | (new Query('contracts')) 130 | ->setSelectionSet(['date']) 131 | ] 132 | ) 133 | ] 134 | ); 135 | ``` 136 | 137 | This query is a more complex one, retrieving not just scalar fields, but object 138 | fields as well. This query returns all companies, displaying their names, serial 139 | numbers, and for each company, all its branches, displaying the branch address, 140 | and for each address, it retrieves all contracts bound to this address, 141 | displaying their dates. 142 | 143 | ## Query With Arguments 144 | 145 | ```php 146 | $gql = (new Query('companies')) 147 | ->setArguments(['name' => 'Tech Co.', 'first' => 3]) 148 | ->setSelectionSet( 149 | [ 150 | 'name', 151 | 'serialNumber' 152 | ] 153 | ); 154 | ``` 155 | 156 | This query does not retrieve all companies by adding arguments. This query will 157 | retrieve the first 3 companies with the name "Tech Co.", displaying their names 158 | and serial numbers. 159 | 160 | ## Query With Array Argument 161 | 162 | ```php 163 | $gql = (new Query('companies')) 164 | ->setArguments(['serialNumbers' => [159, 260, 371]]) 165 | ->setSelectionSet( 166 | [ 167 | 'name', 168 | 'serialNumber' 169 | ] 170 | ); 171 | ``` 172 | 173 | This query is a special case of the arguments query. In this example, the query 174 | will retrieve only the companies with serial number in one of 159, 260, and 371, 175 | displaying the name and serial number. 176 | 177 | ## Query With Input Object Argument 178 | 179 | ```php 180 | $gql = (new Query('companies')) 181 | ->setArguments(['filter' => new RawObject('{name_starts_with: "Face"}')]) 182 | ->setSelectionSet( 183 | [ 184 | 'name', 185 | 'serialNumber' 186 | ] 187 | ); 188 | ``` 189 | 190 | This query is another special case of the arguments query. In this example, 191 | we're setting a custom input object "filter" with some values to limit the 192 | companies being returned. We're setting the filter "name_starts_with" with 193 | value "Face". This query will retrieve only the companies whose names 194 | start with the phrase "Face". 195 | 196 | The RawObject class being constructed is used for injecting the string into the 197 | query as it is. Whatever string is input into the RawObject constructor will be 198 | put in the query as it is without any custom formatting normally done by the 199 | query class. 200 | 201 | ## Query With Variables 202 | 203 | ```php 204 | $gql = (new Query('companies')) 205 | ->setVariables( 206 | [ 207 | new Variable('name', 'String', true), 208 | new Variable('limit', 'Int', false, 5) 209 | ] 210 | ) 211 | ->setArguments(['name' => '$name', 'first' => '$limit']) 212 | ->setSelectionSet( 213 | [ 214 | 'name', 215 | 'serialNumber' 216 | ] 217 | ); 218 | ``` 219 | 220 | This query shows how variables can be used in this package to allow for dynamic 221 | requests enabled by GraphQL standards. 222 | 223 | ### The Variable Class 224 | 225 | The Variable class is an immutable class that represents a variable in GraphQL 226 | standards. Its constructor receives 4 arguments: 227 | 228 | - name: Represents the variable name 229 | - type: Represents the variable type according to the GraphQL server schema 230 | - isRequired (Optional): Represents if the variable is required or not, it's 231 | false by default 232 | - defaultValue (Optional): Represents the default value to be assigned to the 233 | variable. The default value will only be considered 234 | if the isRequired argument is set to false. 235 | 236 | ## Using an alias 237 | ```php 238 | $gql = (new Query()) 239 | ->setSelectionSet( 240 | [ 241 | (new Query('companies', 'TechCo')) 242 | ->setArguments(['name' => 'Tech Co.']) 243 | ->setSelectionSet( 244 | [ 245 | 'name', 246 | 'serialNumber' 247 | ] 248 | ), 249 | (new Query('companies', 'AnotherTechCo')) 250 | ->setArguments(['name' => 'A.N. Other Tech Co.']) 251 | ->setSelectionSet( 252 | [ 253 | 'name', 254 | 'serialNumber' 255 | ] 256 | ) 257 | ] 258 | ); 259 | ``` 260 | 261 | An alias can be set in the second argument of the Query constructor for occasions when the same object needs to be retrieved multiple times with different arguments. 262 | 263 | ```php 264 | $gql = (new Query('companies')) 265 | ->setAlias('CompanyAlias') 266 | ->setSelectionSet( 267 | [ 268 | 'name', 269 | 'serialNumber' 270 | ] 271 | ); 272 | ``` 273 | 274 | The alias can also be set via the setter method. 275 | 276 | ## Using Interfaces: Query With Inline Fragments 277 | 278 | When querying a field that returns an interface type, you might need to use 279 | inline fragments to access data on the underlying concrete type. 280 | 281 | This example show how to generate inline fragments using this package: 282 | 283 | ```php 284 | $gql = new Query('companies'); 285 | $gql->setSelectionSet( 286 | [ 287 | 'serialNumber', 288 | 'name', 289 | (new InlineFragment('PrivateCompany')) 290 | ->setSelectionSet( 291 | [ 292 | 'boardMembers', 293 | 'shareholders', 294 | ] 295 | ), 296 | ] 297 | ); 298 | ``` 299 | 300 | # The Query Builder 301 | 302 | The QueryBuilder class can be used to construct Query objects dynamically, which 303 | can be useful in some cases. It works very similarly to the Query class, but the 304 | Query building is divided into steps. 305 | 306 | That's how the "Query With Input Object Argument" example can be created using 307 | the QueryBuilder: 308 | 309 | ```php 310 | $builder = (new QueryBuilder('companies')) 311 | ->setVariable('namePrefix', 'String', true) 312 | ->setArgument('filter', new RawObject('{name_starts_with: $namePrefix}')) 313 | ->selectField('name') 314 | ->selectField('serialNumber'); 315 | $gql = $builder->getQuery(); 316 | ``` 317 | 318 | As with the Query class, an alias can be set using the second constructor argument. 319 | 320 | ```php 321 | $builder = (new QueryBuilder('companies', 'CompanyAlias')) 322 | ->selectField('name') 323 | ->selectField('serialNumber'); 324 | 325 | $gql = $builder->getQuery(); 326 | ``` 327 | 328 | Or via the setter method 329 | 330 | ```php 331 | $builder = (new QueryBuilder('companies')) 332 | ->setAlias('CompanyAlias') 333 | ->selectField('name') 334 | ->selectField('serialNumber'); 335 | 336 | $gql = $builder->getQuery(); 337 | ``` 338 | 339 | ### The Full Form 340 | 341 | Just like the Query class, the QueryBuilder class can be written in full form to 342 | enable writing multiple queries under one query builder object. Below is an 343 | example for how the full form can be used with the QueryBuilder: 344 | 345 | ```php 346 | $builder = (new QueryBuilder()) 347 | ->setVariable('namePrefix', 'String', true) 348 | ->selectField( 349 | (new QueryBuilder('companies')) 350 | ->setArgument('filter', new RawObject('{name_starts_with: $namePrefix}')) 351 | ->selectField('name') 352 | ->selectField('serialNumber') 353 | ) 354 | ->selectField( 355 | (new QueryBuilder('company')) 356 | ->setArgument('serialNumber', 123) 357 | ->selectField('name') 358 | ); 359 | $gql = $builder->getQuery(); 360 | ``` 361 | 362 | This query is an extension to the query in the previous example. It returns all 363 | companies starting with a name prefix and returns the company with the 364 | `serialNumber` of value 123, both in the same response. 365 | 366 | # Constructing The Client 367 | 368 | A Client object can easily be instantiated by providing the GraphQL endpoint 369 | URL. 370 | 371 | The Client constructor also receives an optional "authorizationHeaders" 372 | array, which can be used to add authorization headers to all requests being sent 373 | to the GraphQL server. 374 | 375 | Example: 376 | 377 | ```php 378 | $client = new Client( 379 | 'http://api.graphql.com', 380 | ['Authorization' => 'Basic xyz'] 381 | ); 382 | ``` 383 | 384 | 385 | The Client constructor also receives an optional "httpOptions" array, which 386 | **overrides** the "authorizationHeaders" and can be used to add custom 387 | [Guzzle HTTP Client request options](https://guzzle.readthedocs.io/en/latest/request-options.html). 388 | 389 | Example: 390 | 391 | ```php 392 | $client = new Client( 393 | 'http://api.graphql.com', 394 | [], 395 | [ 396 | 'connect_timeout' => 5, 397 | 'timeout' => 5, 398 | 'headers' => [ 399 | 'Authorization' => 'Basic xyz' 400 | 'User-Agent' => 'testing/1.0', 401 | ], 402 | 'proxy' => [ 403 | 'http' => 'tcp://localhost:8125', // Use this proxy with "http" 404 | 'https' => 'tcp://localhost:9124', // Use this proxy with "https", 405 | 'no' => ['.mit.edu', 'foo.com'] // Don't use a proxy with these 406 | ], 407 | 'cert' => ['/path/server.pem', 'password'] 408 | ... 409 | ] 410 | ); 411 | ``` 412 | 413 | 414 | It is possible to use your own preconfigured HTTP client that implements the [PSR-18 interface](https://www.php-fig.org/psr/psr-18/). 415 | 416 | Example: 417 | 418 | ```php 419 | $client = new Client( 420 | 'http://api.graphql.com', 421 | [], 422 | [], 423 | $myHttpClient 424 | ); 425 | ``` 426 | 427 | # Running Queries 428 | 429 | ## Result Formatting 430 | 431 | Running query with the GraphQL client and getting the results in object 432 | structure: 433 | 434 | ```php 435 | $results = $client->runQuery($gql); 436 | $results->getData()->companies[0]->branches; 437 | ``` 438 | Or getting results in array structure: 439 | 440 | ```php 441 | $results = $client->runQuery($gql, true); 442 | $results->getData()['companies'][1]['branches']['address']; 443 | ``` 444 | 445 | ## Passing Variables to Queries 446 | 447 | Running queries containing variables requires passing an associative array which 448 | maps variable names (keys) to variable values (values) to the `runQuery` method. 449 | Here's an example: 450 | 451 | ```php 452 | $gql = (new Query('companies')) 453 | ->setVariables( 454 | [ 455 | new Variable('name', 'String', true), 456 | new Variable('limit', 'Int', false, 5) 457 | ] 458 | ) 459 | ->setArguments(['name' => '$name', 'first' => '$limit']) 460 | ->setSelectionSet( 461 | [ 462 | 'name', 463 | 'serialNumber' 464 | ] 465 | ); 466 | $variablesArray = ['name' => 'Tech Co.', 'first' => 5]; 467 | $results = $client->runQuery($gql, true, $variablesArray); 468 | ``` 469 | 470 | # Mutations 471 | 472 | Mutations follow the same rules of queries in GraphQL, they select fields on 473 | returned objects, receive arguments, and can have sub-fields. 474 | 475 | Here's a sample example on how to construct and run mutations: 476 | 477 | ```php 478 | $mutation = (new Mutation('createCompany')) 479 | ->setArguments(['companyObject' => new RawObject('{name: "Trial Company", employees: 200}')]) 480 | ->setSelectionSet( 481 | [ 482 | '_id', 483 | 'name', 484 | 'serialNumber', 485 | ] 486 | ); 487 | $results = $client->runQuery($mutation); 488 | ``` 489 | 490 | Mutations can be run by the client the same way queries are run. 491 | 492 | ## Mutations With Variables Example 493 | 494 | Mutations can utilize the variables in the same way Queries can. Here's an 495 | example on how to use the variables to pass input objects to the GraphQL server 496 | dynamically: 497 | 498 | ```php 499 | $mutation = (new Mutation('createCompany')) 500 | ->setVariables([new Variable('company', 'CompanyInputObject', true)]) 501 | ->setArguments(['companyObject' => '$company']); 502 | 503 | $variables = ['company' => ['name' => 'Tech Company', 'type' => 'Testing', 'size' => 'Medium']]; 504 | $client->runQuery( 505 | $mutation, true, $variables 506 | ); 507 | ``` 508 | 509 | These are the resulting mutation and the variables passed with it: 510 | 511 | ```php 512 | mutation($company: CompanyInputObject!) { 513 | createCompany(companyObject: $company) 514 | } 515 | {"company":{"name":"Tech Company","type":"Testing","size":"Medium"}} 516 | ``` 517 | 518 | # Live API Example 519 | 520 | GraphQL Pokemon is a very cool public GraphQL API available to retrieve Pokemon 521 | data. The API is available publicly on the web, we'll use it to demo the 522 | capabilities of this client. 523 | 524 | Github Repo link: https://github.com/lucasbento/graphql-pokemon 525 | 526 | API link: https://graphql-pokemon.now.sh/ 527 | 528 | This query retrieves any pokemon's evolutions and their attacks: 529 | 530 | ```php 531 | query($name: String!) { 532 | pokemon(name: $name) { 533 | id 534 | number 535 | name 536 | evolutions { 537 | id 538 | number 539 | name 540 | weight { 541 | minimum 542 | maximum 543 | } 544 | attacks { 545 | fast { 546 | name 547 | type 548 | damage 549 | } 550 | } 551 | } 552 | } 553 | } 554 | 555 | ``` 556 | 557 | That's how this query can be written using the query class and run using the 558 | client: 559 | 560 | ```php 561 | $client = new Client( 562 | 'https://graphql-pokemon.now.sh/' 563 | ); 564 | $gql = (new Query('pokemon')) 565 | ->setVariables([new Variable('name', 'String', true)]) 566 | ->setArguments(['name' => '$name']) 567 | ->setSelectionSet( 568 | [ 569 | 'id', 570 | 'number', 571 | 'name', 572 | (new Query('evolutions')) 573 | ->setSelectionSet( 574 | [ 575 | 'id', 576 | 'number', 577 | 'name', 578 | (new Query('attacks')) 579 | ->setSelectionSet( 580 | [ 581 | (new Query('fast')) 582 | ->setSelectionSet( 583 | [ 584 | 'name', 585 | 'type', 586 | 'damage', 587 | ] 588 | ) 589 | ] 590 | ) 591 | ] 592 | ) 593 | ] 594 | ); 595 | try { 596 | $name = readline('Enter pokemon name: '); 597 | $results = $client->runQuery($gql, true, ['name' => $name]); 598 | } 599 | catch (QueryError $exception) { 600 | print_r($exception->getErrorDetails()); 601 | exit; 602 | } 603 | print_r($results->getData()['pokemon']); 604 | ``` 605 | 606 | Or alternatively, That's how this query can be generated using the QueryBuilder 607 | class: 608 | 609 | ```php 610 | $client = new Client( 611 | 'https://graphql-pokemon.now.sh/' 612 | ); 613 | $builder = (new QueryBuilder('pokemon')) 614 | ->setVariable('name', 'String', true) 615 | ->setArgument('name', '$name') 616 | ->selectField('id') 617 | ->selectField('number') 618 | ->selectField('name') 619 | ->selectField( 620 | (new QueryBuilder('evolutions')) 621 | ->selectField('id') 622 | ->selectField('name') 623 | ->selectField('number') 624 | ->selectField( 625 | (new QueryBuilder('attacks')) 626 | ->selectField( 627 | (new QueryBuilder('fast')) 628 | ->selectField('name') 629 | ->selectField('type') 630 | ->selectField('damage') 631 | ) 632 | ) 633 | ); 634 | try { 635 | $name = readline('Enter pokemon name: '); 636 | $results = $client->runQuery($builder, true, ['name' => $name]); 637 | } 638 | catch (QueryError $exception) { 639 | print_r($exception->getErrorDetails()); 640 | exit; 641 | } 642 | print_r($results->getData()['pokemon']); 643 | ``` 644 | 645 | # Running Raw Queries 646 | 647 | Although not the primary goal of this package, but it supports running raw 648 | string queries, just like any other client using the `runRawQuery` method in the 649 | `Client` class. Here's an example on how to use it: 650 | 651 | ```php 652 | $gql = <<runRawQuery($gql); 670 | ``` 671 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gmostafa/php-graphql-client", 3 | "description": "GraphQL client and query builder.", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "graphql", 8 | "graph-ql", 9 | "client", 10 | "php", 11 | "query-builder", 12 | "query", 13 | "builder" 14 | ], 15 | "authors": [ 16 | { 17 | "name": "Mostafa Ghoneimy", 18 | "email": "emostafagh@gmail.com" 19 | } 20 | ], 21 | "autoload": { 22 | "psr-4": { 23 | "GraphQL\\": "src/" 24 | } 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "GraphQL\\Tests\\": "tests/" 29 | } 30 | }, 31 | "require": { 32 | "php": "^7.1 || ^8.0", 33 | "ext-json": "*", 34 | "psr/http-message": "^1.0", 35 | "psr/http-client": "^1.0", 36 | "guzzlehttp/guzzle": "^6.3|^7.0.1" 37 | }, 38 | "require-dev": { 39 | "phpunit/phpunit": "^7.5|^8.0|^9.0", 40 | "codacy/coverage": "^1.4", 41 | "aws/aws-sdk-php": "^3.186" 42 | }, 43 | "conflict": { 44 | "guzzlehttp/psr7": "< 1.7.0" 45 | }, 46 | "scripts": { 47 | "test": "phpunit tests/ --whitelist src/ --coverage-clover build/coverage/xml" 48 | }, 49 | "suggest": { 50 | "aws/aws-sdk-php": "Move this package to require section to use AWS IAM authorization", 51 | "gmostafa/php-graphql-oqm": "To have object-to-query mapping support" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/mutation_example.php: -------------------------------------------------------------------------------- 1 | setArguments(['companyObject' => new RawObject('{name: "Trial Company", employees: 200}')]) 20 | ->setSelectionSet( 21 | [ 22 | '_id', 23 | 'name', 24 | 'serialNumber', 25 | ] 26 | ); 27 | 28 | // Run query to get results 29 | try { 30 | $results = $client->runQuery($gql); 31 | } 32 | catch (QueryError $exception) { 33 | 34 | // Catch query error and desplay error details 35 | print_r($exception->getErrorDetails()); 36 | exit; 37 | } 38 | 39 | // Display original response from endpoint 40 | var_dump($results->getResponseObject()); 41 | 42 | // Display part of the returned results of the object 43 | var_dump($results->getData()->pokemon); 44 | 45 | // Reformat the results to an array and get the results of part of the array 46 | $results->reformatResults(true); 47 | print_r($results->getData()['pokemon']); -------------------------------------------------------------------------------- /examples/query_builder_example.php: -------------------------------------------------------------------------------- 1 | setArgument('name', 'Pikachu') 18 | ->selectField('id') 19 | ->selectField('number') 20 | ->selectField('name') 21 | ->selectField( 22 | (new QueryBuilder('attacks')) 23 | ->selectField( 24 | (new QueryBuilder('special')) 25 | ->selectField('name') 26 | ->selectField('type') 27 | ->selectField('damage') 28 | ) 29 | ) 30 | ->selectField( 31 | (new QueryBuilder('evolutions')) 32 | ->selectField('id') 33 | ->selectField('name') 34 | ->selectField('number') 35 | ->selectField( 36 | (new QueryBuilder('attacks')) 37 | ->selectField( 38 | (new QueryBuilder('fast')) 39 | ->selectField('name') 40 | ->selectField('type') 41 | ->selectField('damage') 42 | ) 43 | ) 44 | ); 45 | 46 | // Run query to get results 47 | try { 48 | $results = $client->runQuery($builder); 49 | } 50 | catch (QueryError $exception) { 51 | 52 | // Catch query error and desplay error details 53 | print_r($exception->getErrorDetails()); 54 | exit; 55 | } 56 | 57 | // Display original response from endpoint 58 | var_dump($results->getResponseObject()); 59 | 60 | // Display part of the returned results of the object 61 | var_dump($results->getData()->pokemon); 62 | 63 | // Reformat the results to an array and get the results of part of the array 64 | $results->reformatResults(true); 65 | print_r($results->getData()['pokemon']); -------------------------------------------------------------------------------- /examples/query_example.php: -------------------------------------------------------------------------------- 1 | setArguments(['name' => 'Pikachu']) 19 | ->setSelectionSet( 20 | [ 21 | 'id', 22 | 'number', 23 | 'name', 24 | (new Query('attacks')) 25 | ->setSelectionSet( 26 | [ 27 | (new Query('special')) 28 | ->setSelectionSet( 29 | [ 30 | 'name', 31 | 'type', 32 | 'damage', 33 | ] 34 | ) 35 | ] 36 | 37 | ), 38 | (new Query('evolutions')) 39 | ->setSelectionSet( 40 | [ 41 | 'id', 42 | 'number', 43 | 'name', 44 | (new Query('attacks')) 45 | ->setSelectionSet( 46 | [ 47 | (new Query('fast')) 48 | ->setSelectionSet( 49 | [ 50 | 'name', 51 | 'type', 52 | 'damage', 53 | ] 54 | ) 55 | ] 56 | ) 57 | ] 58 | ) 59 | ] 60 | ); 61 | 62 | // Run query to get results 63 | try { 64 | $results = $client->runQuery($gql); 65 | } 66 | catch (QueryError $exception) { 67 | 68 | // Catch query error and desplay error details 69 | print_r($exception->getErrorDetails()); 70 | exit; 71 | } 72 | 73 | // Display original response from endpoint 74 | var_dump($results->getResponseObject()); 75 | 76 | // Display part of the returned results of the object 77 | var_dump($results->getData()->pokemon); 78 | 79 | // Reformat the results to an array and get the results of part of the array 80 | $results->reformatResults(true); 81 | print_r($results->getData()['pokemon']); -------------------------------------------------------------------------------- /examples/raw_query_example.php: -------------------------------------------------------------------------------- 1 | runRawQuery($gql); 34 | } 35 | catch (QueryError $exception) { 36 | 37 | // Catch query error and desplay error details 38 | print_r($exception->getErrorDetails()); 39 | exit; 40 | } 41 | 42 | // Display original response from endpoint 43 | var_dump($results->getResponseObject()); 44 | 45 | // Display part of the returned results of the object 46 | var_dump($results->getData()->pokemon); 47 | 48 | // Reformat the results to an array and get the results of part of the array 49 | $results->reformatResults(true); 50 | print_r($results->getData()['pokemon']); -------------------------------------------------------------------------------- /src/Auth/AuthInterface.php: -------------------------------------------------------------------------------- 1 | getSignature($region)->signRequest( 40 | $request, $this->getCredentials(), 41 | self::SERVICE_NAME 42 | ); 43 | } 44 | 45 | /** 46 | * @param string $region 47 | * @return SignatureV4 48 | */ 49 | protected function getSignature(string $region): SignatureV4 50 | { 51 | return new SignatureV4(self::SERVICE_NAME, $region); 52 | } 53 | 54 | /** 55 | * @return Credentials 56 | */ 57 | protected function getCredentials(): Credentials 58 | { 59 | $provider = CredentialProvider::defaultProvider(); 60 | return $provider()->wait(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | 'application/json'] 76 | ); 77 | /** 78 | * All headers will be set on the request objects explicitly, 79 | * Guzzle doesn't have to care about them at this point, so to avoid any conflicts 80 | * we are removing the headers from the options 81 | */ 82 | unset($httpOptions['headers']); 83 | $this->options = $httpOptions; 84 | if ($auth) { 85 | $this->auth = $auth; 86 | } 87 | 88 | $this->endpointUrl = $endpointUrl; 89 | $this->httpClient = $httpClient ?? new GuzzleAdapter(new \GuzzleHttp\Client($httpOptions)); 90 | $this->httpHeaders = $headers; 91 | if ($requestMethod !== 'POST') { 92 | throw new MethodNotSupportedException($requestMethod); 93 | } 94 | $this->requestMethod = $requestMethod; 95 | } 96 | 97 | /** 98 | * @param Query|QueryBuilderInterface $query 99 | * @param bool $resultsAsArray 100 | * @param array $variables 101 | * 102 | * @return Results 103 | * @throws QueryError 104 | */ 105 | public function runQuery($query, bool $resultsAsArray = false, array $variables = []): Results 106 | { 107 | if ($query instanceof QueryBuilderInterface) { 108 | $query = $query->getQuery(); 109 | } 110 | 111 | if (!$query instanceof Query) { 112 | throw new TypeError('Client::runQuery accepts the first argument of type Query or QueryBuilderInterface'); 113 | } 114 | 115 | return $this->runRawQuery((string) $query, $resultsAsArray, $variables); 116 | } 117 | 118 | /** 119 | * @param string $queryString 120 | * @param bool $resultsAsArray 121 | * @param array $variables 122 | * @param 123 | * 124 | * @return Results 125 | * @throws QueryError 126 | */ 127 | public function runRawQuery(string $queryString, $resultsAsArray = false, array $variables = []): Results 128 | { 129 | $request = new Request($this->requestMethod, $this->endpointUrl); 130 | 131 | foreach($this->httpHeaders as $header => $value) { 132 | $request = $request->withHeader($header, $value); 133 | } 134 | 135 | // Convert empty variables array to empty json object 136 | if (empty($variables)) $variables = (object) null; 137 | // Set query in the request body 138 | $bodyArray = ['query' => (string) $queryString, 'variables' => $variables]; 139 | $request = $request->withBody(Utils::streamFor(json_encode($bodyArray))); 140 | 141 | if ($this->auth) { 142 | $request = $this->auth->run($request, $this->options); 143 | } 144 | 145 | // Send api request and get response 146 | try { 147 | $response = $this->httpClient->sendRequest($request); 148 | } 149 | catch (ClientException $exception) { 150 | $response = $exception->getResponse(); 151 | 152 | // If exception thrown by client is "400 Bad Request ", then it can be treated as a successful API request 153 | // with a syntax error in the query, otherwise the exceptions will be propagated 154 | if ($response->getStatusCode() !== 400) { 155 | throw $exception; 156 | } 157 | } 158 | 159 | // Parse response to extract results 160 | return new Results($response, $resultsAsArray); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Exception/ArgumentException.php: -------------------------------------------------------------------------------- 1 | errorDetails = $errorDetails['errors'][0]; 29 | parent::__construct($this->errorDetails['message']); 30 | } 31 | 32 | /** 33 | * @return array 34 | */ 35 | public function getErrorDetails() 36 | { 37 | return $this->errorDetails; 38 | } 39 | } -------------------------------------------------------------------------------- /src/FieldTrait.php: -------------------------------------------------------------------------------- 1 | selectionSet = $selectionSet; 34 | 35 | return $this; 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | protected function constructSelectionSet(): string 42 | { 43 | if (empty($this->selectionSet)) { 44 | return ''; 45 | } 46 | 47 | $attributesString = " {" . PHP_EOL; 48 | $first = true; 49 | foreach ($this->selectionSet as $attribute) { 50 | 51 | // Append empty line at the beginning if it's not the first item on the list 52 | if ($first) { 53 | $first = false; 54 | } else { 55 | $attributesString .= PHP_EOL; 56 | } 57 | 58 | // If query is included in attributes set as a nested query 59 | if ($attribute instanceof Query) { 60 | $attribute->setAsNested(); 61 | } 62 | 63 | // Append attribute to returned attributes list 64 | $attributesString .= $attribute; 65 | } 66 | $attributesString .= PHP_EOL . "}"; 67 | 68 | return $attributesString; 69 | } 70 | 71 | public function getSelectionSet() 72 | { 73 | return $this->selectionSet; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/InlineFragment.php: -------------------------------------------------------------------------------- 1 | typeName = $typeName; 42 | $this->queryBuilder = $queryBuilder; 43 | } 44 | 45 | /** 46 | * 47 | */ 48 | public function __toString() 49 | { 50 | if ($this->queryBuilder !== null) { 51 | $this->setSelectionSet($this->queryBuilder->getQuery()->getSelectionSet()); 52 | } 53 | 54 | return sprintf(static::FORMAT, $this->typeName, $this->constructSelectionSet()); 55 | } 56 | 57 | /** 58 | * @codeCoverageIgnore 59 | * 60 | * @return mixed|void 61 | */ 62 | protected function setAsNested() 63 | { 64 | // TODO: Remove this method, it's purely tech debt 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Mutation.php: -------------------------------------------------------------------------------- 1 | fieldName = $fieldName; 87 | $this->alias = $alias; 88 | $this->operationName = ''; 89 | $this->variables = []; 90 | $this->arguments = []; 91 | $this->selectionSet = []; 92 | $this->isNested = false; 93 | } 94 | 95 | /** 96 | * @param string $alias 97 | * 98 | * @return Query 99 | */ 100 | public function setAlias(string $alias) 101 | { 102 | $this->alias = $alias; 103 | 104 | return $this; 105 | } 106 | 107 | /** 108 | * @param string $operationName 109 | * 110 | * @return Query 111 | */ 112 | public function setOperationName(string $operationName) 113 | { 114 | if (!empty($operationName)) { 115 | $this->operationName = " $operationName"; 116 | } 117 | 118 | return $this; 119 | } 120 | 121 | /** 122 | * @param array $variables 123 | * 124 | * @return Query 125 | */ 126 | public function setVariables(array $variables) 127 | { 128 | $nonVarElements = array_filter($variables, function($e) { 129 | return !$e instanceof Variable; 130 | }); 131 | if (count($nonVarElements) > 0) { 132 | throw new InvalidVariableException('At least one of the elements of the variables array provided is not an instance of GraphQL\\Variable'); 133 | } 134 | 135 | $this->variables = $variables; 136 | 137 | return $this; 138 | } 139 | 140 | /** 141 | * Throwing exception when setting the arguments if they are incorrect because we can't throw an exception during 142 | * the execution of __ToString(), it's a fatal error in PHP 143 | * 144 | * @param array $arguments 145 | * 146 | * @return Query 147 | * @throws ArgumentException 148 | */ 149 | public function setArguments(array $arguments): Query 150 | { 151 | // If one of the arguments does not have a name provided, throw an exception 152 | $nonStringArgs = array_filter(array_keys($arguments), function($element) { 153 | return !is_string($element); 154 | }); 155 | if (!empty($nonStringArgs)) { 156 | throw new ArgumentException( 157 | 'One or more of the arguments provided for creating the query does not have a key, which represents argument name' 158 | ); 159 | } 160 | 161 | $this->arguments = $arguments; 162 | 163 | return $this; 164 | } 165 | 166 | /** 167 | * @return string 168 | */ 169 | protected function constructVariables(): string 170 | { 171 | if (empty($this->variables)) { 172 | return ''; 173 | } 174 | 175 | $varsString = '('; 176 | $first = true; 177 | foreach ($this->variables as $variable) { 178 | 179 | // Append space at the beginning if it's not the first item on the list 180 | if ($first) { 181 | $first = false; 182 | } else { 183 | $varsString .= ' '; 184 | } 185 | 186 | // Append variable string value to the variables string 187 | $varsString .= (string) $variable; 188 | } 189 | $varsString .= ')'; 190 | 191 | return $varsString; 192 | } 193 | 194 | /** 195 | * @return string 196 | */ 197 | protected function constructArguments(): string 198 | { 199 | // Return empty string if list is empty 200 | if (empty($this->arguments)) { 201 | return ''; 202 | } 203 | 204 | // Construct arguments string if list not empty 205 | $constraintsString = '('; 206 | $first = true; 207 | foreach ($this->arguments as $name => $value) { 208 | 209 | // Append space at the beginning if it's not the first item on the list 210 | if ($first) { 211 | $first = false; 212 | } else { 213 | $constraintsString .= ' '; 214 | } 215 | 216 | // Convert argument values to graphql string literal equivalent 217 | if (is_scalar($value) || $value === null) { 218 | // Convert scalar value to its literal in graphql 219 | $value = StringLiteralFormatter::formatValueForRHS($value); 220 | } elseif (is_array($value)) { 221 | // Convert PHP array to its array representation in graphql arguments 222 | $value = StringLiteralFormatter::formatArrayForGQLQuery($value); 223 | } 224 | // TODO: Handle cases where a non-string-convertible object is added to the arguments 225 | $constraintsString .= $name . ': ' . $value; 226 | } 227 | $constraintsString .= ')'; 228 | 229 | return $constraintsString; 230 | } 231 | 232 | /** 233 | * @return string 234 | */ 235 | public function __toString() 236 | { 237 | $queryFormat = static::QUERY_FORMAT; 238 | $selectionSetString = $this->constructSelectionSet(); 239 | 240 | if (!$this->isNested) { 241 | $queryFormat = $this->generateSignature(); 242 | if ($this->fieldName === '') { 243 | 244 | return $queryFormat . $selectionSetString; 245 | } else { 246 | $queryFormat = $this->generateSignature() . " {" . PHP_EOL . static::QUERY_FORMAT . PHP_EOL . "}"; 247 | } 248 | } 249 | $argumentsString = $this->constructArguments(); 250 | 251 | return sprintf($queryFormat, $this->generateFieldName(), $argumentsString, $selectionSetString); 252 | } 253 | 254 | /** 255 | * @return string 256 | */ 257 | protected function generateFieldName(): string 258 | { 259 | return empty($this->alias) ? $this->fieldName : sprintf('%s: %s', $this->alias, $this->fieldName); 260 | } 261 | 262 | /** 263 | * @return string 264 | */ 265 | protected function generateSignature(): string 266 | { 267 | $signatureFormat = '%s%s%s'; 268 | 269 | return sprintf($signatureFormat, static::OPERATION_TYPE, $this->operationName, $this->constructVariables()); 270 | } 271 | 272 | /** 273 | * 274 | */ 275 | protected function setAsNested() 276 | { 277 | $this->isNested = true; 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/QueryBuilder/AbstractQueryBuilder.php: -------------------------------------------------------------------------------- 1 | query = new Query($queryObject, $alias); 46 | $this->variables = []; 47 | $this->selectionSet = []; 48 | $this->argumentsList = []; 49 | } 50 | 51 | /** 52 | * @param string $alias 53 | * 54 | * @return $this 55 | */ 56 | public function setAlias(string $alias) 57 | { 58 | $this->query->setAlias($alias); 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * @return Query 65 | */ 66 | public function getQuery(): Query 67 | { 68 | // Convert nested query builders to query objects 69 | foreach ($this->selectionSet as $key => $field) { 70 | if ($field instanceof QueryBuilderInterface) { 71 | $this->selectionSet[$key] = $field->getQuery(); 72 | } 73 | } 74 | 75 | $this->query->setVariables($this->variables); 76 | $this->query->setArguments($this->argumentsList); 77 | $this->query->setSelectionSet($this->selectionSet); 78 | 79 | return $this->query; 80 | } 81 | 82 | /** 83 | * @param string|QueryBuilderInterface|InlineFragment|Query $selectedField 84 | * 85 | * @return $this 86 | */ 87 | protected function selectField($selectedField) 88 | { 89 | if ( 90 | is_string($selectedField) 91 | || $selectedField instanceof QueryBuilderInterface 92 | || $selectedField instanceof Query 93 | || $selectedField instanceof InlineFragment 94 | ) { 95 | $this->selectionSet[] = $selectedField; 96 | } 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * @param $argumentName 103 | * @param $argumentValue 104 | * 105 | * @return $this 106 | */ 107 | protected function setArgument(string $argumentName, $argumentValue) 108 | { 109 | if (is_scalar($argumentValue) || is_array($argumentValue) || $argumentValue instanceof RawObject) { 110 | $this->argumentsList[$argumentName] = $argumentValue; 111 | } 112 | 113 | return $this; 114 | } 115 | 116 | /** 117 | * @param string $name 118 | * @param string $type 119 | * @param bool $isRequired 120 | * @param null $defaultValue 121 | * 122 | * @return $this 123 | */ 124 | protected function setVariable(string $name, string $type, bool $isRequired = false, $defaultValue = null) 125 | { 126 | $this->variables[] = new Variable($name, $type, $isRequired, $defaultValue); 127 | 128 | return $this; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/QueryBuilder/MutationBuilder.php: -------------------------------------------------------------------------------- 1 | query = new Mutation($queryObject, $alias); 19 | } 20 | 21 | /** 22 | * Synonymous method to getQuery(), it just return a Mutation type instead of Query type creating a neater 23 | * interface when using interfaces 24 | * 25 | * @return Mutation 26 | */ 27 | public function getMutation(): Mutation 28 | { 29 | return $this->getQuery(); 30 | } 31 | } -------------------------------------------------------------------------------- /src/QueryBuilder/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | objectString = $objectString; 25 | } 26 | 27 | /** 28 | * @return mixed 29 | */ 30 | public function __toString() 31 | { 32 | return $this->objectString; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Results.php: -------------------------------------------------------------------------------- 1 | responseObject = $response; 43 | $this->responseBody = $this->responseObject->getBody()->getContents(); 44 | $this->results = json_decode($this->responseBody, $asArray); 45 | 46 | // Check if any errors exist, and throw exception if they do 47 | if ($asArray) $containsErrors = array_key_exists('errors', $this->results); 48 | else $containsErrors = isset($this->results->errors); 49 | 50 | if ($containsErrors) { 51 | 52 | // Reformat results to an array and use it to initialize exception object 53 | $this->reformatResults(true); 54 | throw new QueryError($this->results); 55 | } 56 | } 57 | 58 | /** 59 | * @param bool $asArray 60 | */ 61 | public function reformatResults(bool $asArray): void 62 | { 63 | $this->results = json_decode($this->responseBody, (bool) $asArray); 64 | } 65 | 66 | /** 67 | * Returns only parsed data objects in the requested format 68 | * 69 | * @return array|object 70 | */ 71 | public function getData() 72 | { 73 | if (is_array($this->results)) { 74 | return $this->results['data']; 75 | } 76 | 77 | return $this->results->data; 78 | } 79 | 80 | /** 81 | * Returns entire parsed results in the requested format 82 | * 83 | * @return array|object 84 | */ 85 | public function getResults() 86 | { 87 | return $this->results; 88 | } 89 | 90 | /** 91 | * @return string 92 | */ 93 | public function getResponseBody() 94 | { 95 | return $this->responseBody; 96 | } 97 | 98 | /** 99 | * @return ResponseInterface 100 | */ 101 | public function getResponseObject() 102 | { 103 | return $this->responseObject; 104 | } 105 | } -------------------------------------------------------------------------------- /src/Util/GuzzleAdapter.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | } 27 | 28 | /** 29 | * @param RequestInterface $request 30 | * 31 | * @return ResponseInterface 32 | * @throws GuzzleException 33 | */ 34 | public function sendRequest(RequestInterface $request): ResponseInterface 35 | { 36 | /** 37 | * We are not catching and converting the guzzle exceptions to psr-18 exceptions 38 | * for backward-compatibility sake 39 | */ 40 | 41 | return $this->client->send($request); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Util/StringLiteralFormatter.php: -------------------------------------------------------------------------------- 1 | name = $name; 45 | $this->type = $type; 46 | $this->required = $isRequired; 47 | $this->defaultValue = $defaultValue; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function __toString(): string 54 | { 55 | $varString = "\$$this->name: $this->type"; 56 | if ($this->required) { 57 | $varString .= '!'; 58 | } elseif (!empty($this->defaultValue)) { 59 | $varString .= '=' . StringLiteralFormatter::formatValueForRHS($this->defaultValue); 60 | } 61 | 62 | return $varString; 63 | } 64 | } -------------------------------------------------------------------------------- /tests/Auth/AwsIamAuthTest.php: -------------------------------------------------------------------------------- 1 | auth = new AwsIamAuth(); 20 | } 21 | 22 | /** 23 | * @covers \GraphQL\Auth\AwsIamAuth::run 24 | * @covers \GraphQL\Exception\AwsRegionNotSetException::__construct 25 | */ 26 | public function testRunMissingRegion() 27 | { 28 | $this->expectException(AwsRegionNotSetException::class); 29 | $request = new Request('POST', ''); 30 | $this->auth->run($request, []); 31 | } 32 | 33 | /** 34 | * @covers \GraphQL\Auth\AwsIamAuth::run 35 | * @covers \GraphQL\Auth\AwsIamAuth::getSignature 36 | * @covers \GraphQL\Auth\AwsIamAuth::getCredentials 37 | */ 38 | public function testRunSuccess() 39 | { 40 | $request = $this->auth->run( 41 | new Request('POST', ''), 42 | ['aws_region' => 'us-east-1'] 43 | ); 44 | $headers = $request->getHeaders(); 45 | $this->assertArrayHasKey('X-Amz-Date', $headers); 46 | $this->assertArrayHasKey('X-Amz-Security-Token', $headers); 47 | $this->assertArrayHasKey('Authorization', $headers); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/ClientTest.php: -------------------------------------------------------------------------------- 1 | mockHandler = new MockHandler(); 44 | $handler = HandlerStack::create($this->mockHandler); 45 | $this->client = new Client('', [], ['handler' => $handler]); 46 | } 47 | 48 | /** 49 | * @covers \GraphQL\Client::__construct 50 | * @covers \GraphQL\Client::runRawQuery 51 | * @covers \GraphQL\Util\GuzzleAdapter::__construct 52 | * @covers \GraphQL\Util\GuzzleAdapter::sendRequest 53 | */ 54 | public function testConstructClient() 55 | { 56 | $mockHandler = new MockHandler(); 57 | $handler = HandlerStack::create($mockHandler); 58 | $container = []; 59 | $history = Middleware::history($container); 60 | $handler->push($history); 61 | 62 | $mockHandler->append(new Response(200)); 63 | $mockHandler->append(new Response(200)); 64 | $mockHandler->append(new Response(200)); 65 | $mockHandler->append(new Response(200)); 66 | $mockHandler->append(new Response(200)); 67 | 68 | $client = new Client('', [], ['handler' => $handler]); 69 | $client->runRawQuery('query_string'); 70 | 71 | $client = new Client('', ['Authorization' => 'Basic xyz'], ['handler' => $handler]); 72 | $client->runRawQuery('query_string'); 73 | 74 | $client = new Client('', [], ['handler' => $handler]); 75 | $client->runRawQuery('query_string', false, ['name' => 'val']); 76 | 77 | $client = new Client('', ['Authorization' => 'Basic xyz'], ['handler' => $handler, 'headers' => [ 'Authorization' => 'Basic zyx', 'User-Agent' => 'test' ]]); 78 | $client->runRawQuery('query_string'); 79 | 80 | /** @var Request $firstRequest */ 81 | $firstRequest = $container[0]['request']; 82 | $this->assertEquals('{"query":"query_string","variables":{}}', $firstRequest->getBody()->getContents()); 83 | $this->assertSame('POST', $firstRequest->getMethod()); 84 | 85 | /** @var Request $thirdRequest */ 86 | $thirdRequest = $container[1]['request']; 87 | $this->assertNotEmpty($thirdRequest->getHeader('Authorization')); 88 | $this->assertEquals( 89 | ['Basic xyz'], 90 | $thirdRequest->getHeader('Authorization') 91 | ); 92 | 93 | /** @var Request $secondRequest */ 94 | $secondRequest = $container[2]['request']; 95 | $this->assertEquals('{"query":"query_string","variables":{"name":"val"}}', $secondRequest->getBody()->getContents()); 96 | 97 | /** @var Request $fourthRequest */ 98 | $fourthRequest = $container[3]['request']; 99 | $this->assertNotEmpty($fourthRequest->getHeader('Authorization')); 100 | $this->assertNotEmpty($fourthRequest->getHeader('User-Agent')); 101 | $this->assertEquals(['Basic zyx'], $fourthRequest->getHeader('Authorization')); 102 | $this->assertEquals(['test'], $fourthRequest->getHeader('User-Agent')); 103 | } 104 | 105 | /** 106 | * @covers \GraphQL\Client::__construct 107 | * @covers \GraphQL\Exception\MethodNotSupportedException 108 | */ 109 | public function testConstructClientWithGetRequestMethod() 110 | { 111 | $this->expectException(MethodNotSupportedException::class); 112 | $client = new Client('', [], [], null, 'GET'); 113 | } 114 | 115 | /** 116 | * @covers \GraphQL\Client::runQuery 117 | */ 118 | public function testRunQueryBuilder() 119 | { 120 | $this->mockHandler->append(new Response(200, [], json_encode([ 121 | 'data' => [ 122 | 'someData' 123 | ] 124 | ]))); 125 | 126 | $response = $this->client->runQuery((new QueryBuilder('obj'))->selectField('field')); 127 | $this->assertNotNull($response->getData()); 128 | } 129 | 130 | /** 131 | * @covers \GraphQL\Client::runQuery 132 | */ 133 | public function testRunInvalidQueryClass() 134 | { 135 | $this->expectException(TypeError::class); 136 | $this->client->runQuery(new RawObject('obj')); 137 | } 138 | 139 | /** 140 | * @covers \GraphQL\Client::runRawQuery 141 | */ 142 | public function testValidQueryResponse() 143 | { 144 | $this->mockHandler->append(new Response(200, [], json_encode([ 145 | 'data' => [ 146 | 'someField' => [ 147 | [ 148 | 'data' => 'value', 149 | ], [ 150 | 'data' => 'value', 151 | ] 152 | ] 153 | ] 154 | ]))); 155 | 156 | $objectResults = $this->client->runRawQuery(''); 157 | $this->assertIsObject($objectResults->getResults()); 158 | } 159 | 160 | /** 161 | * @covers \GraphQL\Client::runRawQuery 162 | */ 163 | public function testValidQueryResponseToArray() 164 | { 165 | $this->mockHandler->append(new Response(200, [], json_encode([ 166 | 'data' => [ 167 | 'someField' => [ 168 | [ 169 | 'data' => 'value', 170 | ], [ 171 | 'data' => 'value', 172 | ] 173 | ] 174 | ] 175 | ]))); 176 | 177 | $arrayResults = $this->client->runRawQuery('', true); 178 | $this->assertIsArray($arrayResults->getResults()); 179 | } 180 | 181 | /** 182 | * @covers \GraphQL\Client::runRawQuery 183 | */ 184 | public function testInvalidQueryResponseWith200() 185 | { 186 | $this->mockHandler->append(new Response(200, [], json_encode([ 187 | 'errors' => [ 188 | [ 189 | 'message' => 'some syntax error', 190 | 'location' => [ 191 | [ 192 | 'line' => 1, 193 | 'column' => 3, 194 | ] 195 | ], 196 | ] 197 | ] 198 | ]))); 199 | 200 | $this->expectException(QueryError::class); 201 | $this->client->runRawQuery(''); 202 | } 203 | 204 | /** 205 | * @covers \GraphQL\Client::runRawQuery 206 | */ 207 | public function testInvalidQueryResponseWith400() 208 | { 209 | $this->mockHandler->append(new ClientException('', new Request('post', ''), 210 | new Response(400, [], json_encode([ 211 | 'errors' => [ 212 | [ 213 | 'message' => 'some syntax error', 214 | 'location' => [ 215 | [ 216 | 'line' => 1, 217 | 'column' => 3, 218 | ] 219 | ], 220 | ] 221 | ] 222 | ])))); 223 | 224 | $this->expectException(QueryError::class); 225 | $this->client->runRawQuery(''); 226 | } 227 | 228 | /** 229 | * @covers \GraphQL\Client::runRawQuery 230 | */ 231 | public function testUnauthorizedResponse() 232 | { 233 | $this->mockHandler->append(new ClientException('', new Request('post', ''), 234 | new Response(401, [], json_encode('Unauthorized')) 235 | )); 236 | 237 | $this->expectException(ClientException::class); 238 | $this->client->runRawQuery(''); 239 | } 240 | 241 | /** 242 | * @covers \GraphQL\Client::runRawQuery 243 | */ 244 | public function testNotFoundResponse() 245 | { 246 | $this->mockHandler->append(new ClientException('', new Request('post', ''), new Response(404, []))); 247 | 248 | $this->expectException(ClientException::class); 249 | $this->client->runRawQuery(''); 250 | } 251 | 252 | /** 253 | * @covers \GraphQL\Client::runRawQuery 254 | */ 255 | public function testInternalServerErrorResponse() 256 | { 257 | $this->mockHandler->append(new ServerException('', new Request('post', ''), new Response(500, []))); 258 | 259 | $this->expectException(ServerException::class); 260 | $this->client->runRawQuery(''); 261 | } 262 | 263 | /** 264 | * @covers \GraphQL\Client::runRawQuery 265 | */ 266 | public function testConnectTimeoutResponse() 267 | { 268 | $this->mockHandler->append(new ConnectException('Time Out', new Request('post', ''))); 269 | $this->expectException(ConnectException::class); 270 | $this->client->runRawQuery(''); 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /tests/InlineFragmentTest.php: -------------------------------------------------------------------------------- 1 | setSelectionSet( 27 | [ 28 | 'field1', 29 | 'field2', 30 | ] 31 | ); 32 | 33 | $this->assertEquals( 34 | '... on Test { 35 | field1 36 | field2 37 | }', 38 | (string) $fragment 39 | ); 40 | } 41 | 42 | /** 43 | * @covers \GraphQL\InlineFragment::__construct 44 | * @covers \GraphQL\InlineFragment::setSelectionSet 45 | * @covers \GraphQL\InlineFragment::constructSelectionSet 46 | * @covers \GraphQL\InlineFragment::__toString 47 | */ 48 | public function testConvertNestedFragmentToString() 49 | { 50 | $fragment = new InlineFragment('Test'); 51 | $fragment->setSelectionSet( 52 | [ 53 | 'field1', 54 | 'field2', 55 | (new Query('sub_field')) 56 | ->setArguments( 57 | [ 58 | 'first' => 5 59 | ] 60 | ) 61 | ->setSelectionSet( 62 | [ 63 | 'sub_field3', 64 | (new InlineFragment('Nested')) 65 | ->setSelectionSet( 66 | [ 67 | 'another_field' 68 | ] 69 | ), 70 | ] 71 | ) 72 | ] 73 | ); 74 | 75 | $this->assertEquals( 76 | '... on Test { 77 | field1 78 | field2 79 | sub_field(first: 5) { 80 | sub_field3 81 | ... on Nested { 82 | another_field 83 | } 84 | } 85 | }', 86 | (string) $fragment 87 | ); 88 | } 89 | 90 | /** 91 | * @covers \GraphQL\InlineFragment::__construct 92 | * @covers \GraphQL\InlineFragment::setSelectionSet 93 | * @covers \GraphQL\InlineFragment::getSelectionSet 94 | * @covers \GraphQL\InlineFragment::constructSelectionSet 95 | * @covers \GraphQL\InlineFragment::__toString 96 | */ 97 | public function testConvertQueryBuilderToString() 98 | { 99 | $queryBuilder = new QueryBuilder(); 100 | 101 | $fragment = new InlineFragment('Test', $queryBuilder); 102 | $queryBuilder->selectField('field1'); 103 | $queryBuilder->selectField('field2'); 104 | 105 | $this->assertEquals( 106 | '... on Test { 107 | field1 108 | field2 109 | }', 110 | (string) $fragment 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/MutationBuilderTest.php: -------------------------------------------------------------------------------- 1 | mutationBuilder = new MutationBuilder('createObject'); 22 | } 23 | 24 | /** 25 | * @covers \GraphQL\QueryBuilder\MutationBuilder::__construct 26 | * @covers \GraphQL\QueryBuilder\MutationBuilder::getQuery 27 | * @covers \GraphQL\QueryBuilder\MutationBuilder::getMutation 28 | */ 29 | public function testConstruct() 30 | { 31 | $builder = new MutationBuilder('createObject'); 32 | $builder->selectField('field_one'); 33 | $this->assertInstanceOf(Mutation::class, $builder->getQuery()); 34 | $this->assertInstanceOf(Mutation::class, $builder->getMutation()); 35 | 36 | $expectedString = 'mutation { 37 | createObject { 38 | field_one 39 | } 40 | }'; 41 | $this->assertEquals($expectedString, (string) $builder->getQuery()); 42 | $this->assertEquals($expectedString, (string) $builder->getMutation()); 43 | } 44 | } -------------------------------------------------------------------------------- /tests/MutationTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 18 | 'mutation { 19 | createObject 20 | }', 21 | (string) $mutation 22 | ); 23 | } 24 | 25 | /** 26 | * 27 | */ 28 | public function testMutationWithOperationType() 29 | { 30 | $mutation = new Mutation(); 31 | $mutation 32 | ->setSelectionSet( 33 | [ 34 | (new Mutation('createObject')) 35 | ->setArguments(['name' => 'TestObject']) 36 | ] 37 | ); 38 | 39 | $this->assertEquals( 40 | 'mutation { 41 | createObject(name: "TestObject") 42 | }', 43 | (string) $mutation 44 | ); 45 | } 46 | 47 | /** 48 | * 49 | */ 50 | public function testMutationWithoutSelectedFields() 51 | { 52 | $mutation = (new Mutation('createObject')) 53 | ->setArguments(['name' => 'TestObject', 'type' => 'TestType']); 54 | $this->assertEquals( 55 | 'mutation { 56 | createObject(name: "TestObject" type: "TestType") 57 | }', 58 | (string) $mutation); 59 | } 60 | 61 | /** 62 | * 63 | */ 64 | public function testMutationWithFields() 65 | { 66 | $mutation = (new Mutation('createObject')) 67 | ->setSelectionSet( 68 | [ 69 | 'fieldOne', 70 | 'fieldTwo', 71 | ] 72 | ); 73 | 74 | $this->assertEquals( 75 | 'mutation { 76 | createObject { 77 | fieldOne 78 | fieldTwo 79 | } 80 | }', 81 | (string) $mutation 82 | ); 83 | } 84 | 85 | /** 86 | * 87 | */ 88 | public function testMutationWithArgumentsAndFields() 89 | { 90 | $mutation = (new Mutation('createObject')) 91 | ->setSelectionSet( 92 | [ 93 | 'fieldOne', 94 | 'fieldTwo', 95 | ] 96 | )->setArguments( 97 | [ 98 | 'argOne' => 1, 99 | 'argTwo' => 'val' 100 | ] 101 | ); 102 | 103 | $this->assertEquals( 104 | 'mutation { 105 | createObject(argOne: 1 argTwo: "val") { 106 | fieldOne 107 | fieldTwo 108 | } 109 | }', 110 | (string) $mutation 111 | ); 112 | } 113 | } -------------------------------------------------------------------------------- /tests/QueryBuilderTest.php: -------------------------------------------------------------------------------- 1 | queryBuilder = new QueryBuilder('Object'); 31 | } 32 | 33 | /** 34 | * @covers \GraphQL\QueryBuilder\QueryBuilder::__construct 35 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::__construct 36 | */ 37 | public function testConstruct() 38 | { 39 | $builder = new QueryBuilder('Object'); 40 | $builder->selectField('field_one'); 41 | $this->assertEquals( 42 | 'query { 43 | Object { 44 | field_one 45 | } 46 | }', 47 | (string) $builder->getQuery() 48 | ); 49 | } 50 | 51 | /** 52 | * @covers \GraphQL\QueryBuilder\QueryBuilder::__construct 53 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::__construct 54 | */ 55 | public function testConstructWithAlias() 56 | { 57 | $builder = new QueryBuilder('Object', 'ObjectAlias'); 58 | $builder->selectField('field_one'); 59 | $this->assertEquals( 60 | 'query { 61 | ObjectAlias: Object { 62 | field_one 63 | } 64 | }', 65 | (string) $builder->getQuery() 66 | ); 67 | } 68 | 69 | /** 70 | * @covers \GraphQL\QueryBuilder\QueryBuilder::__construct 71 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::__construct 72 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::setAlias 73 | */ 74 | public function testSetAlias() 75 | { 76 | $builder = (new QueryBuilder('Object')) 77 | ->setAlias('ObjectAlias');; 78 | $builder->selectField('field_one'); 79 | $this->assertEquals( 80 | 'query { 81 | ObjectAlias: Object { 82 | field_one 83 | } 84 | }', 85 | (string) $builder->getQuery() 86 | ); 87 | } 88 | 89 | /** 90 | * @covers \GraphQL\QueryBuilder\QueryBuilder::setVariable 91 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::setVariable 92 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::getQuery 93 | */ 94 | public function testAddVariables() 95 | { 96 | $this->queryBuilder 97 | ->setVariable('var', 'String') 98 | ->setVariable('intVar', 'Int', false, 4) 99 | ->selectField('fieldOne'); 100 | $this->assertEquals( 101 | 'query($var: String $intVar: Int=4) { 102 | Object { 103 | fieldOne 104 | } 105 | }', 106 | (string) $this->queryBuilder->getQuery() 107 | ); 108 | } 109 | 110 | /** 111 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::getQuery 112 | */ 113 | public function testAddVariablesToSecondLevelQueryDoesNothing() 114 | { 115 | $this->queryBuilder 116 | ->setVariable('var', 'String') 117 | ->selectField('fieldOne') 118 | ->selectField( 119 | (new QueryBuilder('Nested')) 120 | ->setVariable('var', 'String') 121 | ->selectField('fieldTwo') 122 | ); 123 | $this->assertEquals( 124 | 'query($var: String) { 125 | Object { 126 | fieldOne 127 | Nested { 128 | fieldTwo 129 | } 130 | } 131 | }', 132 | (string) $this->queryBuilder->getQuery() 133 | ); 134 | } 135 | 136 | /** 137 | * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery 138 | * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField 139 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::selectField 140 | */ 141 | public function testSelectScalarFields() 142 | { 143 | $this->queryBuilder->selectField('field_one'); 144 | $this->queryBuilder->selectField('field_two'); 145 | $this->assertEquals( 146 | 'query { 147 | Object { 148 | field_one 149 | field_two 150 | } 151 | }', 152 | (string) $this->queryBuilder->getQuery() 153 | ); 154 | } 155 | 156 | /** 157 | * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery 158 | * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField 159 | */ 160 | public function testSelectNestedQuery() 161 | { 162 | $this->queryBuilder->selectField( 163 | (new Query('Nested')) 164 | ->setSelectionSet(['some_field']) 165 | ); 166 | $this->assertEquals( 167 | 'query { 168 | Object { 169 | Nested { 170 | some_field 171 | } 172 | } 173 | }', 174 | (string) $this->queryBuilder->getQuery() 175 | ); 176 | } 177 | 178 | /** 179 | * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery 180 | * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField 181 | */ 182 | public function testSelectNestedQueryBuilder() 183 | { 184 | $this->queryBuilder->selectField( 185 | (new QueryBuilder('Nested')) 186 | ->selectField('some_field') 187 | ); 188 | $this->assertEquals( 189 | 'query { 190 | Object { 191 | Nested { 192 | some_field 193 | } 194 | } 195 | }', 196 | (string) $this->queryBuilder->getQuery() 197 | ); 198 | } 199 | 200 | /** 201 | * @covers \GraphQL\QueryBuilder\QueryBuilder::__construct 202 | * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery 203 | * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField 204 | */ 205 | public function testQueryBuilderWithoutFieldName() 206 | { 207 | $builder = (new QueryBuilder()) 208 | ->selectField( 209 | (new QueryBuilder('Object')) 210 | ->selectField('one') 211 | ) 212 | ->selectField( 213 | (new QueryBuilder('Another')) 214 | ->selectField('two') 215 | ); 216 | 217 | $this->assertEquals('query { 218 | Object { 219 | one 220 | } 221 | Another { 222 | two 223 | } 224 | }', 225 | (string) $builder->getQuery()); 226 | } 227 | 228 | /** 229 | * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery 230 | * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField 231 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::selectField 232 | */ 233 | public function testSelectInlineFragment() 234 | { 235 | $this->queryBuilder->selectField( 236 | (new InlineFragment('Type')) 237 | ->setSelectionSet(['field']) 238 | ); 239 | $this->assertEquals( 240 | 'query { 241 | Object { 242 | ... on Type { 243 | field 244 | } 245 | } 246 | }', 247 | (string) $this->queryBuilder->getQuery() 248 | ); 249 | } 250 | 251 | /** 252 | * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery 253 | * @covers \GraphQL\QueryBuilder\QueryBuilder::setArgument 254 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::setArgument 255 | */ 256 | public function testSelectArguments() 257 | { 258 | $this->queryBuilder->selectField('field'); 259 | $this->queryBuilder->setArgument('str_arg', 'value'); 260 | $this->assertEquals( 261 | 'query { 262 | Object(str_arg: "value") { 263 | field 264 | } 265 | }', 266 | (string) $this->queryBuilder->getQuery() 267 | ); 268 | 269 | $this->queryBuilder->setArgument('bool_arg', true); 270 | $this->assertEquals( 271 | 'query { 272 | Object(str_arg: "value" bool_arg: true) { 273 | field 274 | } 275 | }', 276 | (string) $this->queryBuilder->getQuery() 277 | ); 278 | 279 | $this->queryBuilder->setArgument('int_arg', 10); 280 | $this->assertEquals( 281 | 'query { 282 | Object(str_arg: "value" bool_arg: true int_arg: 10) { 283 | field 284 | } 285 | }', 286 | (string) $this->queryBuilder->getQuery() 287 | ); 288 | 289 | $this->queryBuilder->setArgument('array_arg', ['one', 'two', 'three']); 290 | $this->assertEquals( 291 | 'query { 292 | Object(str_arg: "value" bool_arg: true int_arg: 10 array_arg: ["one", "two", "three"]) { 293 | field 294 | } 295 | }', 296 | (string) $this->queryBuilder->getQuery() 297 | ); 298 | 299 | $this->queryBuilder->setArgument('input_object_arg', new RawObject('{field_not: "x"}')); 300 | $this->assertEquals( 301 | 'query { 302 | Object(str_arg: "value" bool_arg: true int_arg: 10 array_arg: ["one", "two", "three"] input_object_arg: {field_not: "x"}) { 303 | field 304 | } 305 | }', 306 | (string) $this->queryBuilder->getQuery() 307 | ); 308 | } 309 | 310 | /** 311 | * @covers \GraphQL\QueryBuilder\QueryBuilder::getQuery 312 | * @covers \GraphQL\QueryBuilder\QueryBuilder::setArgument 313 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::setArgument 314 | * @covers \GraphQL\QueryBuilder\QueryBuilder::selectField 315 | * @covers \GraphQL\QueryBuilder\AbstractQueryBuilder::selectField 316 | */ 317 | public function testSetTwoLevelArguments() 318 | { 319 | $this->queryBuilder->selectField( 320 | (new QueryBuilder('Nested')) 321 | ->selectField('some_field') 322 | ->selectField('another_field') 323 | ->setArgument('nested_arg', [1, 2, 3]) 324 | ) 325 | ->setArgument('outer_arg', 'outer val'); 326 | $this->assertEquals( 327 | 'query { 328 | Object(outer_arg: "outer val") { 329 | Nested(nested_arg: [1, 2, 3]) { 330 | some_field 331 | another_field 332 | } 333 | } 334 | }', 335 | (string) $this->queryBuilder->getQuery() 336 | ); 337 | } 338 | } -------------------------------------------------------------------------------- /tests/QueryErrorTest.php: -------------------------------------------------------------------------------- 1 | [ 24 | [ 25 | 'message' => $exceptionMessage, 26 | 'location' => [ 27 | [ 28 | 'line' => 1, 29 | 'column' => 3, 30 | ] 31 | ], 32 | ] 33 | ] 34 | ]; 35 | 36 | $queryError = new QueryError($errorData); 37 | $this->assertEquals($exceptionMessage, $queryError->getMessage()); 38 | $this->assertEquals( 39 | [ 40 | 'message' => 'some syntax error', 41 | 'location' => [ 42 | [ 43 | 'line' => 1, 44 | 'column' => 3, 45 | ] 46 | ] 47 | ], 48 | $queryError->getErrorDetails() 49 | ); 50 | } 51 | } -------------------------------------------------------------------------------- /tests/QueryTest.php: -------------------------------------------------------------------------------- 1 | assertIsString((string) $query, 'Failed to convert query to string'); 29 | 30 | return $query; 31 | } 32 | 33 | /** 34 | * @depends testConvertsToString 35 | * 36 | * @covers \GraphQL\Query::constructArguments 37 | * 38 | * @param Query $query 39 | * 40 | * @return Query 41 | */ 42 | public function testEmptyArguments(Query $query) 43 | { 44 | $this->assertStringNotContainsString("()", (string) $query, 'Query has empty arguments list'); 45 | 46 | return $query; 47 | } 48 | 49 | /** 50 | * @covers \GraphQL\Query::__toString 51 | * @covers FieldTrait::constructSelectionSet 52 | */ 53 | public function testQueryWithoutFieldName() 54 | { 55 | $query = new Query(); 56 | 57 | $this->assertEquals( 58 | "query", 59 | (string) $query 60 | ); 61 | 62 | $query->setSelectionSet( 63 | [ 64 | (new Query('Object')) 65 | ->setSelectionSet(['one']), 66 | (new Query('Another')) 67 | ->setSelectionSet(['two']) 68 | ] 69 | ); 70 | 71 | $this->assertEquals( 72 | "query { 73 | Object { 74 | one 75 | } 76 | Another { 77 | two 78 | } 79 | }", 80 | (string) $query 81 | ); 82 | } 83 | 84 | /** 85 | * @depends testConvertsToString 86 | * 87 | * @covers \GraphQL\Query::generateSignature 88 | * @covers \GraphQL\Query::__toString 89 | */ 90 | public function testQueryWithAlias() 91 | { 92 | $query = (new Query('Object', 'ObjectAlias')) 93 | ->setSelectionSet([ 94 | 'one' 95 | ]); 96 | 97 | $this->assertEquals( 98 | "query { 99 | ObjectAlias: Object { 100 | one 101 | } 102 | }", 103 | (string) $query 104 | ); 105 | } 106 | 107 | /** 108 | * @depends testConvertsToString 109 | * 110 | * @covers \GraphQL\Query::setAlias 111 | * @covers \GraphQL\Query::generateSignature 112 | * @covers \GraphQL\Query::__toString 113 | */ 114 | public function testQueryWithSetAlias() 115 | { 116 | $query = (new Query('Object')) 117 | ->setAlias('ObjectAlias') 118 | ->setSelectionSet([ 119 | 'one' 120 | ]); 121 | 122 | $this->assertEquals( 123 | "query { 124 | ObjectAlias: Object { 125 | one 126 | } 127 | }", 128 | (string) $query 129 | ); 130 | } 131 | 132 | /** 133 | * @depends testConvertsToString 134 | * 135 | * @covers \GraphQL\Query::generateSignature 136 | * @covers \GraphQL\Query::setOperationName 137 | * @covers \GraphQL\Query::__toString 138 | */ 139 | public function testQueryWithOperationName() 140 | { 141 | $query = (new Query('Object')) 142 | ->setOperationName('retrieveObject'); 143 | $this->assertEquals( 144 | 'query retrieveObject { 145 | Object 146 | }', 147 | (string) $query 148 | ); 149 | } 150 | 151 | /** 152 | * @depends testQueryWithoutFieldName 153 | * @depends testQueryWithOperationName 154 | * 155 | * @covers \GraphQL\Query::generateSignature 156 | * @covers \GraphQL\Query::setOperationName 157 | * @covers \GraphQL\Query::__toString 158 | */ 159 | public function testQueryWithOperationNameAndOperationType() 160 | { 161 | $query = (new Query()) 162 | ->setOperationName('retrieveObject') 163 | ->setSelectionSet([new Query('Object')]); 164 | $this->assertEquals( 165 | 'query retrieveObject { 166 | Object 167 | }', 168 | (string) $query 169 | ); 170 | } 171 | 172 | /** 173 | * @depends testQueryWithOperationName 174 | * 175 | * @covers \GraphQL\Query::generateSignature 176 | * @covers \GraphQL\Query::setOperationName 177 | * @covers \GraphQL\Query::__toString 178 | */ 179 | public function testQueryWithOperationNameInSecondLevelDoesNothing() 180 | { 181 | $query = (new Query('Object')) 182 | ->setOperationName('retrieveObject') 183 | ->setSelectionSet([(new Query('Nested'))->setOperationName('opName')]); 184 | $this->assertEquals( 185 | 'query retrieveObject { 186 | Object { 187 | Nested 188 | } 189 | }', 190 | (string) $query 191 | ); 192 | } 193 | 194 | /** 195 | * @covers \GraphQL\Query::setVariables 196 | * @covers \GraphQL\Exception\InvalidVariableException 197 | */ 198 | public function testSetVariablesWithoutVariableObjects() 199 | { 200 | $this->expectException(InvalidVariableException::class); 201 | (new Query('Object'))->setVariables(['one', 'two']); 202 | } 203 | 204 | /** 205 | * @depends testConvertsToString 206 | * 207 | * @covers \GraphQL\Query::setVariables 208 | * @covers \GraphQL\Query::generateSignature 209 | * @covers \GraphQL\Query::constructVariables 210 | * @covers \GraphQL\Query::__toString 211 | */ 212 | public function testQueryWithOneVariable() 213 | { 214 | $query = (new Query('Object')) 215 | ->setVariables([new Variable('var', 'String')]); 216 | $this->assertEquals( 217 | 'query($var: String) { 218 | Object 219 | }', 220 | (string) $query 221 | ); 222 | } 223 | 224 | /** 225 | * @depends testQueryWithOneVariable 226 | * 227 | * @covers \GraphQL\Query::setVariables 228 | * @covers \GraphQL\Query::generateSignature 229 | * @covers \GraphQL\Query::constructVariables 230 | * @covers \GraphQL\Query::__toString 231 | */ 232 | public function testQueryWithMultipleVariables() 233 | { 234 | $query = (new Query('Object')) 235 | ->setVariables([new Variable('var', 'String'), new Variable('intVar', 'Int', false, 4)]); 236 | $this->assertEquals( 237 | 'query($var: String $intVar: Int=4) { 238 | Object 239 | }', 240 | (string) $query 241 | ); 242 | } 243 | 244 | /** 245 | * @depends testConvertsToString 246 | * 247 | * @covers \GraphQL\Query::__toString 248 | */ 249 | public function testQueryWithVariablesInSecondLevelDoesNothing() 250 | { 251 | $query = (new Query('Object')) 252 | ->setVariables([new Variable('var', 'String'), new Variable('intVar', 'Int', false, 4)]) 253 | ->setSelectionSet([(new Query('Nested'))]) 254 | ->setVariables([new Variable('var', 'String'), new Variable('intVar', 'Int', false, 4)]); 255 | $this->assertEquals( 256 | 'query($var: String $intVar: Int=4) { 257 | Object { 258 | Nested 259 | } 260 | }', 261 | (string) $query 262 | ); 263 | } 264 | 265 | /** 266 | * @depends testQueryWithMultipleVariables 267 | * @depends testQueryWithOperationName 268 | * 269 | * @covers \GraphQL\Query::generateSignature 270 | * @covers \GraphQL\Query::__toString 271 | */ 272 | public function testQueryWithOperationNameAndVariables() 273 | { 274 | $query = (new Query('Object')) 275 | ->setOperationName('retrieveObject') 276 | ->setVariables([new Variable('var', 'String')]); 277 | $this->assertEquals( 278 | 'query retrieveObject($var: String) { 279 | Object 280 | }', 281 | (string) $query 282 | ); 283 | } 284 | 285 | /** 286 | * @depends clone testEmptyArguments 287 | * 288 | * @covers \GraphQL\Query::__toString 289 | * 290 | * @param Query $query 291 | * 292 | * @return Query 293 | */ 294 | public function testEmptyQuery(Query $query) 295 | { 296 | $this->assertEquals( 297 | "query { 298 | Object 299 | }", 300 | (string) $query, 301 | 'Incorrect empty query string' 302 | ); 303 | 304 | return $query; 305 | } 306 | 307 | /** 308 | * @depends clone testEmptyArguments 309 | * 310 | * @covers \GraphQL\Exception\ArgumentException 311 | * @covers \GraphQL\Query::setArguments 312 | * 313 | * @param Query $query 314 | * 315 | * @return Query 316 | */ 317 | public function testArgumentWithoutName(Query $query) 318 | { 319 | $this->expectException(ArgumentException::class); 320 | $query->setArguments(['val']); 321 | 322 | return $query; 323 | } 324 | 325 | /** 326 | * @depends clone testEmptyArguments 327 | * 328 | * @covers \GraphQL\Query::setArguments 329 | * @covers \GraphQL\Query::constructArguments 330 | * 331 | * @param Query $query 332 | * 333 | * @return Query 334 | */ 335 | public function testStringArgumentValue(Query $query) 336 | { 337 | $query->setArguments(['arg1' => 'value']); 338 | $this->assertEquals( 339 | "query { 340 | Object(arg1: \"value\") 341 | }", 342 | (string) $query, 343 | 'Query has improperly formatted parameter list' 344 | ); 345 | 346 | return $query; 347 | } 348 | 349 | /** 350 | * @depends clone testEmptyArguments 351 | * 352 | * @covers \GraphQL\Query::setArguments 353 | * @covers \GraphQL\Query::constructArguments 354 | * 355 | * @param Query $query 356 | * 357 | * @return Query 358 | */ 359 | public function testIntegerArgumentValue(Query $query) 360 | { 361 | $query->setArguments(['arg1' => 23]); 362 | $this->assertEquals( 363 | "query { 364 | Object(arg1: 23) 365 | }", 366 | (string) $query 367 | ); 368 | 369 | return $query; 370 | } 371 | 372 | /** 373 | * @depends clone testEmptyArguments 374 | 375 | * @covers \GraphQL\Query::setArguments 376 | * @covers \GraphQL\Query::constructArguments 377 | * 378 | * @param Query $query 379 | * 380 | * @return Query 381 | */ 382 | public function testBooleanArgumentValue(Query $query) 383 | { 384 | $query->setArguments(['arg1' => true]); 385 | $this->assertEquals( 386 | "query { 387 | Object(arg1: true) 388 | }", 389 | (string) $query 390 | ); 391 | 392 | return $query; 393 | } 394 | 395 | /** 396 | * @depends clone testEmptyArguments 397 | * 398 | * @covers \GraphQL\Query::setArguments 399 | * @covers \GraphQL\Query::constructArguments 400 | * 401 | * @param Query $query 402 | * 403 | * @return Query 404 | */ 405 | public function testNullArgumentValue(Query $query) 406 | { 407 | $query->setArguments(['arg1' => null]); 408 | $this->assertEquals( 409 | "query { 410 | Object(arg1: null) 411 | }" 412 | , (string) $query 413 | ); 414 | 415 | return $query; 416 | } 417 | 418 | /** 419 | * @depends clone testEmptyArguments 420 | * 421 | * @covers \GraphQL\Query::setArguments 422 | * @covers \GraphQL\Query::constructArguments 423 | * 424 | * @param Query $query 425 | * 426 | * @return Query 427 | */ 428 | public function testArrayIntegerArgumentValue(Query $query) 429 | { 430 | $query->setArguments(['arg1' => [1, 2, 3]]); 431 | $this->assertEquals( 432 | "query { 433 | Object(arg1: [1, 2, 3]) 434 | }", 435 | (string) $query 436 | ); 437 | 438 | return $query; 439 | } 440 | 441 | /** 442 | * @depends clone testEmptyArguments 443 | * 444 | * @covers \GraphQL\Query::setArguments 445 | * @covers \GraphQL\Query::constructArguments 446 | * @covers \GraphQL\RawObject::__toString 447 | * 448 | * @param Query $query 449 | * 450 | * @return Query 451 | */ 452 | public function testJsonObjectArgumentValue(Query $query) 453 | { 454 | $query->setArguments(['obj' => new RawObject('{json_string_array: ["json value"]}')]); 455 | $this->assertEquals( 456 | "query { 457 | Object(obj: {json_string_array: [\"json value\"]}) 458 | }" 459 | , (string) $query 460 | ); 461 | 462 | return $query; 463 | } 464 | 465 | /** 466 | * @depends clone testEmptyArguments 467 | * 468 | * @covers \GraphQL\Query::setArguments 469 | * @covers \GraphQL\Query::constructArguments 470 | * 471 | * @param Query $query 472 | * 473 | * @return Query 474 | */ 475 | public function testArrayStringArgumentValue(Query $query) 476 | { 477 | $query->setArguments(['arg1' => ['one', 'two', 'three']]); 478 | $this->assertEquals( 479 | "query { 480 | Object(arg1: [\"one\", \"two\", \"three\"]) 481 | }", 482 | (string) $query 483 | ); 484 | 485 | return $query; 486 | } 487 | 488 | /** 489 | * @depends clone testStringArgumentValue 490 | * @depends testIntegerArgumentValue 491 | * @depends testBooleanArgumentValue 492 | * 493 | * @covers \GraphQL\Query::setArguments 494 | * @covers \GraphQL\Query::constructArguments 495 | * 496 | * @param Query $query 497 | * 498 | * @return Query 499 | */ 500 | public function testTwoOrMoreArguments(Query $query) 501 | { 502 | $query->setArguments(['arg1' => 'val1', 'arg2' => 2, 'arg3' => true]); 503 | $this->assertEquals( 504 | "query { 505 | Object(arg1: \"val1\" arg2: 2 arg3: true) 506 | }", 507 | (string) $query, 508 | 'Query has improperly formatted parameter list' 509 | ); 510 | 511 | return $query; 512 | } 513 | 514 | /** 515 | * @depends testStringArgumentValue 516 | * 517 | * @covers \GraphQL\Query::setArguments 518 | * @covers \GraphQL\Query::constructArguments 519 | * @covers \GraphQL\Query::setArguments 520 | * @covers \GraphQL\Query::constructArguments 521 | */ 522 | public function testStringWrappingWorks() 523 | { 524 | // TODO: Remove this in v1.0 release 525 | $queryWrapped = new Query('Object'); 526 | $queryWrapped->setArguments(['arg1' => '"val"']); 527 | 528 | $queryNotWrapped = new Query('Object'); 529 | $queryNotWrapped->setArguments(['arg1' => 'val']); 530 | 531 | $this->assertEquals((string) $queryWrapped, (string) $queryWrapped); 532 | } 533 | 534 | /** 535 | * @depends clone testEmptyQuery 536 | * 537 | * @covers \GraphQL\Query::setSelectionSet 538 | * @covers \GraphQL\FieldTrait::constructSelectionSet 539 | * 540 | * @param Query $query 541 | * 542 | * @return Query 543 | */ 544 | public function testSingleSelectionField(Query $query) 545 | { 546 | $query->setSelectionSet(['field1']); 547 | $this->assertEquals( 548 | "query { 549 | Object { 550 | field1 551 | } 552 | }", 553 | (string) $query, 554 | 'Query has improperly formatted selection set' 555 | ); 556 | 557 | return $query; 558 | } 559 | 560 | /** 561 | * @depends clone testEmptyQuery 562 | * 563 | * @covers \GraphQL\Query::setSelectionSet 564 | * @covers \GraphQL\FieldTrait::constructSelectionSet 565 | * 566 | * @param Query $query 567 | * 568 | * @return Query 569 | */ 570 | public function testTwoOrMoreSelectionFields(Query $query) 571 | { 572 | $query->setSelectionSet(['field1', 'field2']); 573 | $this->assertEquals( 574 | "query { 575 | Object { 576 | field1 577 | field2 578 | } 579 | }", 580 | (string) $query, 581 | 'Query has improperly formatted selection set' 582 | ); 583 | 584 | return $query; 585 | } 586 | 587 | /** 588 | * @depends clone testEmptyQuery 589 | * 590 | * @covers \GraphQL\Exception\InvalidSelectionException 591 | * @covers \GraphQL\Query::setSelectionSet 592 | * 593 | * @param Query $query 594 | * 595 | * @return Query 596 | */ 597 | public function testSelectNonStringValues(Query $query) 598 | { 599 | $this->expectException(InvalidSelectionException::class); 600 | $query->setSelectionSet([true, 1.5]); 601 | 602 | return $query; 603 | } 604 | 605 | /** 606 | * @depends clone testEmptyQuery 607 | * 608 | * @coversNothing 609 | * 610 | * @param Query $query 611 | * 612 | * @return Query 613 | */ 614 | public function testOneLevelQuery(Query $query) 615 | { 616 | $query->setSelectionSet(['field1', 'field2']); 617 | $query->setArguments(['arg1' => 'val1', 'arg2' => 'val2']); 618 | $this->assertEquals( 619 | "query { 620 | Object(arg1: \"val1\" arg2: \"val2\") { 621 | field1 622 | field2 623 | } 624 | }", 625 | (string) $query, 626 | 'One level query not formatted correctly' 627 | ); 628 | 629 | return $query; 630 | } 631 | 632 | /** 633 | * @depends clone testOneLevelQuery 634 | * 635 | * @covers \GraphQL\FieldTrait::constructSelectionSet 636 | * @covers \GraphQL\Query::setAsNested 637 | * 638 | * @param Query $query 639 | * 640 | * @return Query 641 | */ 642 | public function testTwoLevelQueryDoesNotContainWordQuery(Query $query) 643 | { 644 | $query->setSelectionSet( 645 | [ 646 | 'field1', 647 | 'field2', 648 | (new Query('Object2')) 649 | ->setSelectionSet(['field3']) 650 | ] 651 | ); 652 | $this->assertStringNotContainsString( 653 | "\nquery {", 654 | (string) $query, 655 | 'Nested query contains "query" word' 656 | ); 657 | 658 | return $query; 659 | } 660 | 661 | /** 662 | * @depends clone testTwoLevelQueryDoesNotContainWordQuery 663 | * 664 | * @covers \GraphQL\Query::setAsNested 665 | * 666 | * @param Query $query 667 | * 668 | * @return Query 669 | */ 670 | public function testTwoLevelQuery(Query $query) 671 | { 672 | $query->setSelectionSet( 673 | [ 674 | 'field1', 675 | 'field2', 676 | (new Query('Object2')) 677 | ->setSelectionSet(['field3']) 678 | ] 679 | ); 680 | $this->assertEquals( 681 | "query { 682 | Object(arg1: \"val1\" arg2: \"val2\") { 683 | field1 684 | field2 685 | Object2 { 686 | field3 687 | } 688 | } 689 | }", 690 | (string) $query, 691 | 'Two level query not formatted correctly' 692 | ); 693 | 694 | return $query; 695 | } 696 | 697 | /** 698 | * @depends clone testTwoLevelQueryDoesNotContainWordQuery 699 | * 700 | * @param Query $query 701 | * 702 | * @return Query 703 | */ 704 | public function testTwoLevelQueryWithInlineFragment(Query $query) 705 | { 706 | $query->setSelectionSet( 707 | [ 708 | 'field1', 709 | (new InlineFragment('Object')) 710 | ->setSelectionSet( 711 | [ 712 | 'fragment_field1', 713 | 'fragment_field2', 714 | ] 715 | ), 716 | ] 717 | ); 718 | $this->assertEquals( 719 | 'query { 720 | Object(arg1: "val1" arg2: "val2") { 721 | field1 722 | ... on Object { 723 | fragment_field1 724 | fragment_field2 725 | } 726 | } 727 | }', 728 | (string) $query 729 | ); 730 | 731 | return $query; 732 | } 733 | } 734 | -------------------------------------------------------------------------------- /tests/RawObjectTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('[1, 4, "y", 6.7]', (string) $json); 19 | 20 | // Test convert graphql object 21 | $json = new RawObject('{arr: [1, "z"], str: "val", int: 1, obj: {x: "y"}}'); 22 | $this->assertEquals('{arr: [1, "z"], str: "val", int: 1, obj: {x: "y"}}', (string) $json); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/ResultsTest.php: -------------------------------------------------------------------------------- 1 | mockHandler = new MockHandler(); 36 | $this->client = new Client(['handler' => $this->mockHandler]); 37 | } 38 | 39 | /** 40 | * @covers \GraphQL\Results::__construct 41 | * @covers \GraphQL\Results::getResponseObject 42 | * @covers \GraphQL\Results::getResponseBody 43 | * @covers \GraphQL\Results::getResults 44 | * @covers \GraphQL\Results::getData 45 | */ 46 | public function testGetSuccessResponseAsObject() 47 | { 48 | $body = json_encode([ 49 | 'data' => [ 50 | 'someField' => [ 51 | [ 52 | 'data' => 'value', 53 | ], 54 | [ 55 | 'data' => 'value', 56 | ] 57 | ] 58 | ] 59 | ]); 60 | $response = new Response(200, [], $body); 61 | $this->mockHandler->append($response); 62 | 63 | $response = $this->client->post('', []); 64 | $results = new Results($response); 65 | 66 | $this->assertEquals($response, $results->getResponseObject()); 67 | $this->assertEquals($body, $results->getResponseBody()); 68 | 69 | $object = new stdClass(); 70 | $object->data = new stdClass(); 71 | $object->data->someField = []; 72 | $object->data->someField[] = new stdClass(); 73 | $object->data->someField[] = new stdClass(); 74 | $object->data->someField[0]->data = 'value'; 75 | $object->data->someField[1]->data = 'value'; 76 | $this->assertEquals( 77 | $object, 78 | $results->getResults() 79 | ); 80 | $this->assertEquals( 81 | $object->data, 82 | $results->getData() 83 | ); 84 | } 85 | 86 | /** 87 | * @covers \GraphQL\Results::__construct 88 | * @covers \GraphQL\Results::getResponseObject 89 | * @covers \GraphQL\Results::getResponseBody 90 | * @covers \GraphQL\Results::getResults 91 | * @covers \GraphQL\Results::getData 92 | */ 93 | public function testGetSuccessResponseAsArray() 94 | { 95 | $body = json_encode([ 96 | 'data' => [ 97 | 'someField' => [ 98 | [ 99 | 'data' => 'value', 100 | ], 101 | [ 102 | 'data' => 'value', 103 | ] 104 | ] 105 | ] 106 | ]); 107 | $originalResponse = new Response(200, [], $body); 108 | $this->mockHandler->append($originalResponse); 109 | 110 | $response = $this->client->post('', []); 111 | $results = new Results($response, true); 112 | 113 | $this->assertEquals($originalResponse, $results->getResponseObject()); 114 | $this->assertEquals($body, $results->getResponseBody()); 115 | $this->assertEquals( 116 | [ 117 | 'data' => [ 118 | 'someField' => [ 119 | [ 120 | 'data' => 'value', 121 | ], 122 | [ 123 | 'data' => 'value', 124 | ] 125 | ] 126 | ] 127 | ], 128 | $results->getResults() 129 | ); 130 | $this->assertEquals( 131 | [ 132 | 'someField' => [ 133 | [ 134 | 'data' => 'value', 135 | ], 136 | [ 137 | 'data' => 'value', 138 | ] 139 | ] 140 | ], 141 | $results->getData() 142 | ); 143 | } 144 | 145 | /** 146 | * @covers \GraphQL\Results::__construct 147 | */ 148 | public function testGetQueryInvalidSyntaxError() 149 | { 150 | $body = json_encode([ 151 | 'errors' => [ 152 | [ 153 | 'message' => 'some syntax error', 154 | 'location' => [ 155 | [ 156 | 'line' => 1, 157 | 'column' => 3, 158 | ] 159 | ], 160 | ] 161 | ] 162 | ]); 163 | $originalResponse = new Response(200, [], $body); 164 | $this->mockHandler->append($originalResponse); 165 | 166 | $response = $this->client->post('', []); 167 | $this->expectException(QueryError::class); 168 | new Results($response); 169 | } 170 | 171 | /** 172 | * @covers \GraphQL\Results::__construct 173 | * @covers \GraphQL\Results::reformatResults 174 | * @covers \GraphQL\Results::getResponseObject 175 | * @covers \GraphQL\Results::getResponseBody 176 | * @covers \GraphQL\Results::getResults 177 | * @covers \GraphQL\Results::getData 178 | */ 179 | public function testReformatResultsFromObjectToArray() 180 | { 181 | $body = json_encode([ 182 | 'data' => [ 183 | 'someField' => [ 184 | [ 185 | 'data' => 'value', 186 | ], 187 | [ 188 | 'data' => 'value', 189 | ] 190 | ] 191 | ] 192 | ]); 193 | $originalResponse = new Response(200, [], $body); 194 | $this->mockHandler->append($originalResponse); 195 | 196 | $response = $this->client->post('', []); 197 | $results = new Results($response); 198 | $results->reformatResults(true); 199 | 200 | $this->assertEquals( 201 | [ 202 | 'data' => [ 203 | 'someField' => [ 204 | [ 205 | 'data' => 'value', 206 | ], 207 | [ 208 | 'data' => 'value', 209 | ] 210 | ] 211 | ] 212 | ], 213 | $results->getResults() 214 | ); 215 | $this->assertEquals( 216 | [ 217 | 'someField' => [ 218 | [ 219 | 'data' => 'value', 220 | ], 221 | [ 222 | 'data' => 'value', 223 | ] 224 | ] 225 | ], 226 | $results->getData() 227 | ); 228 | } 229 | 230 | /** 231 | * @covers \GraphQL\Results::__construct 232 | * @covers \GraphQL\Results::reformatResults 233 | * @covers \GraphQL\Results::getResponseObject 234 | * @covers \GraphQL\Results::getResponseBody 235 | * @covers \GraphQL\Results::getResults 236 | * @covers \GraphQL\Results::getData 237 | */ 238 | public function testReformatResultsFromArrayToObject() 239 | { 240 | $body = json_encode([ 241 | 'data' => [ 242 | 'someField' => [ 243 | [ 244 | 'data' => 'value', 245 | ], 246 | [ 247 | 'data' => 'value', 248 | ] 249 | ] 250 | ] 251 | ]); 252 | $originalResponse = new Response(200, [], $body); 253 | $this->mockHandler->append($originalResponse); 254 | 255 | $response = $this->client->post('', []); 256 | $results = new Results($response, true); 257 | $results->reformatResults(false); 258 | 259 | $object = new stdClass(); 260 | $object->data = new stdClass(); 261 | $object->data->someField = []; 262 | $object->data->someField[] = new stdClass(); 263 | $object->data->someField[] = new stdClass(); 264 | $object->data->someField[0]->data = 'value'; 265 | $object->data->someField[1]->data = 'value'; 266 | $this->assertEquals( 267 | $object, 268 | $results->getResults() 269 | ); 270 | $this->assertEquals( 271 | $object->data, 272 | $results->getData() 273 | ); 274 | } 275 | } -------------------------------------------------------------------------------- /tests/StringLiteralFormatterTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('null', $nullString); 23 | 24 | // String tests 25 | $emptyString = StringLiteralFormatter::formatValueForRHS(''); 26 | $this->assertEquals('""', $emptyString); 27 | 28 | $formattedString = StringLiteralFormatter::formatValueForRHS('someString'); 29 | $this->assertEquals('"someString"', $formattedString); 30 | 31 | $formattedString = StringLiteralFormatter::formatValueForRHS('"quotedString"'); 32 | $this->assertEquals('"\"quotedString\""', $formattedString); 33 | 34 | $formattedString = StringLiteralFormatter::formatValueForRHS("\"quotedString\""); 35 | $this->assertEquals('"\"quotedString\""', $formattedString); 36 | 37 | $formattedString = StringLiteralFormatter::formatValueForRHS('\'singleQuotes\''); 38 | $this->assertEquals('"\'singleQuotes\'"', $formattedString); 39 | 40 | $formattedString = StringLiteralFormatter::formatValueForRHS("with \n newlines"); 41 | $this->assertEquals("\"\"\"with \n newlines\"\"\"", $formattedString); 42 | 43 | $formattedString = StringLiteralFormatter::formatValueForRHS('$var'); 44 | $this->assertEquals('$var', $formattedString); 45 | 46 | $formattedString = StringLiteralFormatter::formatValueForRHS('$400'); 47 | $this->assertEquals('"$400"', $formattedString); 48 | 49 | // Integer tests 50 | $integerString = StringLiteralFormatter::formatValueForRHS(25); 51 | $this->assertEquals('25', $integerString); 52 | 53 | // Float tests 54 | $floatString = StringLiteralFormatter::formatValueForRHS(123.123); 55 | $this->assertEquals('123.123', $floatString); 56 | 57 | // Bool tests 58 | $stringTrue = StringLiteralFormatter::formatValueForRHS(true); 59 | $this->assertEquals('true', $stringTrue); 60 | 61 | $stringFalse = StringLiteralFormatter::formatValueForRHS(false); 62 | $this->assertEquals('false', $stringFalse); 63 | } 64 | 65 | /** 66 | * @covers \GraphQL\Util\StringLiteralFormatter::formatArrayForGQLQuery 67 | */ 68 | public function testFormatArrayForGQLQuery() 69 | { 70 | $emptyArray = []; 71 | $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($emptyArray); 72 | $this->assertEquals('[]', $stringArray); 73 | 74 | $oneValueArray = [1]; 75 | $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($oneValueArray); 76 | $this->assertEquals('[1]', $stringArray); 77 | 78 | $twoValueArray = [1, 2]; 79 | $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($twoValueArray); 80 | $this->assertEquals('[1, 2]', $stringArray); 81 | 82 | $stringArray = ['one', 'two']; 83 | $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($stringArray); 84 | $this->assertEquals('["one", "two"]', $stringArray); 85 | 86 | $booleanArray = [true, false]; 87 | $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($booleanArray); 88 | $this->assertEquals('[true, false]', $stringArray); 89 | 90 | $floatArray = [1.1, 2.2]; 91 | $stringArray = StringLiteralFormatter::formatArrayForGQLQuery($floatArray); 92 | $this->assertEquals('[1.1, 2.2]', $stringArray); 93 | } 94 | 95 | /** 96 | * @covers \GraphQL\Util\StringLiteralFormatter::formatUpperCamelCase 97 | */ 98 | public function testFormatUpperCamelCase() 99 | { 100 | $snakeCase = 'some_snake_case'; 101 | $camelCase = StringLiteralFormatter::formatUpperCamelCase($snakeCase); 102 | $this->assertEquals('SomeSnakeCase', $camelCase); 103 | 104 | $nonSnakeCase = 'somenonSnakeCase'; 105 | $camelCase = StringLiteralFormatter::formatUpperCamelCase($nonSnakeCase); 106 | $this->assertEquals('SomenonSnakeCase', $camelCase); 107 | } 108 | 109 | /** 110 | * @covers \GraphQL\Util\StringLiteralFormatter::formatLowerCamelCase 111 | */ 112 | public function testFormatLowerCamelCase() 113 | { 114 | $snakeCase = 'some_snake_case'; 115 | $camelCase = StringLiteralFormatter::formatLowerCamelCase($snakeCase); 116 | $this->assertEquals('someSnakeCase', $camelCase); 117 | 118 | $nonSnakeCase = 'somenonSnakeCase'; 119 | $camelCase = StringLiteralFormatter::formatLowerCamelCase($nonSnakeCase); 120 | $this->assertEquals('somenonSnakeCase', $camelCase); 121 | } 122 | 123 | 124 | } 125 | -------------------------------------------------------------------------------- /tests/VariableTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('$var: String', (string) $variable); 23 | } 24 | 25 | /** 26 | * @depends testCreateVariable 27 | * 28 | * @covers \GraphQL\Variable::__construct 29 | * @covers \GraphQL\Variable::__toString 30 | */ 31 | public function testCreateRequiredVariable() 32 | { 33 | $variable = new Variable('var', 'String', true); 34 | $this->assertEquals('$var: String!', (string) $variable); 35 | } 36 | 37 | /** 38 | * @depends testCreateRequiredVariable 39 | * 40 | * @covers \GraphQL\Variable::__construct 41 | * @covers \GraphQL\Variable::__toString 42 | */ 43 | public function testRequiredVariableWithDefaultValueDoesNothing() 44 | { 45 | $variable = new Variable('var', 'String', true, 'def'); 46 | $this->assertEquals('$var: String!', (string) $variable); 47 | } 48 | 49 | /** 50 | * @depends testCreateVariable 51 | * 52 | * @covers \GraphQL\Variable::__construct 53 | * @covers \GraphQL\Variable::__toString 54 | */ 55 | public function testOptionalVariableWithDefaultValue() 56 | { 57 | $variable = new Variable('var', 'String', false, 'def'); 58 | $this->assertEquals('$var: String="def"', (string) $variable); 59 | 60 | $variable = new Variable('var', 'String', false, '4'); 61 | $this->assertEquals('$var: String="4"', (string) $variable); 62 | 63 | $variable = new Variable('var', 'Int', false, 4); 64 | $this->assertEquals('$var: Int=4', (string) $variable); 65 | 66 | $variable = new Variable('var', 'Boolean', false, true); 67 | $this->assertEquals('$var: Boolean=true', (string) $variable); 68 | } 69 | } --------------------------------------------------------------------------------