Skip to content

Commit

Permalink
Merge pull request #107 from moufmouf/fix_prefetch_cache
Browse files Browse the repository at this point in the history
Fixing prefetch cache
  • Loading branch information
moufmouf committed Oct 18, 2019
2 parents 5d60ddc + 82d297a commit 50fb4df
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 13 deletions.
17 changes: 16 additions & 1 deletion docs/other_frameworks.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ $factory->prodMode();
$factory->devMode();
```

### GraphQLite context

Webonyx allows you pass a "context" object when running a query.
For some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context
with an instance of the `TheCodingMachine\GraphQLite\Context\Context` class.

For instance:

```php
use TheCodingMachine\GraphQLite\Context\Context;

$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);
```

## Minimal example

The smallest working example using no framework is:
Expand All @@ -90,6 +104,7 @@ The smallest working example using no framework is:
use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use TheCodingMachine\GraphQLite\SchemaFactory;
use TheCodingMachine\GraphQLite\Context\Context;

// $cache is a PSR-16 compatible cache.
// $container is a PSR-11 compatible container.
Expand All @@ -104,7 +119,7 @@ $input = json_decode($rawInput, true);
$query = $input['query'];
$variableValues = isset($input['variables']) ? $input['variables'] : null;

$result = GraphQL::executeQuery($schema, $query, null, null, $variableValues);
$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);
$output = $result->toArray();

header('Content-Type: application/json');
Expand Down
44 changes: 44 additions & 0 deletions src/Context/Context.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Context;

use SplObjectStorage;
use TheCodingMachine\GraphQLite\PrefetchBuffer;
use TheCodingMachine\GraphQLite\QueryField;

/**
* A context class that should be passed to the Webonyx executor.
*/
class Context implements ContextInterface, ResetableContextInterface
{
/** @var SplObjectStorage */
private $prefetchBuffers;

public function __construct()
{
$this->prefetchBuffers = new SplObjectStorage();
}

/**
* Returns the prefetch buffer associated to the field $field.
* (the buffer is created on the fly if it does not exist yet).
*/
public function getPrefetchBuffer(QueryField $field): PrefetchBuffer
{
if ($this->prefetchBuffers->offsetExists($this)) {
$prefetchBuffer = $this->prefetchBuffers->offsetGet($this);
} else {
$prefetchBuffer = new PrefetchBuffer();
$this->prefetchBuffers->offsetSet($this, $prefetchBuffer);
}

return $prefetchBuffer;
}

public function reset(): void
{
$this->prefetchBuffers = new SplObjectStorage();
}
}
20 changes: 20 additions & 0 deletions src/Context/ContextInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Context;

use TheCodingMachine\GraphQLite\PrefetchBuffer;
use TheCodingMachine\GraphQLite\QueryField;

/**
* A context class that should be passed to the Webonyx executor.
*/
interface ContextInterface
{
/**
* Returns the prefetch buffer associated to the field $field.
* (the buffer is created on the fly if it does not exist yet).
*/
public function getPrefetchBuffer(QueryField $field): PrefetchBuffer;
}
13 changes: 13 additions & 0 deletions src/Context/ResetableContextInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Context;

/**
* Contexts implementing this interface can be reseted.
*/
interface ResetableContextInterface
{
public function reset(): void;
}
2 changes: 2 additions & 0 deletions src/Http/Psr15GraphQLMiddlewareBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Server\MiddlewareInterface;
use TheCodingMachine\GraphQLite\Context\Context;
use TheCodingMachine\GraphQLite\Exceptions\WebonyxErrorHandler;
use TheCodingMachine\GraphQLite\GraphQLRuntimeException;
use Zend\Diactoros\ResponseFactory;
Expand Down Expand Up @@ -42,6 +43,7 @@ public function __construct(Schema $schema)
$this->config->setDebug(Debug::RETHROW_UNSAFE_EXCEPTIONS);
$this->config->setErrorFormatter([WebonyxErrorHandler::class, 'errorFormatter']);
$this->config->setErrorsHandler([WebonyxErrorHandler::class, 'errorHandler']);
$this->config->setContext(new Context());
$this->httpCodeDecider = new HttpCodeDecider();
}

Expand Down
6 changes: 5 additions & 1 deletion src/Http/WebonyxGraphqlMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use RuntimeException;
use TheCodingMachine\GraphQLite\Context\ResetableContextInterface;
use const JSON_ERROR_NONE;
use function array_map;
use function explode;
Expand Down Expand Up @@ -79,14 +80,17 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
$content = $request->getBody()->getContents();
$data = json_decode($content, true);

// FIXME: DO WE NEED THIS????
if ($data === false || json_last_error() !== JSON_ERROR_NONE) {
throw new InvalidArgumentException(json_last_error_msg() . ' in body: "' . $content . '"'); // @codeCoverageIgnore
}

$request = $request->withParsedBody($data);
}

$context = $this->config->getContext();
if ($context instanceof ResetableContextInterface) {
$context->reset();
}
$result = $this->standardServer->executePsrRequest($request);
//return $this->standardServer->processPsrRequest($request, $this->responseFactory->createResponse(), $this->streamFactory->createStream());

Expand Down
12 changes: 10 additions & 2 deletions src/QueryField.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use InvalidArgumentException;
use TheCodingMachine\GraphQLite\Context\ContextInterface;
use TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException;
use TheCodingMachine\GraphQLite\Middlewares\MissingAuthorizationException;
use TheCodingMachine\GraphQLite\Parameters\MissingArgumentException;
Expand Down Expand Up @@ -82,9 +83,16 @@ public function __construct(string $name, OutputType $type, array $arguments, ?c
if ($prefetchMethodName === null) {
$config['resolve'] = $resolveFn;
} else {
$prefetchBuffer = new PrefetchBuffer();
$config['resolve'] = function ($source, array $args, $context, ResolveInfo $info) use ($arguments, $prefetchArgs, $prefetchMethodName, $resolve, $resolveFn) {
// The PrefetchBuffer must be tied to the current request execution. The only object we have for this is $context
// $context MUST be a ContextInterface

if (! $context instanceof ContextInterface) {
throw new GraphQLRuntimeException('When using "prefetch", you sure ensure that the GraphQL execution "context" (passed to the GraphQL::executeQuery method) is an instance of \TheCodingMachine\GraphQLite\Context\Context');
}

$prefetchBuffer = $context->getPrefetchBuffer($this);

$config['resolve'] = function ($source, array $args, $context, ResolveInfo $info) use ($prefetchBuffer, $arguments, $prefetchArgs, $prefetchMethodName, $resolve, $resolveFn) {
$prefetchBuffer->register($source, $args);

return new Deferred(function () use ($prefetchBuffer, $source, $args, $context, $info, $prefetchArgs, $prefetchMethodName, $arguments, $resolveFn, $resolve) {
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/Integration/Models/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public function setCompany(string $company): void
*/
public function repeatInnerName($data): string
{
$index = array_search($this, $data, false);
$index = array_search($this, $data, true);
if ($index === false) {
throw new \RuntimeException('Index not found');
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/Integration/Types/ContactType.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function customField(Contact $contact, string $prefix): string
*/
public function repeatName(Contact $contact, $data, string $suffix): string
{
$index = array_search($contact, $data['contacts'], false);
$index = array_search($contact, $data['contacts'], true);
if ($index === false) {
throw new \RuntimeException('Index not found');
}
Expand Down
49 changes: 42 additions & 7 deletions tests/Integration/EndToEndTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,18 @@
use Doctrine\Common\Annotations\AnnotationReader as DoctrineAnnotationReader;
use GraphQL\Error\Debug;
use GraphQL\GraphQL;
use GraphQL\Type\Definition\NonNull;
use Mouf\Picotainer\Picotainer;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Psr\SimpleCache\CacheInterface;
use stdClass;
use Symfony\Component\Cache\Adapter\Psr16Adapter;
use Symfony\Component\Cache\Simple\ArrayCache;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Lock\Factory as LockFactory;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;
use TheCodingMachine\GraphQLite\AnnotationReader;
use TheCodingMachine\GraphQLite\Context\Context;
use TheCodingMachine\GraphQLite\FieldsBuilder;
use TheCodingMachine\GraphQLite\GlobControllerQueryProvider;
use TheCodingMachine\GraphQLite\GraphQLRuntimeException;
use TheCodingMachine\GraphQLite\InputTypeGenerator;
use TheCodingMachine\GraphQLite\InputTypeUtils;
use TheCodingMachine\GraphQLite\Mappers\CompositeTypeMapper;
Expand Down Expand Up @@ -263,7 +260,9 @@ public function testEndToEnd(): void

$result = GraphQL::executeQuery(
$schema,
$queryString
$queryString,
null,
new Context()
);

$this->assertSame([
Expand All @@ -290,7 +289,9 @@ public function testEndToEnd(): void
// Let's redo this to test cache.
$result = GraphQL::executeQuery(
$schema,
$queryString
$queryString,
null,
new Context()
);

$this->assertSame([
Expand All @@ -315,6 +316,40 @@ public function testEndToEnd(): void
], $result->toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS)['data']);
}

public function testPrefetchException(): void
{
/**
* @var Schema $schema
*/
$schema = $this->mainContainer->get(Schema::class);

$schema->assertValid();

$queryString = '
query {
contacts {
name
company
uppercaseName
repeatName(prefix:"foo", suffix:"bar")
repeatInnerName
... on User {
email
}
}
}
';

$result = GraphQL::executeQuery(
$schema,
$queryString
);

$this->expectException(GraphQLRuntimeException::class);
$this->expectExceptionMessage('When using "prefetch", you sure ensure that the GraphQL execution "context" (passed to the GraphQL::executeQuery method) is an instance of \\TheCodingMachine\\GraphQLite\\Context');
$result->toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS);
}

public function testEndToEndInputTypeDate()
{
/**
Expand Down

0 comments on commit 50fb4df

Please sign in to comment.