Skip to content
Merged
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: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
run: composer validate --strict

- name: Run composer install
run: composer install -n --prefer-dist
run: composer install -n --prefer-dist --dev

- name: Run PHPUnit with coverage
run: php ./vendor/bin/phpunit --coverage-text
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
3.0.1

- add amphp tests and suggestions

3.0.0

- upgrade php version to 8.3
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Can easy to integrate graphql to any project, all you need is controller. Fast s
- Support all features from [webonyx/graphql-php](https://github.com/webonyx/graphql-php)
- Executable directives
- Apollo Federation/Federation2 support
- Amphp v3 support for async executions
- Popular framework integration:
+ Symfony [axtiva/flexible-graphql-bundle](//github.com/axtiva/flexible-graphql-bundle)

Expand Down Expand Up @@ -61,7 +62,7 @@ Example:
}
```

and run `php example/generate_code.php`, after this you will find in [example/Resolver/CodedCurrency/CodeResolver.php](example/Resolver/CodedCurrency/CodeResolver.php).
and run `php example/generate_code.php`, after this you will find in [example/GraphQL/Resolver/CodedCurrency/CodeResolver.php](example/GraphQL/Resolver/CodedCurrency/CodeResolver.php).
this is your field resolver, define him in your psr container like PsrContainerExample in [example/start_graphql_server.php](example/start_graphql_server.php):

```diff
Expand Down
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
"require-dev": {
"psr/container": "^1 | ^2",
"phpunit/phpunit": "^12",
"phpstan/phpstan": "^2.1"
"phpstan/phpstan": "^2.1",
"amphp/amp": "^3"
},
"suggest": {
"amphp/amp": "Needed for async execution support"
}
}
65 changes: 65 additions & 0 deletions tests/Execution/Amphp/AmphpTypeRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Axtiva\FlexibleGraphql\Tests\Execution\Amphp;

use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use Psr\Container\ContainerInterface;

/**
* A minimal TypeRegistry that wraps field resolvers with AMPHP v3 async wrappers.
* This matches the code pattern generated by TypeRegistryGeneratorBuilderAmphp.
*/
class AmphpTypeRegistry
{
private ContainerInterface $container;

public function __construct(ContainerInterface $container)
{
$this->container = $container;
}

/**
* @return object
*/
private function getService(string $id): object
{
$service = $this->container->get($id);
if (!is_object($service)) {
throw new \RuntimeException("Service not found or invalid object: " . $id);
}

return $service;
}

public function Query(): ObjectType
{
return new ObjectType([
'name' => 'Query',
'fields' => fn() => [
'sum' => new FieldDefinition([
'name' => 'sum',
'description' => null,
'deprecationReason' => null,
'resolve' => (function ($rootValue, $args, $context, $info) {
return \Amp\async((function (mixed $rootValue, array $args, mixed $context, \GraphQL\Type\Definition\ResolveInfo $info): mixed {
return (function ($rootValue, $args, $context, $info) {
$resolver = $this->getService(SumResolver::class);
if (!\is_callable($resolver)) {
throw new \RuntimeException('Resolver service is not callable: ' . SumResolver::class);
}

return $resolver($rootValue, $args, $context, $info);
})($rootValue, $args, $context, $info);
}), $rootValue, $args, $context, $info);
}),
'type' => fn() => Type::int(),
'args' => [],
]),
],
]);
}
}
20 changes: 20 additions & 0 deletions tests/Execution/Amphp/SumResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Axtiva\FlexibleGraphql\Tests\Execution\Amphp;

use Axtiva\FlexibleGraphql\Resolver\ResolverInterface;
use ArrayAccess;
use GraphQL\Type\Definition\ResolveInfo;

/**
* A simple resolver for Mutation.sum that returns 1 + 1 = 2
*/
final class SumResolver implements ResolverInterface
{
public function __invoke(mixed $rootValue, array|ArrayAccess|null $args, mixed $context, ResolveInfo $info): mixed
{
return 1 + 1;
}
}
78 changes: 78 additions & 0 deletions tests/Execution/AmphpV3ExecutionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace Axtiva\FlexibleGraphql\Tests\Execution;

use Axtiva\FlexibleGraphql\Example\PsrContainerExample;
use Axtiva\FlexibleGraphql\Tests\Execution\Amphp\AmphpTypeRegistry;
use Axtiva\FlexibleGraphql\Tests\Execution\Amphp\SumResolver;
use Amp\Future;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use PHPUnit\Framework\TestCase;
use Revolt\EventLoop;

/**
* Validates that the AMPHP v3 integration works correctly at runtime.
*
* The TypeRegistryGeneratorBuilderAmphp builder generates TypeRegistry code where every
* field resolver closure is wrapped with \Amp\async(), matching the pattern tested here.
*/
class AmphpV3ExecutionTest extends TestCase
{
public function testAmphpV3WrappedResolverReturnsFuture(): void
{
$container = new PsrContainerExample([
SumResolver::class => new SumResolver(),
]);

$typeRegistry = new AmphpTypeRegistry($container);
$queryType = $typeRegistry->Query();

$this->assertInstanceOf(ObjectType::class, $queryType);

$fields = $queryType->getFields();
$this->assertArrayHasKey('sum', $fields);

$resolveCallback = $fields['sum']->resolveFn;
$this->assertNotNull($resolveCallback);

/** @var ResolveInfo $resolveInfo */
$resolveInfo = $this->createStub(ResolveInfo::class);
$result = $resolveCallback(null, [], null, $resolveInfo);

$this->assertInstanceOf(Future::class, $result);
}

public function testAmphpV3WrappedResolverResolvesCorrectValue(): void
{
$container = new PsrContainerExample([
SumResolver::class => new SumResolver(),
]);

$typeRegistry = new AmphpTypeRegistry($container);
$queryType = $typeRegistry->Query();

$fields = $queryType->getFields();
$resolveCallback = $fields['sum']->resolveFn;
$this->assertNotNull($resolveCallback);

/** @var ResolveInfo $resolveInfo */
$resolveInfo = $this->createStub(ResolveInfo::class);

/** @var Future<mixed> $future */
$future = $resolveCallback(null, [], null, $resolveInfo);

$resolvedValue = null;

$fiber = new \Fiber(function () use ($future, &$resolvedValue): void {
$resolvedValue = $future->await();
});

$fiber->start();
EventLoop::run();

$this->assertSame(2, $resolvedValue);
}
}
Loading