Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

- Implement `AmpFutureAdapter` for integration with AMPHP v3

## v15.31.5

### Fixed
Expand Down
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"ext-mbstring": "*"
},
"require-dev": {
"amphp/amp": "^2.6",
"amphp/http-server": "^2.1",
"amphp/amp": "^2.6 || ^3.0",
"amphp/http-server": "^2.1 || ^3.0",
"dms/phpunit-arraysubset-asserts": "dev-master",
"ergebnis/composer-normalize": "^2.28",
"friendsofphp/php-cs-fixer": "3.95.1",
Expand All @@ -37,6 +37,7 @@
"ticketswap/phpstan-error-formatter": "1.3.0"
},
"suggest": {
"amphp/amp": "To leverage async resolving on AMPHP platform (v3 with AmpFutureAdapter, v2 with AmpPromiseAdapter)",
"amphp/http-server": "To leverage async resolving with webserver on AMPHP platform",
"psr/http-message": "To use standard GraphQL server",
"react/promise": "To leverage async resolving on React PHP platform"
Expand Down
47 changes: 47 additions & 0 deletions examples/04-async-php/amphp-v3/event-loop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## GraphQL with AMPHP v3 (Fiber-based)

This example uses [AMPHP v3](https://amphp.org/) with fiber-based async execution via `AmpFutureAdapter`.

### Dependencies

```sh
composer require amphp/amp:^3
```

### Run locally

```sh
php -S localhost:8080 graphql.php
```

### Make a request

```sh
curl --data '{"query": "query { product article }"}' \
--header "Content-Type: application/json" \
localhost:8080
```

### Migrating from AMPHP v2

If you were using the `AmpPromiseAdapter` with AMPHP v2, switch to `AmpFutureAdapter` with AMPHP v3:

```php
// Before (amphp/amp ^2)
use GraphQL\Executor\Promise\Adapter\AmpPromiseAdapter;
$adapter = new AmpPromiseAdapter();

// After (amphp/amp ^3)
use GraphQL\Executor\Promise\Adapter\AmpFutureAdapter;
$adapter = new AmpFutureAdapter();
```

Resolver return types change from `Amp\Promise` to `Amp\Future`:

```php
// Before
'resolve' => fn (): \Amp\Promise => \Amp\call(fn (): string => 'value'),

// After
'resolve' => fn (): \Amp\Future => \Amp\async(fn (): string => 'value'),
```
35 changes: 35 additions & 0 deletions examples/04-async-php/amphp-v3/event-loop/graphql.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php declare(strict_types=1);

// php -S localhost:8080 graphql.php
// curl --data '{"query": "query { product article }" }' --header "Content-Type: application/json" localhost:8080

require_once __DIR__ . '/../../../../vendor/autoload.php';

use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Promise\Adapter\AmpFutureAdapter;
use GraphQL\GraphQL;

$schema = require_once __DIR__ . '/../../amphp-v3/schema.php';

$content = file_get_contents('php://input');
$input = [];

if ($content !== false) {
$input = json_decode($content, true);
}

$adapter = new AmpFutureAdapter();

$promise = GraphQL::promiseToExecute(
$adapter,
$schema,
$input['query'],
null,
null,
$input['variables'] ?? null,
$input['operationName'] ?? null
);

$promise->then(function (ExecutionResult $result): void {
echo json_encode($result->toArray(), JSON_THROW_ON_ERROR);
});
18 changes: 18 additions & 0 deletions examples/04-async-php/amphp-v3/http-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## HTTP-Server example with AMPHP

This is a basic example using [AMPHP](https://amphp.org/).

### Dependencies

When you want to use this in your project, you need to install the
following dependencies:

```
composer require amphp/http-server
```

### Run locally

```
php graphql.php
```
55 changes: 55 additions & 0 deletions examples/04-async-php/amphp-v3/http-server/graphql.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types=1);

// php graphql.php
// curl --data '{"query": "query { product article }" }' --header "Content-Type: application/json" localhost:8080

require_once __DIR__ . '/../../../../vendor/autoload.php';

use Amp\Http\Server\DefaultErrorHandler;
use Amp\Http\Server\Request;
use Amp\Http\Server\RequestHandler\ClosureRequestHandler;
use Amp\Http\Server\Response;
use Amp\Http\Server\SocketHttpServer;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Promise\Adapter\AmpFutureAdapter;
use GraphQL\GraphQL;
use Psr\Log\NullLogger;

use function Amp\ByteStream\buffer;

$schema = require_once __DIR__ . '/../schema.php';

$server = SocketHttpServer::createForDirectAccess(new NullLogger());
$server->expose('localhost:8080');

$server->start(
new ClosureRequestHandler(static function (Request $request) use ($schema): Response {
$input = json_decode(buffer($request->getBody()), true);

$adapter = new AmpFutureAdapter();
$promise = GraphQL::promiseToExecute(
$adapter,
$schema,
$input['query'],
null,
null,
$input['variables'] ?? null,
$input['operationName'] ?? null
);

$future = $promise->adoptedPromise;
$result = $future->await();
assert($result instanceof ExecutionResult);

return new Response(
200,
['Content-Type' => 'application/json'],
json_encode($result->toArray(), JSON_THROW_ON_ERROR)
);
}),
new DefaultErrorHandler()
);

Amp\trapSignal([SIGINT, SIGTERM]);

$server->stop();
37 changes: 37 additions & 0 deletions examples/04-async-php/amphp-v3/schema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types=1);

/*
* The resolvers for "product" and "article" run asynchronously via fibers.
* This can have big advantages if you fetch data from different microservices.
* Keep in mind resolvers should use non-blocking async libraries like
* amphp/mysql, amphp/http-client, etc.
*/

use Amp\Future;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;

use function Amp\async;

return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'product' => [
'type' => Type::string(),
'resolve' => static fn (): Future => async(
// use inside the closure e.g. amphp/mysql, amphp/http-client, ...
static fn (): string => 'xyz'
),
],
'article' => [
'type' => Type::string(),
'resolve' => static fn (): Future => async(
// use inside the closure e.g. amphp/mysql, amphp/http-client, ...
static fn (): string => 'zyx'
),
],
],
]),
]);
8 changes: 8 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ parameters:
- message: "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\Node\\:\\:\\$.+~"
path: tests/Language/VisitorTest.php

# Ignore not installed Amp v2 packages
- message: "~(Amp\\\\.* not found|(unknown class|has invalid type|has invalid bound type|invalid return type|Result of method|Method|Cannot instantiate interface) Amp\\\\)~"
paths:
- tests/Executor/Promise/AmpFutureAdapterTest.php
- tests/Executor/Promise/AmpPromiseAdapterTest.php
- src/Executor/Promise
- examples/04-async-php

includes:
- phpstan-baseline.neon
- phpstan/include-by-php-version.php
Expand Down
Loading