Skip to content

Refactoring duplicated code into an "ArgumentResolver" #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 9, 2019
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
19 changes: 10 additions & 9 deletions src/FieldsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use TheCodingMachine\GraphQLite\Hydrators\HydratorInterface;
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeExceptionInterface;
use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory;
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
use TheCodingMachine\GraphQLite\Types\CustomTypesRegistry;
use TheCodingMachine\GraphQLite\Types\ID;
use TheCodingMachine\GraphQLite\Types\TypeResolver;
Expand Down Expand Up @@ -65,9 +66,9 @@ class FieldsBuilder
*/
private $typeMapper;
/**
* @var HydratorInterface
* @var ArgumentResolver
*/
private $hydrator;
private $argumentResolver;
/**
* @var AuthenticationServiceInterface
*/
Expand All @@ -90,13 +91,13 @@ class FieldsBuilder
private $namingStrategy;

public function __construct(AnnotationReader $annotationReader, RecursiveTypeMapperInterface $typeMapper,
HydratorInterface $hydrator, AuthenticationServiceInterface $authenticationService,
ArgumentResolver $argumentResolver, AuthenticationServiceInterface $authenticationService,
AuthorizationServiceInterface $authorizationService, TypeResolver $typeResolver,
CachedDocBlockFactory $cachedDocBlockFactory, NamingStrategyInterface $namingStrategy)
{
$this->annotationReader = $annotationReader;
$this->typeMapper = $typeMapper;
$this->hydrator = $hydrator;
$this->argumentResolver = $argumentResolver;
$this->authenticationService = $authenticationService;
$this->authorizationService = $authorizationService;
$this->typeResolver = $typeResolver;
Expand Down Expand Up @@ -266,13 +267,13 @@ private function getFieldsByAnnotations($controller, string $annotationName, boo
if ($failWithValue === null && $type instanceof NonNull) {
$type = $type->getWrappedType();
}
$queryList[] = new QueryField($name, $type, $args, $callable, null, $this->hydrator, $docBlockComment, $injectSource);
$queryList[] = new QueryField($name, $type, $args, $callable, null, $this->argumentResolver, $docBlockComment, $injectSource);
} else {
$callable = [$controller, $methodName];
if ($sourceClassName !== null) {
$queryList[] = new QueryField($name, $type, $args, null, $callable[1], $this->hydrator, $docBlockComment, $injectSource);
$queryList[] = new QueryField($name, $type, $args, null, $callable[1], $this->argumentResolver, $docBlockComment, $injectSource);
} else {
$queryList[] = new QueryField($name, $type, $args, $callable, null, $this->hydrator, $docBlockComment, $injectSource);
$queryList[] = new QueryField($name, $type, $args, $callable, null, $this->argumentResolver, $docBlockComment, $injectSource);
}
}
}
Expand Down Expand Up @@ -392,7 +393,7 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
}

if (!$unauthorized) {
$queryList[] = new QueryField($sourceField->getName(), $type, $args, null, $methodName, $this->hydrator, $docBlockComment, false);
$queryList[] = new QueryField($sourceField->getName(), $type, $args, null, $methodName, $this->argumentResolver, $docBlockComment, false);
} else {
$failWithValue = $sourceField->getFailWith();
$callable = function() use ($failWithValue) {
Expand All @@ -401,7 +402,7 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
if ($failWithValue === null && $type instanceof NonNull) {
$type = $type->getWrappedType();
}
$queryList[] = new QueryField($sourceField->getName(), $type, $args, $callable, null, $this->hydrator, $docBlockComment, false);
$queryList[] = new QueryField($sourceField->getName(), $type, $args, $callable, null, $this->argumentResolver, $docBlockComment, false);
}

}
Expand Down
3 changes: 2 additions & 1 deletion src/FieldsBuilderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory;
use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface;
use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface;
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
use TheCodingMachine\GraphQLite\Types\TypeResolver;

class FieldsBuilderFactory
Expand Down Expand Up @@ -65,7 +66,7 @@ public function buildFieldsBuilder(RecursiveTypeMapperInterface $typeMapper): Fi
return new FieldsBuilder(
$this->annotationReader,
$typeMapper,
$this->hydrator,
new ArgumentResolver($this->hydrator),
$this->authenticationService,
$this->authorizationService,
$this->typeResolver,
Expand Down
2 changes: 1 addition & 1 deletion src/Hydrators/HydratorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
interface HydratorInterface
{
/**
* Whether this hydrate can hydrate the passed data.
* Whether this hydrator can hydrate the passed data.
*
* @param mixed[] $data
* @param InputObjectType $type
Expand Down
11 changes: 6 additions & 5 deletions src/InputTypeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use TheCodingMachine\GraphQLite\Annotations\Type;
use TheCodingMachine\GraphQLite\Hydrators\HydratorInterface;
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
use TheCodingMachine\GraphQLite\Types\ResolvableInputObjectType;

/**
Expand All @@ -31,21 +32,21 @@ class InputTypeGenerator
*/
private $cache = [];
/**
* @var HydratorInterface
* @var ArgumentResolver
*/
private $hydrator;
private $argumentResolver;
/**
* @var InputTypeUtils
*/
private $inputTypeUtils;

public function __construct(InputTypeUtils $inputTypeUtils,
FieldsBuilderFactory $fieldsBuilderFactory,
HydratorInterface $hydrator)
ArgumentResolver $argumentResolver)
{
$this->inputTypeUtils = $inputTypeUtils;
$this->fieldsBuilderFactory = $fieldsBuilderFactory;
$this->hydrator = $hydrator;
$this->argumentResolver = $argumentResolver;
}

/**
Expand All @@ -68,7 +69,7 @@ public function mapFactoryMethod(string $factory, string $methodName, RecursiveT

if (!isset($this->cache[$inputName])) {
// TODO: add comment argument.
$this->cache[$inputName] = new ResolvableInputObjectType($inputName, $this->fieldsBuilderFactory, $recursiveTypeMapper, $object, $methodName, $this->hydrator, null);
$this->cache[$inputName] = new ResolvableInputObjectType($inputName, $this->fieldsBuilderFactory, $recursiveTypeMapper, $object, $methodName, $this->argumentResolver, null);
}

return $this->cache[$inputName];
Expand Down
49 changes: 8 additions & 41 deletions src/QueryField.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use GraphQL\Type\Definition\IDType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\LeafType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\OutputType;
Expand All @@ -16,11 +17,14 @@
use InvalidArgumentException;
use function is_array;
use TheCodingMachine\GraphQLite\Hydrators\HydratorInterface;
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
use TheCodingMachine\GraphQLite\Types\DateTimeType;
use TheCodingMachine\GraphQLite\Types\ID;

/**
* A GraphQL field that maps to a PHP method automatically.
*
* @internal
*/
class QueryField extends FieldDefinition
{
Expand All @@ -31,12 +35,12 @@ class QueryField extends FieldDefinition
* @param array[] $arguments Indexed by argument name, value: ['type'=>InputType, 'defaultValue'=>val].
* @param callable|null $resolve The method to execute
* @param string|null $targetMethodOnSource The name of the method to execute on the source object. Mutually exclusive with $resolve parameter.
* @param HydratorInterface $hydrator
* @param ArgumentResolver $argumentResolver
* @param null|string $comment
* @param bool $injectSource Whether to inject the source object (for Fields), or null for Query and Mutations
* @param array $additionalConfig
*/
public function __construct(string $name, OutputType $type, array $arguments, ?callable $resolve, ?string $targetMethodOnSource, HydratorInterface $hydrator, ?string $comment, bool $injectSource, array $additionalConfig = [])
public function __construct(string $name, OutputType $type, array $arguments, ?callable $resolve, ?string $targetMethodOnSource, ArgumentResolver $argumentResolver, ?string $comment, bool $injectSource, array $additionalConfig = [])
{
$config = [
'name' => $name,
Expand All @@ -47,15 +51,15 @@ public function __construct(string $name, OutputType $type, array $arguments, ?c
$config['description'] = $comment;
}

$config['resolve'] = function ($source, array $args) use ($resolve, $targetMethodOnSource, $arguments, $injectSource, $hydrator) {
$config['resolve'] = function ($source, array $args) use ($resolve, $targetMethodOnSource, $arguments, $injectSource, $argumentResolver) {
$toPassArgs = [];
if ($injectSource) {
$toPassArgs[] = $source;
}
foreach ($arguments as $name => $arr) {
$type = $arr['type'];
if (isset($args[$name])) {
$val = $this->castVal($args[$name], $type, $hydrator);
$val = $argumentResolver->resolve($args[$name], $type);
} elseif (array_key_exists('defaultValue', $arr)) {
$val = $arr['defaultValue'];
} else {
Expand All @@ -78,41 +82,4 @@ public function __construct(string $name, OutputType $type, array $arguments, ?c
$config += $additionalConfig;
parent::__construct($config);
}

private function stripNonNullType(Type $type): Type
{
if ($type instanceof NonNull) {
return $this->stripNonNullType($type->getWrappedType());
}
return $type;
}

/**
* Casts a value received from GraphQL into an argument passed to a method.
*
* @param mixed $val
* @param InputType $type
* @return mixed
*/
private function castVal($val, InputType $type, HydratorInterface $hydrator)
{
$type = $this->stripNonNullType($type);
if ($type instanceof ListOfType) {
if (!is_array($val)) {
throw new InvalidArgumentException('Expected GraphQL List but value passed is not an array.');
}
return array_map(function($item) use ($type, $hydrator) {
return $this->castVal($item, $type->getWrappedType(), $hydrator);
}, $val);
} elseif ($type instanceof DateTimeType) {
return new \DateTimeImmutable($val);
} elseif ($type instanceof IDType) {
return new ID($val);
} elseif ($type instanceof InputObjectType) {
return $hydrator->hydrate($val, $type);
} elseif (!$type instanceof ScalarType) {
throw new \RuntimeException('Unexpected type: '.get_class($type));
}
return $val;
}
}
4 changes: 3 additions & 1 deletion src/SchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use TheCodingMachine\GraphQLite\Security\FailAuthorizationService;
use TheCodingMachine\GraphQLite\Security\VoidAuthenticationService;
use TheCodingMachine\GraphQLite\Security\VoidAuthorizationService;
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
use TheCodingMachine\GraphQLite\Types\TypeResolver;

/**
Expand Down Expand Up @@ -183,6 +184,7 @@ public function createSchema(): Schema
{
$annotationReader = new AnnotationReader($this->getDoctrineAnnotationReader(), AnnotationReader::LAX_MODE);
$hydrator = $this->hydrator ?: new FactoryHydrator();
$argumentResolver = new ArgumentResolver($hydrator);
$authenticationService = $this->authenticationService ?: new FailAuthenticationService();
$authorizationService = $this->authorizationService ?: new FailAuthorizationService();
$typeResolver = new TypeResolver();
Expand All @@ -202,7 +204,7 @@ public function createSchema(): Schema

$typeGenerator = new TypeGenerator($annotationReader, $fieldsBuilderFactory, $namingStrategy, $typeRegistry, $this->container);
$inputTypeUtils = new InputTypeUtils($annotationReader, $namingStrategy);
$inputTypeGenerator = new InputTypeGenerator($inputTypeUtils, $fieldsBuilderFactory, $hydrator);
$inputTypeGenerator = new InputTypeGenerator($inputTypeUtils, $fieldsBuilderFactory, $argumentResolver);

$typeMappers = [];

Expand Down
69 changes: 69 additions & 0 deletions src/Types/ArgumentResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php


namespace TheCodingMachine\GraphQLite\Types;

use function array_map;
use function get_class;
use GraphQL\Type\Definition\IDType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\LeafType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\Type;
use InvalidArgumentException;
use function is_array;
use TheCodingMachine\GraphQLite\Hydrators\HydratorInterface;

/**
* Resolves arguments based on input value and InputType
*/
class ArgumentResolver
{
/**
* @var HydratorInterface
*/
private $hydrator;

public function __construct(HydratorInterface $hydrator)
{
$this->hydrator = $hydrator;
}

/**
* Casts a value received from GraphQL into an argument passed to a method.
*
* @param mixed $val
* @param InputType $type
* @return mixed
*/
public function resolve($val, InputType $type)
{
$type = $this->stripNonNullType($type);
if ($type instanceof ListOfType) {
if (!is_array($val)) {
throw new InvalidArgumentException('Expected GraphQL List but value passed is not an array.');
}
return array_map(function($item) use ($type) {
return $this->resolve($item, $type->getWrappedType());
}, $val);
} elseif ($type instanceof IDType) {
return new ID($val);
} elseif ($type instanceof LeafType) {
return $type->parseValue($val);
} elseif ($type instanceof InputObjectType) {
return $this->hydrator->hydrate($val, $type);
} else {
throw new \RuntimeException('Unexpected type: '.get_class($type));
}
}

private function stripNonNullType(Type $type): Type
{
if ($type instanceof NonNull) {
return $this->stripNonNullType($type->getWrappedType());
}
return $type;
}
}
10 changes: 7 additions & 3 deletions src/Types/DateTimeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

use DateTime;
use DateTimeImmutable;
use Exception;
use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;

Expand Down Expand Up @@ -53,7 +53,10 @@ public function serialize($value): string
*/
public function parseValue($value): ?DateTimeImmutable
{
return DateTimeImmutable::createFromFormat(DateTime::ATOM, $value) ?: null;
if ($value === null) {
return null;
}
return new DateTimeImmutable($value);
}

/**
Expand All @@ -72,6 +75,7 @@ public function parseLiteral($valueNode, array $variables = null)
return $valueNode->value;
}

return null;
// Intentionally without message, as all information already in wrapped Exception
throw new Exception();
}
}
Loading