Skip to content

Annotated input type #269

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 42 commits into from
Mar 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
eb783f3
Implemented fields on properties
devmaslov May 20, 2020
ac99000
Implemented input type mapping from a class
devmaslov Jun 6, 2020
0bd8d39
Implemented @Field "for" for @Type
devmaslov Jun 7, 2020
38661b0
Implemented inputType for Field annotation
devmaslov Jun 8, 2020
4f3869f
Adjusted code for phpstan
devmaslov Jun 8, 2020
7e64775
Removed class attribute from Input annotation
devmaslov Jun 8, 2020
ef0f751
Fixed return type in mapPropertyType either for InputType or OutputType
devmaslov Jun 11, 2020
e97b684
Fixed the typo with description attribute on Input annotation
devmaslov Jun 13, 2020
93432f8
Fixed callable PHPDoc type in QueryFiledDescriptor
devmaslov Jun 13, 2020
0005802
Applying cs-fix
moufmouf Jun 26, 2020
bb69621
Adding a integration test to test @Field annotation in properties.
moufmouf Jun 26, 2020
ecc7386
Fixed the bug about breaking access to child fields
devmaslov Jun 27, 2020
b7df51b
Adjusted Field::description for output type
devmaslov Jun 27, 2020
8f22ee6
Applying cs fix
devmaslov Jun 27, 2020
31db01e
Merge branch 'master' into annotated-input-type
devmaslov Dec 12, 2020
0548019
Fixed Fields constructor param typehint
devmaslov Dec 12, 2020
794f6f3
Throw errors when field cannot be accessed
devmaslov Jan 23, 2021
0797af7
Added test for FailWith annotation used on property
devmaslov Jan 23, 2021
697a296
Added security tests on properties
devmaslov Jan 23, 2021
5a7b979
Added test for required param in getter
devmaslov Jan 23, 2021
540a36c
Added unit end to end tests for input annotations usage
devmaslov Jan 24, 2021
62d3f4a
Removed redundant "class" property from Input annotation
devmaslov Jan 24, 2021
5ac1d4d
Added "duplicated" exception for inputs and unit test for it
devmaslov Jan 24, 2021
b83ac16
Fixed inherited Fields via private properties and covered input type …
devmaslov Jan 27, 2021
68604fa
Added verification for non instantiable input classes
devmaslov Jan 30, 2021
42e449e
Added unit test for QueryFieldDescriptor for target property on source
devmaslov Jan 30, 2021
dc4605c
Added unit tests for InputType
devmaslov Jan 31, 2021
675cd02
Adjusted tests for lower PHP versions
devmaslov Jan 31, 2021
0f4026e
Skip adding 'Input' suffix for classes which names ends with it already
devmaslov Jan 31, 2021
b31f2f5
Stop using setter for input properties mentioned in the constructor
devmaslov Jan 31, 2021
2dcc430
Fixed return type for getClassConstructParameterNames()
devmaslov Jan 31, 2021
c976563
Added documentation for annotation references
devmaslov Jan 31, 2021
fecf4fb
Partially covered documentation for input annotation
devmaslov Jan 31, 2021
991d390
Added documentation for input type
devmaslov Feb 1, 2021
4c797fd
Force fields to be optional with "update" input
devmaslov Feb 2, 2021
2ca6f24
Attempting to change code coverage driver to see if this fixes core d…
moufmouf Feb 8, 2021
a3c1aec
adding XSD to PHPUnit file
moufmouf Feb 8, 2021
627516a
Fixing PHPStan issue
moufmouf Feb 8, 2021
8ab5cf3
Code style fixes
devmaslov Feb 11, 2021
8cdacaf
Fixed phpstan error ignoring for ReflectionMethod in PHP8
devmaslov Feb 11, 2021
0ff74b5
Updating GraphQLite to 4.2 release
moufmouf Mar 27, 2021
1860b20
Updating PHPStan version and checks
moufmouf Mar 27, 2021
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
2 changes: 1 addition & 1 deletion .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: "Install PHP with extensions"
uses: "shivammathur/setup-php@v2"
with:
coverage: "pcov"
coverage: "xdebug"
php-version: "${{ matrix.php-version }}"
tools: composer:v2

Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"phpunit/phpunit": "^8.2.4||^9.4",
"php-coveralls/php-coveralls": "^2.1",
"mouf/picotainer": "^1.1",
"phpstan/phpstan": "^0.12.25",
"phpstan/phpstan": "^0.12.82",
"beberlei/porpaginas": "^1.2",
"myclabs/php-enum": "^1.6.6",
"doctrine/coding-standard": "^8.2",
Expand Down Expand Up @@ -66,7 +66,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "4.1.x-dev"
"dev-master": "4.2.x-dev"
}
}
}
39 changes: 29 additions & 10 deletions docs/annotations_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,35 @@ name | see below | string | The targeted GraphQL output type.

One and only one of "class" and "name" parameter can be passed at the same time.

## @Input annotation

The `@Input` annotation is used to declare a GraphQL input type.

**Applies on**: classes.

Attribute | Compulsory | Type | Definition
---------------|------------|--------|--------
name | *no* | string | The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.
description | *no* | string | Description of the input type in the documentation. If not passed, PHP doc comment is used.
default | *no* | bool | Defaults to *true* if name is not specified. Whether the annotated PHP class should be mapped by default to this type.
update | *no* | bool | Determines if the the input represents a partial update. When set to *true* all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation.


## @Field annotation

The `@Field` annotation is used to declare a GraphQL field.

**Applies on**: methods of classes annotated with `@Type` or `@ExtendType`.
**Applies on**: methods or properties of classes annotated with `@Type`, `@ExtendType` or `@Input`.
When it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly
whether it's used for output type or input type. For example if property name is `foo` then getter should be `getFoo()` or setter should be `setFoo($foo)`. Setter can be omitted if property related to the field is present in the constructor with the same name.

Attribute | Compulsory | Type | Definition
---------------|------------|------|--------
name | *no* | string | The name of the field. If skipped, the name of the method is used instead.
[outputType](custom_types.md) | *no* | string | Forces the GraphQL output type of a query.
Attribute | Compulsory | Type | Definition
------------------------------|------------|---------------|--------
name | *no* | string | The name of the field. If skipped, the name of the method is used instead.
for | *no* | string, array | Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.
description | *no* | string | Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.
[outputType](custom_types.md) | *no* | string | Forces the GraphQL output type of a query.
[inputType](input_types.md) | *no* | string | Forces the GraphQL input type of a query.

## @SourceField annotation

Expand Down Expand Up @@ -100,15 +119,15 @@ annotations | *no* | array<Annotations> | A set of annotations that ap

The `@Logged` annotation is used to declare a Query/Mutation/Field is only visible to logged users.

**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field`.
**Applies on**: methods or properties annotated with `@Query`, `@Mutation` or `@Field`.

This annotation allows no attributes.

## @Right annotation

The `@Right` annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right.

**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field`.
**Applies on**: methods or properties annotated with `@Query`, `@Mutation` or `@Field`.

Attribute | Compulsory | Type | Definition
---------------|------------|------|--------
Expand All @@ -119,7 +138,7 @@ name | *yes* | string | The name of the right.
The `@FailWith` annotation is used to declare a default value to return in the user is not authorized to see a specific
query / mutation / field (according to the `@Logged` and `@Right` annotations).

**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field` and one of `@Logged` or `@Right` annotations.
**Applies on**: methods or properties annotated with `@Query`, `@Mutation` or `@Field` and one of `@Logged` or `@Right` annotations.

Attribute | Compulsory | Type | Definition
---------------|------------|------|--------
Expand All @@ -130,7 +149,7 @@ value | *yes* | mixed | The value to return if the user is not au
The `@HideIfUnauthorized` annotation is used to completely hide the query / mutation / field if the user is not authorized
to access it (according to the `@Logged` and `@Right` annotations).

**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field` and one of `@Logged` or `@Right` annotations.
**Applies on**: methods or properties annotated with `@Query`, `@Mutation` or `@Field` and one of `@Logged` or `@Right` annotations.

`@HideIfUnauthorized` and `@FailWith` are mutually exclusive.

Expand All @@ -152,7 +171,7 @@ It is very flexible: it allows you to pass an expression that can contains custo

See [the fine grained security page](fine-grained-security.md) for more details.

**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field`.
**Applies on**: methods or properties annotated with `@Query`, `@Mutation` or `@Field`.

Attribute | Compulsory | Type | Definition
---------------|------------|--------|--------
Expand Down
168 changes: 166 additions & 2 deletions docs/input_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ You are running into this error because GraphQLite does not know how to handle t

In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an **Input Type**.

In order to declare that type, in GraphQLite, we will declare a **Factory**.
There are two ways for declaring that type, in GraphQLite: using **Factory** or annotating the class with `@Input`.

## Factory

A **Factory** is a method that takes in parameter all the fields of the input type and return an object.

Expand Down Expand Up @@ -136,7 +138,7 @@ class MyFactory
and now, you can run query like this:

```
mutation {
query {
getCities(location: {
latitude: 45.0,
longitude: 0.0,
Expand Down Expand Up @@ -387,3 +389,165 @@ public function getProductById(string $id, bool $lazyLoad = true): Product
With the `@HideParameter` annotation, you can choose to remove from the GraphQL schema any argument.

To be able to hide an argument, the argument must have a default value.

## @Input Annotation

Let's transform `Location` class into an input type by adding `@Input` annotation to it and `@Field` annotation to corresponding properties:

<!--DOCUSAURUS_CODE_TABS-->
<!--PHP 8+-->
```php
#[Input]
class Location
{

#[Field]
private float $latitude;

#[Field]
private float $longitude;

public function __construct(float $latitude, float $longitude)
{
$this->latitude = $latitude;
$this->longitude = $longitude;
}

public function getLatitude(): float
{
return $this->latitude;
}

public function getLongitude(): float
{
return $this->longitude;
}
}
```
<!--PHP 7+-->
```php
/**
* @Input
*/
class Location
{

/**
* @Field
* @var float
*/
private $latitude;

/**
* @Field
* @var float
*/
private $longitude;

public function __construct(float $latitude, float $longitude)
{
$this->latitude = $latitude;
$this->longitude = $longitude;
}

public function getLatitude(): float
{
return $this->latitude;
}

public function getLongitude(): float
{
return $this->longitude;
}
}
```
<!--END_DOCUSAURUS_CODE_TABS-->

Now if you call `getCities()` query you can pass the location input in the same way as with factories.
The `Location` object will be automatically instantiated with provided `latitude` / `longitude` and passed to the controller as a parameter.

There are some important things to notice:

- `@Field` annotation is recognized only on properties for Input Type.
- There are 3 ways for fields to be resolved:
- Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above.
- If properties are public, they will be just set without any additional effort.
- For private or protected properties implemented public setter is required (if they are not set via constructor). For example `setLatitude(float $latitude)`.

### Multiple input types per one class

Simple usage of `@Input` annotation on a class creates an GraphQl input named by class name + "Input" suffix if a class name does not end with it already.
You can add multiple `@Input` annotations to the same class, give them different names and link different fields.
Consider the following example:

<!--DOCUSAURUS_CODE_TABS-->
<!--PHP 8+-->
```php
#[Input(name: 'CreateUserInput', default: true)]
#[Input(name: 'UpdateUserInput', update: true)]
class UserInput
{

#[Field]
public string $username;

#[Field(for: 'CreateUserInput')]
public string $email;

#[Field(for: 'CreateUserInput', inputType: 'String!')]
#[Field(for: 'UpdateUserInput', inputType: 'String')]
public string $password;

#[Field]
public ?int $age;
}
```
<!--PHP 7+-->
```php
/**
* @Input(name="CreateUserInput", default=true)
* @Input(name="UpdateUserInput", update=true)
*/
class UserInput
{

/**
* @Field()
* @var string
*/
public $username;

/**
* @Field(for="CreateUserInput")
* @var string
*/
public string $email;

/**
* @Field(for="CreateUserInput", inputType="String!")
* @Field(for="UpdateUserInput", inputType="String")
* @var string|null
*/
public $password;

/**
* @Field()
* @var int|null
*/
public $age;
}
```
<!--END_DOCUSAURUS_CODE_TABS-->

There are 2 input types created for just one class: `CreateUserInput` and `UpdateUserInput`. A few notes:
- `CreateUserInput` input will be used by default for this class.
- Field `username` is created for both input types, and it is required because the property type is not nullable.
- Field `email` will appear only for `CreateUserInput` input.
- Field `password` will appear for both. For `CreateUserInput` it'll be the required field and for `UpdateUserInput` optional.
- Field `age` is optional for both input types.

Note that `update: true` argument for `UpdateUserInput`. It should be used when input type is used for a partial update,
It makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.
In example above if you use the class as `UpdateUserInput` and set only `username` the other ones will be ignored.
In PHP 7 they will be set to `null`, while in PHP 8 they will be in not initialized state - this can be used as a trick
to check if user actually passed a value for a certain field.
12 changes: 8 additions & 4 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ parameters:
ignoreErrors:
- "#PHPDoc tag \\@throws with type Psr\\\\Container\\\\ContainerExceptionInterface is not subtype of Throwable#"
#- "#Property TheCodingMachine\\\\GraphQLite\\\\Types\\\\ResolvableInputObjectType::\\$resolve \\(array<int, object\\|string>&callable\\) does not accept array<int,object\\|string>#"
- "#Variable \\$prefetchRefMethod might not be defined.#"
#- "#Parameter \\#2 \\$type of class TheCodingMachine\\\\GraphQLite\\\\Parameters\\\\InputTypeParameter constructor expects GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type, GraphQL\\\\Type\\\\Definition\\\\InputType\\|GraphQL\\\\Type\\\\Definition\\\\Type given.#"
- "#Parameter .* of class ReflectionMethod constructor expects string, object\\|string given.#"
- "#Parameter .* of class ReflectionMethod constructor expects string(\\|null)?, object\\|string given.#"
-
message: '#Method TheCodingMachine\\GraphQLite\\Types\\Mutable(Interface|Object)Type::getFields\(\) should return array<GraphQL\\Type\\Definition\\FieldDefinition> but returns array\|float\|int#'
path: src/Types/MutableTrait.php
Expand Down Expand Up @@ -34,9 +33,14 @@ parameters:
message: '#Property TheCodingMachine\\GraphQLite\\Annotations\\Type::\$class \(class-string<object>\\|null\) does not accept string.#'
path: src/Annotations/Type.php
-
message: '#Method TheCodingMachine\\GraphQLite\\AnnotationReader::getMethodAnnotations\(\) should return array<int, T of object> but returns array<object>.#'
message: '#Method TheCodingMachine\\GraphQLite\\AnnotationReader::(getMethodAnnotations|getPropertyAnnotations)\(\) should return array<int, T of object> but returns array<object>.#'
path: src/AnnotationReader.php
-
message: '#Method TheCodingMachine\\GraphQLite\\AnnotationReader::getClassAnnotations\(\) should return array<A of object> but returns array<object>.#'
path: src/AnnotationReader.php
-
message: '#Parameter \#1 \$annotations of class TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations constructor expects array<int, TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotationInterface>, array<object> given.#'
path: src/AnnotationReader.php
- '#Call to an undefined method GraphQL\\Error\\ClientAware::getMessage\(\)#'


#-
Expand Down
37 changes: 20 additions & 17 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
Expand All @@ -10,21 +13,21 @@
stopOnFailure="false"
bootstrap="tests/Bootstrap.php"
>
<testsuites>
<testsuite name="GraphQLite Test Suite">
<directory>./tests/</directory>
<exclude>./tests/dependencies/</exclude>
<exclude>./tests/Bootstrap.php</exclude>
</testsuite>
</testsuites>
<testsuites>
<testsuite name="GraphQLite Test Suite">
<directory>./tests/</directory>
<exclude>./tests/dependencies/</exclude>
<exclude>./tests/Bootstrap.php</exclude>
</testsuite>
</testsuites>

<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="build/coverage"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="build/coverage"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
</phpunit>
Loading