Skip to content

Commit

Permalink
Allow constructor fields to use middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
oprypkhantc committed Nov 18, 2023
1 parent 1f55409 commit 0f3aaa5
Show file tree
Hide file tree
Showing 15 changed files with 257 additions and 493 deletions.
4 changes: 2 additions & 2 deletions src/FailedResolvingInputType.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

class FailedResolvingInputType extends RuntimeException
{
public static function createForMissingConstructorParameter(string $class, string $parameter): self
public static function createForMissingConstructorParameter(string $original): self
{
return new self(sprintf("Parameter '%s' is missing for class '%s' constructor. It should be mapped as required field.", $parameter, $class));
return new self(sprintf("%s. It should be mapped as required field.", $original));
}

public static function createForDecorator(string $class): self
Expand Down
168 changes: 72 additions & 96 deletions src/FieldsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@
use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewareInterface;
use TheCodingMachine\GraphQLite\Middlewares\InputFieldHandlerInterface;
use TheCodingMachine\GraphQLite\Middlewares\InputFieldMiddlewareInterface;
use TheCodingMachine\GraphQLite\Middlewares\MagicPropertyResolver;
use TheCodingMachine\GraphQLite\Middlewares\MissingMagicGetException;
use TheCodingMachine\GraphQLite\Middlewares\ServiceResolver;
use TheCodingMachine\GraphQLite\Middlewares\SourceConstructorParameterResolver;
use TheCodingMachine\GraphQLite\Middlewares\SourceInputPropertyResolver;
use TheCodingMachine\GraphQLite\Middlewares\SourceMethodResolver;
use TheCodingMachine\GraphQLite\Middlewares\SourcePropertyResolver;
use TheCodingMachine\GraphQLite\Parameters\InputTypeParameterInterface;
use TheCodingMachine\GraphQLite\Parameters\ParameterInterface;
use TheCodingMachine\GraphQLite\Parameters\PrefetchDataParameter;
Expand Down Expand Up @@ -370,14 +376,6 @@ private function getFieldsByMethodAnnotations(string|object $controller, Reflect
$type = $this->typeMapper->mapReturnType($refMethod, $docBlockObj);
}

$fieldDescriptor = new QueryFieldDescriptor(
name: $name,
type: $type,
comment: trim($description),
deprecationReason: $this->getDeprecationReason($docBlockObj),
refMethod: $refMethod,
);

$parameters = $refMethod->getParameters();
if ($injectSource === true) {
$firstParameter = array_shift($parameters);
Expand All @@ -398,19 +396,19 @@ private function getFieldsByMethodAnnotations(string|object $controller, Reflect
$args = ['__graphqlite_prefectData' => $prefetchDataParameter, ...$args];
}

$fieldDescriptor = $fieldDescriptor->withParameters($args);
$resolver = is_string($controller) ? new SourceMethodResolver($refMethod) : new ServiceResolver([$controller, $methodName]);

if (is_string($controller)) {
$fieldDescriptor = $fieldDescriptor->withTargetMethodOnSource($refMethod->getDeclaringClass()->getName(), $methodName);
} else {
$callable = [$controller, $methodName];
assert(is_callable($callable));
$fieldDescriptor = $fieldDescriptor->withCallable($callable);
}

$fieldDescriptor = $fieldDescriptor
->withInjectSource($injectSource)
->withMiddlewareAnnotations($this->annotationReader->getMiddlewareAnnotations($refMethod));
$fieldDescriptor = new QueryFieldDescriptor(
name: $name,
type: $type,
resolver: $resolver,
originalResolver: $resolver,
parameters: $args,
injectSource: $injectSource,
comment: trim($description),
deprecationReason: $this->getDeprecationReason($docBlockObj),
middlewareAnnotations: $this->annotationReader->getMiddlewareAnnotations($refMethod),
);

$field = $this->fieldMiddleware->process($fieldDescriptor, new class implements FieldHandlerInterface {
public function handle(QueryFieldDescriptor $fieldDescriptor): FieldDefinition|null
Expand Down Expand Up @@ -480,26 +478,21 @@ private function getFieldsByPropertyAnnotations(string|object $controller, Refle
assert($type instanceof OutputType);
}

$resolver = is_string($controller) ?
new SourcePropertyResolver($refProperty) :
new ServiceResolver(fn () => PropertyAccessor::getValue($controller, $refProperty->getName()));

$fieldDescriptor = new QueryFieldDescriptor(
name: $name,
type: $type,
resolver: $resolver,
originalResolver: $resolver,
injectSource: false,
comment: trim($description),
deprecationReason: $this->getDeprecationReason($docBlock),
refProperty: $refProperty,
middlewareAnnotations: $this->annotationReader->getMiddlewareAnnotations($refProperty),
);

if (is_string($controller)) {
$fieldDescriptor = $fieldDescriptor->withTargetPropertyOnSource($refProperty->getDeclaringClass()->getName(), $refProperty->getName());
} else {
$fieldDescriptor = $fieldDescriptor->withCallable(static function () use ($controller, $refProperty) {
return PropertyAccessor::getValue($controller, $refProperty->getName());
});
}

$fieldDescriptor = $fieldDescriptor
->withInjectSource(false)
->withMiddlewareAnnotations($this->annotationReader->getMiddlewareAnnotations($refProperty));

$field = $this->fieldMiddleware->process($fieldDescriptor, new class implements FieldHandlerInterface {
public function handle(QueryFieldDescriptor $fieldDescriptor): FieldDefinition|null
{
Expand Down Expand Up @@ -597,15 +590,16 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
$type = $this->typeMapper->mapReturnType($refMethod, $docBlockObj);
}

$resolver = new SourceMethodResolver($refMethod);

$fieldDescriptor = new QueryFieldDescriptor(
name: $sourceField->getName(),
type: $type,
resolver: $resolver,
originalResolver: $resolver,
parameters: $args,
targetClass: $refMethod->getDeclaringClass()->getName(),
targetMethodOnSource: $methodName,
comment: $description,
deprecationReason: $deprecationReason ?? null,
refMethod: $refMethod,
);
} else {
$outputType = $sourceField->getOutputType();
Expand All @@ -619,11 +613,13 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
$type = $this->resolvePhpType($phpTypeStr, $refClass, $magicGefRefMethod);
}

$resolver = new MagicPropertyResolver($refClass->getName(), $sourceField->getSourceName() ?? $sourceField->getName());

$fieldDescriptor = new QueryFieldDescriptor(
name: $sourceField->getName(),
type: $type,
targetClass: $refClass->getName(),
magicProperty: $sourceField->getSourceName() ?? $sourceField->getName(),
resolver: $resolver,
originalResolver: $resolver,
comment: $sourceField->getDescription(),
);
}
Expand Down Expand Up @@ -889,27 +885,22 @@ private function getInputFieldsByMethodAnnotations(string|object $controller, Re

assert($type instanceof InputType);

$resolver = new SourceMethodResolver($refMethod);

$inputFieldDescriptor = new InputFieldDescriptor(
name: $name,
type: $type,
resolver: $resolver,
originalResolver: $resolver,
parameters: $args,
injectSource: $injectSource,
comment: trim($description),
refMethod: $refMethod,
middlewareAnnotations: $this->annotationReader->getMiddlewareAnnotations($refMethod),
isUpdate: $isUpdate,
hasDefaultValue: $isUpdate,
defaultValue: $args[$name]->getDefaultValue()
);

$inputFieldDescriptor = $inputFieldDescriptor
->withHasDefaultValue($isUpdate)
->withDefaultValue($args[$name]->getDefaultValue());
$constructerParameters = $this->getClassConstructParameterNames($refClass);
if (!in_array($name, $constructerParameters)) {
$inputFieldDescriptor = $inputFieldDescriptor->withTargetMethodOnSource($refMethod->getDeclaringClass()->getName(), $methodName);
}

$inputFieldDescriptor = $inputFieldDescriptor
->withInjectSource($injectSource)
->withMiddlewareAnnotations($this->annotationReader->getMiddlewareAnnotations($refMethod));

$field = $this->inputFieldMiddleware->process($inputFieldDescriptor, new class implements InputFieldHandlerInterface {
public function handle(InputFieldDescriptor $inputFieldDescriptor): InputField|null
{
Expand Down Expand Up @@ -965,53 +956,38 @@ private function getInputFieldsByPropertyAnnotations(string|object $controller,
$description = $inputProperty->getDescription();
}

if (in_array($name, $constructerParameters)) {
$middlewareAnnotations = $this->annotationReader->getPropertyAnnotations($refProperty, MiddlewareAnnotationInterface::class);
if ($middlewareAnnotations !== []) {
throw IncompatibleAnnotationsException::middlewareAnnotationsUnsupported();
}
// constructor hydrated
$field = new InputField(
$name,
$inputProperty->getType(),
[$inputProperty->getName() => $inputProperty],
null,
null,
trim($description),
$isUpdate,
$inputProperty->hasDefaultValue(),
$inputProperty->getDefaultValue(),
);
} else {
$type = $inputProperty->getType();
if (!$inputType && $isUpdate && $type instanceof NonNull) {
$type = $type->getWrappedType();
}
assert($type instanceof InputType);
$type = $inputProperty->getType();
if (!$inputType && $isUpdate && $type instanceof NonNull) {
$type = $type->getWrappedType();
}
assert($type instanceof InputType);
$forConstructorHydration = in_array($name, $constructerParameters);
$resolver = $forConstructorHydration ?
new SourceConstructorParameterResolver($refProperty->getDeclaringClass()->getName(), $refProperty->getName()) :
new SourceInputPropertyResolver($refProperty);

// setters and properties
$inputFieldDescriptor = new InputFieldDescriptor(
name: $inputProperty->getName(),
type: $type,
parameters: [$inputProperty->getName() => $inputProperty],
targetClass: $refProperty->getDeclaringClass()->getName(),
targetPropertyOnSource: $refProperty->getName(),
injectSource: false,
comment: trim($description),
middlewareAnnotations: $this->annotationReader->getMiddlewareAnnotations($refProperty),
refProperty: $refProperty,
isUpdate: $isUpdate,
hasDefaultValue: $inputProperty->hasDefaultValue(),
defaultValue: $inputProperty->getDefaultValue(),
);
// setters and properties
$inputFieldDescriptor = new InputFieldDescriptor(
name: $inputProperty->getName(),
type: $type,
resolver: $resolver,
originalResolver: $resolver,
parameters: [$inputProperty->getName() => $inputProperty],
injectSource: false,
forConstructorHydration: $forConstructorHydration,
comment: trim($description),
middlewareAnnotations: $this->annotationReader->getMiddlewareAnnotations($refProperty),
isUpdate: $isUpdate,
hasDefaultValue: $inputProperty->hasDefaultValue(),
defaultValue: $inputProperty->getDefaultValue(),
);

$field = $this->inputFieldMiddleware->process($inputFieldDescriptor, new class implements InputFieldHandlerInterface {
public function handle(InputFieldDescriptor $inputFieldDescriptor): InputField|null
{
return InputField::fromFieldDescriptor($inputFieldDescriptor);
}
});
}
$field = $this->inputFieldMiddleware->process($inputFieldDescriptor, new class implements InputFieldHandlerInterface {
public function handle(InputFieldDescriptor $inputFieldDescriptor): InputField|null
{
return InputField::fromFieldDescriptor($inputFieldDescriptor);
}
});

if ($field === null) {
continue;
Expand Down
57 changes: 33 additions & 24 deletions src/InputField.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,25 @@ final class InputField extends InputObjectField
/** @var callable */
private $resolve;

private bool $forConstructorHydration = false;

/**
* @param (Type&InputType) $type
* @param array<string, ParameterInterface> $arguments Indexed by argument name.
* @param mixed|null $defaultValue the default value set for this field
* @param array{defaultValue?: mixed,description?: string|null,astNode?: InputValueDefinitionNode|null}|null $additionalConfig
*/
public function __construct(string $name, InputType $type, array $arguments, ResolverInterface|null $originalResolver, callable|null $resolver, string|null $comment, bool $isUpdate, bool $hasDefaultValue, mixed $defaultValue, array|null $additionalConfig = null)
{
public function __construct(
string $name,
InputType $type,
array $arguments,
ResolverInterface|null $originalResolver,
callable|null $resolver,
private bool $forConstructorHydration,
string|null $comment,
bool $isUpdate,
bool $hasDefaultValue,
mixed $defaultValue,
array|null $additionalConfig = null
) {
$config = [
'name' => $name,
'type' => $type,
Expand All @@ -52,28 +61,27 @@ public function __construct(string $name, InputType $type, array $arguments, Res
$config['defaultValue'] = $defaultValue;
}

if ($originalResolver !== null && $resolver !== null) {
$this->resolve = function (object $source, array $args, $context, ResolveInfo $info) use ($arguments, $originalResolver, $resolver) {
$this->resolve = function (object|null $source, array $args, $context, ResolveInfo $info) use ($arguments, $originalResolver, $resolver) {
if ($this->forConstructorHydration) {
$toPassArgs = [
$arguments[$this->name]->resolve($source, $args, $context, $info)
];
} else {
$toPassArgs = $this->paramsToArguments($arguments, $source, $args, $context, $info, $resolver);
$result = $resolver($source, ...$toPassArgs);

try {
$this->assertInputType($result);
} catch (TypeMismatchRuntimeException $e) {
$e->addInfo($this->name, $originalResolver->toString());
throw $e;
}

return $result;
};
} else {
$this->forConstructorHydration = true;
$this->resolve = function (object|null $source, array $args, $context, ResolveInfo $info) use ($arguments) {
$result = $arguments[$this->name]->resolve($source, $args, $context, $info);
}

$result = $resolver($source, ...$toPassArgs);

try {
$this->assertInputType($result);
return $result;
};
}
} catch (TypeMismatchRuntimeException $e) {
$e->addInfo($this->name, $originalResolver->toString());
throw $e;
}

return $result;
};

if ($additionalConfig !== null) {
if (isset($additionalConfig['astNode'])) {
$config['astNode'] = $additionalConfig['astNode'];
Expand Down Expand Up @@ -126,6 +134,7 @@ private static function fromDescriptor(InputFieldDescriptor $fieldDescriptor): s
$fieldDescriptor->getParameters(),
$fieldDescriptor->getOriginalResolver(),
$fieldDescriptor->getResolver(),
$fieldDescriptor->isForConstructorHydration(),
$fieldDescriptor->getComment(),
$fieldDescriptor->isUpdate(),
$fieldDescriptor->hasDefaultValue(),
Expand Down
Loading

0 comments on commit 0f3aaa5

Please sign in to comment.