Skip to content

Bring input setters on par with type getters #466

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
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
b5a0a90
Fixed incorrect mapping causing input types to be undiscovered during…
oojacoboo Apr 9, 2022
83357f0
Treat "Input" as a "type" for AnnotationReader
oojacoboo Apr 9, 2022
b906e01
Use TypeInterface for return typing
oojacoboo Apr 9, 2022
0dc67cb
Further interface implementation
oojacoboo Apr 9, 2022
17505ec
Default to true, regardless of name attribute - following @Type imple…
oojacoboo Apr 9, 2022
a53a2dc
Ensure the mapping has the type name as well
oojacoboo Apr 9, 2022
0a1d85c
CS fixes
oojacoboo Apr 9, 2022
b739d31
Fixed tests
oojacoboo Apr 9, 2022
c579d20
Handle Input type processing in RecursiveTypeMapper
oojacoboo Apr 9, 2022
0e2186e
Default value is null
oojacoboo Apr 9, 2022
37d2aa5
Resolve PHPStan error
oojacoboo Apr 9, 2022
badb43a
Resolved PHPStan errors
oojacoboo Apr 9, 2022
3e14ea0
Revert default logic to avoid default attribute requirements
oojacoboo Apr 9, 2022
189c2a7
CS fixes
oojacoboo Apr 9, 2022
da4a42b
Added additional clarity to docs on @Input annotation
oojacoboo Apr 9, 2022
d397a17
Removed superfluous annotation name
oojacoboo Apr 9, 2022
b1a4509
Remove temporary phpunit group annotation
oojacoboo Apr 10, 2022
357500c
Add experimental setter equivalent to getter functionality for fields
Lappihuan Apr 19, 2022
e158fb7
add .idea
eldr0n Apr 19, 2022
9b3a12f
add missing naming strategy for input type
Lappihuan Apr 19, 2022
13811f2
Merge remote-tracking branch 'origin/bring_input_setters_on_par_with_…
Lappihuan Apr 19, 2022
31a413b
added simple inputType unit test with field annotation on setter inst…
Lappihuan Apr 19, 2022
9814c4a
Add TrickyProduct Model for e2e test
eldr0n Apr 19, 2022
67bdb8b
exclude fields from type if the method begins with set*
Lappihuan Apr 19, 2022
6d892b4
Add Constructor and Getter
eldr0n Apr 19, 2022
360179c
Add Query and Mutations for TrickyProduct
eldr0n Apr 19, 2022
b0e5f03
remove redundant field annotation from model
Lappihuan Apr 19, 2022
5ab9e3c
call setter even if property is already set via constructor
eldr0n Apr 20, 2022
8e90786
add autowired service to setter
eldr0n Apr 20, 2022
091794f
add UseInputType annotation
eldr0n Apr 20, 2022
5567475
add test for model with autowire on setter
eldr0n Apr 20, 2022
b9bf877
Bump codecov/codecov-action from 2.1.0 to 3.0.0 (#459)
dependabot[bot] Apr 13, 2022
d0118d0
Default to true, regardless of name attribute - following @Type imple…
oojacoboo Apr 9, 2022
b92ec80
Resolved PHPStan errors
oojacoboo Apr 9, 2022
242752a
Revert default logic to avoid default attribute requirements
oojacoboo Apr 9, 2022
e4cd3ca
Merge branch 'oojacoboo:master' into bring_input_setters_on_par_with_…
eldr0n Apr 20, 2022
1b72106
cloned fild middleware for inputs
Lappihuan Apr 21, 2022
97ca804
reused InputTypeProperty for property based argument resolution
Lappihuan Apr 21, 2022
5262570
added clones of authorization and security middlewares for inputs
Lappihuan Apr 21, 2022
035109f
fix authorization input middleware to not throw an error on inspectio…
Lappihuan Apr 21, 2022
9ef4459
add test for model with autowire on setter and security
eldr0n Apr 22, 2022
95b436c
phpstan cleanup
Lappihuan Apr 22, 2022
16cd4b0
fix typings arround input field middleware
Lappihuan Apr 22, 2022
4ae6c2a
fix default values in InputFieldDescriptor so the middleware tests ca…
Lappihuan Apr 22, 2022
51297dd
fix strange intersection type issue on InputFieldDescriptor
Lappihuan Apr 22, 2022
3987609
fix strange intersection type issue on InputFieldDescriptor (1)
Lappihuan Apr 22, 2022
27d2c7a
fix strange intersection type issue on InputFieldDescriptor (2)
Lappihuan Apr 22, 2022
f7db15e
satisfy phpstan with typing that phpstorm doesn't like..
Lappihuan Apr 22, 2022
1cddc02
satisfy phpstan with typing that phpstorm doesn't like..
Lappihuan Apr 22, 2022
776c589
satisfy phpstan with typing that phpstorm doesn't like..
Lappihuan Apr 22, 2022
91d0889
make sure input type is using callable
Lappihuan Apr 22, 2022
1a21b83
allow "inputType" property on "Field" Attribute to overwrite nullabil…
Lappihuan Apr 24, 2022
944b1bd
making sure "set" prefixed methods are only skipped for fields, previ…
Lappihuan Apr 24, 2022
c2762a8
minor cleanup and additional codecoverage
Lappihuan Apr 24, 2022
ef0c531
changed field resolution for input types to be properly lazy.
Lappihuan Apr 24, 2022
985a1e5
removing unused code
Lappihuan Apr 25, 2022
458cff3
reverted duplicate field exception to not throw and just overwrite pr…
Lappihuan Apr 26, 2022
1b0c521
add test for model with array field
eldr0n Apr 27, 2022
029d7a4
fixed return of resolve to actually return the value so it can be ass…
Lappihuan Apr 27, 2022
b95e08c
Added test for Input construction and property hydration
oojacoboo May 1, 2022
ec2dcbb
fixed inputFields property should be initialized as empty array
Lappihuan May 1, 2022
612b6a9
freeze and getFields has to be called on InputType in the test
Lappihuan May 1, 2022
07d2ea2
made test not fail
Lappihuan May 1, 2022
38d3232
handle constructor set properties with more care.
Lappihuan May 2, 2022
9b27f09
detect constructor hydrated fields early and prevent middleware funct…
Lappihuan May 3, 2022
a57a435
minor cleanup
Lappihuan May 3, 2022
d599513
fixed all coding style issues pointed out in review
Lappihuan May 22, 2022
f765a22
Added comments and resolved remaining requested changes
oojacoboo Jun 10, 2022
59c11cd
CS fixes
oojacoboo Jun 11, 2022
a5ceec8
CS fixes
oojacoboo Jun 11, 2022
e9e9854
Test removing Symfony v4 for PHP 8 support
oojacoboo Jun 11, 2022
b0520c9
Require ecodev/graphql-upload 6.1.3 for PHP 8.1 support
oojacoboo Jun 11, 2022
b025aed
Do not ignore platform-reqs with Composer on CI
oojacoboo Jun 11, 2022
329c948
Allow 6.1 of ecodev/graphql-upload for PHP 7.4 and 8.0 support
oojacoboo Jun 11, 2022
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
6 changes: 0 additions & 6 deletions .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,6 @@ jobs:
- name: "Install dependencies with composer"
run: |
composer update ${{ matrix.install-args }} --no-interaction --no-progress --prefer-dist
if: ${{ matrix.php-version != '8.0' }}

- name: "Install dependencies with composer. Ignoring platform reqs to bypass a problem with ecodev/graphql-upload available only with latest Webonyx on PHP8."
run: |
composer update ${{ matrix.install-args }} --no-interaction --no-progress --prefer-dist --ignore-platform-reqs
if: ${{ matrix.php-version == '8.0' }}

- name: "Run tests with phpunit/phpunit"
run: "vendor/bin/phpunit"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/src/Tests/
/phpunit.xml
/.php_cs/cache
/.idea

node_modules

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"require-dev": {
"beberlei/porpaginas": "^1.2",
"doctrine/coding-standard": "^9.0",
"ecodev/graphql-upload": "^4.0 || ^5.0 || ^6.0",
"ecodev/graphql-upload": "^6.1",
"laminas/laminas-diactoros": "^2",
"mouf/picotainer": "^1.1",
"myclabs/php-enum": "^1.6.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ public static function cannotUseFailWithAndHide(): self
{
return new self('You cannot use "FailWith" and "HideIfUnauthorized" annotations in the same method. These annotations are mutually exclusive.');
}

public static function middlewareAnnotationsUnsupported(): self
{
return new self('You cannot use Middleware annotations for properties that are hydrated via constructor.');
}
}
297 changes: 270 additions & 27 deletions src/FieldsBuilder.php

Large diffs are not rendered by default.

196 changes: 196 additions & 0 deletions src/InputField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite;

use GraphQL\Error\ClientAware;
use GraphQL\Type\Definition\InputObjectField;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\NullableType;
use TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException;
use TheCodingMachine\GraphQLite\Middlewares\MissingAuthorizationException;
use TheCodingMachine\GraphQLite\Middlewares\ResolverInterface;
use TheCodingMachine\GraphQLite\Middlewares\SourceResolverInterface;
use TheCodingMachine\GraphQLite\Parameters\InputTypeParameter;
use TheCodingMachine\GraphQLite\Parameters\InputTypeProperty;
use TheCodingMachine\GraphQLite\Parameters\MissingArgumentException;
use TheCodingMachine\GraphQLite\Parameters\ParameterInterface;
use TheCodingMachine\GraphQLite\Parameters\SourceParameter;

/**
* A GraphQL input field that maps to a PHP method automatically.
*
* @internal
*/
class InputField extends InputObjectField
{
/** @var Callable */
private $resolve;

/** @var bool */
private $forConstructorHydration = false;

/**
* @param InputType|(NullableType&Type) $type
* @param array<string, ParameterInterface> $arguments Indexed by argument name.
* @param ResolverInterface $originalResolver A pointer to the resolver being called (but not wrapped by any field middleware)
* @param callable $resolver The resolver actually called
* @param mixed|null $defaultValue the default value set for this field
* @param array<string, mixed> $additionalConfig
*/
public function __construct(string $name, $type, array $arguments, ?ResolverInterface $originalResolver, ?callable $resolver, ?string $comment, bool $isUpdate,bool $hasDefaultValue, $defaultValue, array $additionalConfig = [])
{
$config = [
'name' => $name,
'type' => $type,
'description' => $comment
];

if (!(!$hasDefaultValue || $isUpdate)) {
$config['defaultValue'] = $defaultValue;
}

if ($originalResolver !== null && $resolver !== null){
$this->resolve = function ($source, array $args, $context, ResolveInfo $info) use ($arguments, $originalResolver, $resolver) {
if ($originalResolver instanceof SourceResolverInterface) {
$originalResolver->setObject($source);
}
$toPassArgs = $this->paramsToArguments($arguments, $source, $args, $context, $info, $resolver);
$result = $resolver(...$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 ($source, array $args, $context, ResolveInfo $info) use ($arguments) {
$result = $arguments[$this->name]->resolve($source, $args, $context, $info);
$this->assertInputType($result);
return $result;
};
}


$config += $additionalConfig;
parent::__construct($config);
}

/**
* @return Callable
*/
public function getResolve()
{
return $this->resolve;
}

/**
*
* @param mixed $input
*/
private function assertInputType($input): void
{
$type = $this->removeNonNull($this->getType());
if (! $type instanceof ListOfType) {
return;
}

ResolveUtils::assertInnerInputType($input, $type);
}

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

return $type;
}

public function forConstructorHydration(): bool
{
return $this->forConstructorHydration;
}

/**
* @param bool $isNotLogged False if the user is logged (and the error is a 403), true if the error is unlogged (the error is a 401)
*
* @return InputField
*/
public static function unauthorizedError(InputFieldDescriptor $fieldDescriptor, bool $isNotLogged): self
{
$callable = static function () use ($isNotLogged): void {
if ($isNotLogged) {
throw MissingAuthorizationException::unauthorized();
}
throw MissingAuthorizationException::forbidden();
};

$fieldDescriptor->setResolver($callable);

return self::fromDescriptor($fieldDescriptor);
}

private static function fromDescriptor(InputFieldDescriptor $fieldDescriptor): self
{
return new self(
$fieldDescriptor->getName(),
$fieldDescriptor->getType(),
$fieldDescriptor->getParameters(),
$fieldDescriptor->getOriginalResolver(),
$fieldDescriptor->getResolver(),
$fieldDescriptor->getComment(),
$fieldDescriptor->isUpdate(),
$fieldDescriptor->hasDefaultValue(),
$fieldDescriptor->getDefaultValue(),
);
}

public static function fromFieldDescriptor(InputFieldDescriptor $fieldDescriptor): self
{
$arguments = $fieldDescriptor->getParameters();
if ($fieldDescriptor->isInjectSource() === true) {
$arguments = ['__graphqlite_source' => new SourceParameter()] + $arguments;
}
$fieldDescriptor->setParameters($arguments);

return self::fromDescriptor($fieldDescriptor);
}

/**
* Casts parameters array into an array of arguments ready to be passed to the resolver.
*
* @param ParameterInterface[] $parameters
* @param array<string, mixed> $args
* @param mixed $context
*
* @return array<int, mixed>
*/
private function paramsToArguments(array $parameters, ?object $source, array $args, $context, ResolveInfo $info, callable $resolve): array
{
$toPassArgs = [];
$exceptions = [];
foreach ($parameters as $parameter) {
try {
$toPassArgs[] = $parameter->resolve($source, $args, $context, $info);
} catch (MissingArgumentException $e) {
throw MissingArgumentException::wrapWithFieldContext($e, $this->name, $resolve);
} catch (ClientAware $e) {
$exceptions[] = $e;
}
}
GraphQLAggregateException::throwExceptions($exceptions);

return $toPassArgs;
}
}
Loading