Skip to content

Commit 0f3aaa5

Browse files
committed
Allow constructor fields to use middleware
1 parent 1f55409 commit 0f3aaa5

15 files changed

+257
-493
lines changed

src/FailedResolvingInputType.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

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

1818
public static function createForDecorator(string $class): self

src/FieldsBuilder.php

+72-96
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@
3838
use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewareInterface;
3939
use TheCodingMachine\GraphQLite\Middlewares\InputFieldHandlerInterface;
4040
use TheCodingMachine\GraphQLite\Middlewares\InputFieldMiddlewareInterface;
41+
use TheCodingMachine\GraphQLite\Middlewares\MagicPropertyResolver;
4142
use TheCodingMachine\GraphQLite\Middlewares\MissingMagicGetException;
43+
use TheCodingMachine\GraphQLite\Middlewares\ServiceResolver;
44+
use TheCodingMachine\GraphQLite\Middlewares\SourceConstructorParameterResolver;
45+
use TheCodingMachine\GraphQLite\Middlewares\SourceInputPropertyResolver;
46+
use TheCodingMachine\GraphQLite\Middlewares\SourceMethodResolver;
47+
use TheCodingMachine\GraphQLite\Middlewares\SourcePropertyResolver;
4248
use TheCodingMachine\GraphQLite\Parameters\InputTypeParameterInterface;
4349
use TheCodingMachine\GraphQLite\Parameters\ParameterInterface;
4450
use TheCodingMachine\GraphQLite\Parameters\PrefetchDataParameter;
@@ -370,14 +376,6 @@ private function getFieldsByMethodAnnotations(string|object $controller, Reflect
370376
$type = $this->typeMapper->mapReturnType($refMethod, $docBlockObj);
371377
}
372378

373-
$fieldDescriptor = new QueryFieldDescriptor(
374-
name: $name,
375-
type: $type,
376-
comment: trim($description),
377-
deprecationReason: $this->getDeprecationReason($docBlockObj),
378-
refMethod: $refMethod,
379-
);
380-
381379
$parameters = $refMethod->getParameters();
382380
if ($injectSource === true) {
383381
$firstParameter = array_shift($parameters);
@@ -398,19 +396,19 @@ private function getFieldsByMethodAnnotations(string|object $controller, Reflect
398396
$args = ['__graphqlite_prefectData' => $prefetchDataParameter, ...$args];
399397
}
400398

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

403-
if (is_string($controller)) {
404-
$fieldDescriptor = $fieldDescriptor->withTargetMethodOnSource($refMethod->getDeclaringClass()->getName(), $methodName);
405-
} else {
406-
$callable = [$controller, $methodName];
407-
assert(is_callable($callable));
408-
$fieldDescriptor = $fieldDescriptor->withCallable($callable);
409-
}
410-
411-
$fieldDescriptor = $fieldDescriptor
412-
->withInjectSource($injectSource)
413-
->withMiddlewareAnnotations($this->annotationReader->getMiddlewareAnnotations($refMethod));
401+
$fieldDescriptor = new QueryFieldDescriptor(
402+
name: $name,
403+
type: $type,
404+
resolver: $resolver,
405+
originalResolver: $resolver,
406+
parameters: $args,
407+
injectSource: $injectSource,
408+
comment: trim($description),
409+
deprecationReason: $this->getDeprecationReason($docBlockObj),
410+
middlewareAnnotations: $this->annotationReader->getMiddlewareAnnotations($refMethod),
411+
);
414412

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

481+
$resolver = is_string($controller) ?
482+
new SourcePropertyResolver($refProperty) :
483+
new ServiceResolver(fn () => PropertyAccessor::getValue($controller, $refProperty->getName()));
484+
483485
$fieldDescriptor = new QueryFieldDescriptor(
484486
name: $name,
485487
type: $type,
488+
resolver: $resolver,
489+
originalResolver: $resolver,
490+
injectSource: false,
486491
comment: trim($description),
487492
deprecationReason: $this->getDeprecationReason($docBlock),
488-
refProperty: $refProperty,
493+
middlewareAnnotations: $this->annotationReader->getMiddlewareAnnotations($refProperty),
489494
);
490495

491-
if (is_string($controller)) {
492-
$fieldDescriptor = $fieldDescriptor->withTargetPropertyOnSource($refProperty->getDeclaringClass()->getName(), $refProperty->getName());
493-
} else {
494-
$fieldDescriptor = $fieldDescriptor->withCallable(static function () use ($controller, $refProperty) {
495-
return PropertyAccessor::getValue($controller, $refProperty->getName());
496-
});
497-
}
498-
499-
$fieldDescriptor = $fieldDescriptor
500-
->withInjectSource(false)
501-
->withMiddlewareAnnotations($this->annotationReader->getMiddlewareAnnotations($refProperty));
502-
503496
$field = $this->fieldMiddleware->process($fieldDescriptor, new class implements FieldHandlerInterface {
504497
public function handle(QueryFieldDescriptor $fieldDescriptor): FieldDefinition|null
505498
{
@@ -597,15 +590,16 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
597590
$type = $this->typeMapper->mapReturnType($refMethod, $docBlockObj);
598591
}
599592

593+
$resolver = new SourceMethodResolver($refMethod);
594+
600595
$fieldDescriptor = new QueryFieldDescriptor(
601596
name: $sourceField->getName(),
602597
type: $type,
598+
resolver: $resolver,
599+
originalResolver: $resolver,
603600
parameters: $args,
604-
targetClass: $refMethod->getDeclaringClass()->getName(),
605-
targetMethodOnSource: $methodName,
606601
comment: $description,
607602
deprecationReason: $deprecationReason ?? null,
608-
refMethod: $refMethod,
609603
);
610604
} else {
611605
$outputType = $sourceField->getOutputType();
@@ -619,11 +613,13 @@ private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionC
619613
$type = $this->resolvePhpType($phpTypeStr, $refClass, $magicGefRefMethod);
620614
}
621615

616+
$resolver = new MagicPropertyResolver($refClass->getName(), $sourceField->getSourceName() ?? $sourceField->getName());
617+
622618
$fieldDescriptor = new QueryFieldDescriptor(
623619
name: $sourceField->getName(),
624620
type: $type,
625-
targetClass: $refClass->getName(),
626-
magicProperty: $sourceField->getSourceName() ?? $sourceField->getName(),
621+
resolver: $resolver,
622+
originalResolver: $resolver,
627623
comment: $sourceField->getDescription(),
628624
);
629625
}
@@ -889,27 +885,22 @@ private function getInputFieldsByMethodAnnotations(string|object $controller, Re
889885

890886
assert($type instanceof InputType);
891887

888+
$resolver = new SourceMethodResolver($refMethod);
889+
892890
$inputFieldDescriptor = new InputFieldDescriptor(
893891
name: $name,
894892
type: $type,
893+
resolver: $resolver,
894+
originalResolver: $resolver,
895895
parameters: $args,
896+
injectSource: $injectSource,
896897
comment: trim($description),
897-
refMethod: $refMethod,
898+
middlewareAnnotations: $this->annotationReader->getMiddlewareAnnotations($refMethod),
898899
isUpdate: $isUpdate,
900+
hasDefaultValue: $isUpdate,
901+
defaultValue: $args[$name]->getDefaultValue()
899902
);
900903

901-
$inputFieldDescriptor = $inputFieldDescriptor
902-
->withHasDefaultValue($isUpdate)
903-
->withDefaultValue($args[$name]->getDefaultValue());
904-
$constructerParameters = $this->getClassConstructParameterNames($refClass);
905-
if (!in_array($name, $constructerParameters)) {
906-
$inputFieldDescriptor = $inputFieldDescriptor->withTargetMethodOnSource($refMethod->getDeclaringClass()->getName(), $methodName);
907-
}
908-
909-
$inputFieldDescriptor = $inputFieldDescriptor
910-
->withInjectSource($injectSource)
911-
->withMiddlewareAnnotations($this->annotationReader->getMiddlewareAnnotations($refMethod));
912-
913904
$field = $this->inputFieldMiddleware->process($inputFieldDescriptor, new class implements InputFieldHandlerInterface {
914905
public function handle(InputFieldDescriptor $inputFieldDescriptor): InputField|null
915906
{
@@ -965,53 +956,38 @@ private function getInputFieldsByPropertyAnnotations(string|object $controller,
965956
$description = $inputProperty->getDescription();
966957
}
967958

968-
if (in_array($name, $constructerParameters)) {
969-
$middlewareAnnotations = $this->annotationReader->getPropertyAnnotations($refProperty, MiddlewareAnnotationInterface::class);
970-
if ($middlewareAnnotations !== []) {
971-
throw IncompatibleAnnotationsException::middlewareAnnotationsUnsupported();
972-
}
973-
// constructor hydrated
974-
$field = new InputField(
975-
$name,
976-
$inputProperty->getType(),
977-
[$inputProperty->getName() => $inputProperty],
978-
null,
979-
null,
980-
trim($description),
981-
$isUpdate,
982-
$inputProperty->hasDefaultValue(),
983-
$inputProperty->getDefaultValue(),
984-
);
985-
} else {
986-
$type = $inputProperty->getType();
987-
if (!$inputType && $isUpdate && $type instanceof NonNull) {
988-
$type = $type->getWrappedType();
989-
}
990-
assert($type instanceof InputType);
959+
$type = $inputProperty->getType();
960+
if (!$inputType && $isUpdate && $type instanceof NonNull) {
961+
$type = $type->getWrappedType();
962+
}
963+
assert($type instanceof InputType);
964+
$forConstructorHydration = in_array($name, $constructerParameters);
965+
$resolver = $forConstructorHydration ?
966+
new SourceConstructorParameterResolver($refProperty->getDeclaringClass()->getName(), $refProperty->getName()) :
967+
new SourceInputPropertyResolver($refProperty);
991968

992-
// setters and properties
993-
$inputFieldDescriptor = new InputFieldDescriptor(
994-
name: $inputProperty->getName(),
995-
type: $type,
996-
parameters: [$inputProperty->getName() => $inputProperty],
997-
targetClass: $refProperty->getDeclaringClass()->getName(),
998-
targetPropertyOnSource: $refProperty->getName(),
999-
injectSource: false,
1000-
comment: trim($description),
1001-
middlewareAnnotations: $this->annotationReader->getMiddlewareAnnotations($refProperty),
1002-
refProperty: $refProperty,
1003-
isUpdate: $isUpdate,
1004-
hasDefaultValue: $inputProperty->hasDefaultValue(),
1005-
defaultValue: $inputProperty->getDefaultValue(),
1006-
);
969+
// setters and properties
970+
$inputFieldDescriptor = new InputFieldDescriptor(
971+
name: $inputProperty->getName(),
972+
type: $type,
973+
resolver: $resolver,
974+
originalResolver: $resolver,
975+
parameters: [$inputProperty->getName() => $inputProperty],
976+
injectSource: false,
977+
forConstructorHydration: $forConstructorHydration,
978+
comment: trim($description),
979+
middlewareAnnotations: $this->annotationReader->getMiddlewareAnnotations($refProperty),
980+
isUpdate: $isUpdate,
981+
hasDefaultValue: $inputProperty->hasDefaultValue(),
982+
defaultValue: $inputProperty->getDefaultValue(),
983+
);
1007984

1008-
$field = $this->inputFieldMiddleware->process($inputFieldDescriptor, new class implements InputFieldHandlerInterface {
1009-
public function handle(InputFieldDescriptor $inputFieldDescriptor): InputField|null
1010-
{
1011-
return InputField::fromFieldDescriptor($inputFieldDescriptor);
1012-
}
1013-
});
1014-
}
985+
$field = $this->inputFieldMiddleware->process($inputFieldDescriptor, new class implements InputFieldHandlerInterface {
986+
public function handle(InputFieldDescriptor $inputFieldDescriptor): InputField|null
987+
{
988+
return InputField::fromFieldDescriptor($inputFieldDescriptor);
989+
}
990+
});
1015991

1016992
if ($field === null) {
1017993
continue;

src/InputField.php

+33-24
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,25 @@ final class InputField extends InputObjectField
3232
/** @var callable */
3333
private $resolve;
3434

35-
private bool $forConstructorHydration = false;
36-
3735
/**
3836
* @param (Type&InputType) $type
3937
* @param array<string, ParameterInterface> $arguments Indexed by argument name.
4038
* @param mixed|null $defaultValue the default value set for this field
4139
* @param array{defaultValue?: mixed,description?: string|null,astNode?: InputValueDefinitionNode|null}|null $additionalConfig
4240
*/
43-
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)
44-
{
41+
public function __construct(
42+
string $name,
43+
InputType $type,
44+
array $arguments,
45+
ResolverInterface|null $originalResolver,
46+
callable|null $resolver,
47+
private bool $forConstructorHydration,
48+
string|null $comment,
49+
bool $isUpdate,
50+
bool $hasDefaultValue,
51+
mixed $defaultValue,
52+
array|null $additionalConfig = null
53+
) {
4554
$config = [
4655
'name' => $name,
4756
'type' => $type,
@@ -52,28 +61,27 @@ public function __construct(string $name, InputType $type, array $arguments, Res
5261
$config['defaultValue'] = $defaultValue;
5362
}
5463

55-
if ($originalResolver !== null && $resolver !== null) {
56-
$this->resolve = function (object $source, array $args, $context, ResolveInfo $info) use ($arguments, $originalResolver, $resolver) {
64+
$this->resolve = function (object|null $source, array $args, $context, ResolveInfo $info) use ($arguments, $originalResolver, $resolver) {
65+
if ($this->forConstructorHydration) {
66+
$toPassArgs = [
67+
$arguments[$this->name]->resolve($source, $args, $context, $info)
68+
];
69+
} else {
5770
$toPassArgs = $this->paramsToArguments($arguments, $source, $args, $context, $info, $resolver);
58-
$result = $resolver($source, ...$toPassArgs);
59-
60-
try {
61-
$this->assertInputType($result);
62-
} catch (TypeMismatchRuntimeException $e) {
63-
$e->addInfo($this->name, $originalResolver->toString());
64-
throw $e;
65-
}
66-
67-
return $result;
68-
};
69-
} else {
70-
$this->forConstructorHydration = true;
71-
$this->resolve = function (object|null $source, array $args, $context, ResolveInfo $info) use ($arguments) {
72-
$result = $arguments[$this->name]->resolve($source, $args, $context, $info);
71+
}
72+
73+
$result = $resolver($source, ...$toPassArgs);
74+
75+
try {
7376
$this->assertInputType($result);
74-
return $result;
75-
};
76-
}
77+
} catch (TypeMismatchRuntimeException $e) {
78+
$e->addInfo($this->name, $originalResolver->toString());
79+
throw $e;
80+
}
81+
82+
return $result;
83+
};
84+
7785
if ($additionalConfig !== null) {
7886
if (isset($additionalConfig['astNode'])) {
7987
$config['astNode'] = $additionalConfig['astNode'];
@@ -126,6 +134,7 @@ private static function fromDescriptor(InputFieldDescriptor $fieldDescriptor): s
126134
$fieldDescriptor->getParameters(),
127135
$fieldDescriptor->getOriginalResolver(),
128136
$fieldDescriptor->getResolver(),
137+
$fieldDescriptor->isForConstructorHydration(),
129138
$fieldDescriptor->getComment(),
130139
$fieldDescriptor->isUpdate(),
131140
$fieldDescriptor->hasDefaultValue(),

0 commit comments

Comments
 (0)