diff --git a/website/docs/CHANGELOG.md b/website/docs/CHANGELOG.md index feb5972c91..fc3f57dba6 100644 --- a/website/docs/CHANGELOG.md +++ b/website/docs/CHANGELOG.md @@ -138,9 +138,9 @@ public function toGraphQLInputType(Type $type, ?InputType $subType, string $argu ### New features -- [@Input](annotations-reference.md#input-annotation) annotation is introduced as an alternative to `@Factory`. Now GraphQL input type can be created in the same manner as `@Type` in combination with `@Field` - [example](input-types.mdx#input-attribute). +- [@Input](annotations-reference.md#input-annotation) annotation is introduced as an alternative to `#[Factory]`. Now GraphQL input type can be created in the same manner as `#[Type]` in combination with `#[Field]` - [example](input-types.mdx#input-attribute). - New attributes has been added to [@Field](annotations-reference.md#field-annotation) annotation: `for`, `inputType` and `description`. -- The following annotations now can be applied to class properties directly: `@Field`, `@Logged`, `@Right`, `@FailWith`, `@HideIfUnauthorized` and `@Security`. +- The following annotations now can be applied to class properties directly: `#[Field]`, `#[Logged]`, `#[Right]`, `@FailWith`, `@HideIfUnauthorized` and `#[Security]`. ## 4.1.0 @@ -174,21 +174,21 @@ changed. ### New features -- You can directly [annotate a PHP interface with `@Type` to make it a GraphQL interface](inheritance-interfaces.mdx#mapping-interfaces) +- You can directly [annotate a PHP interface with `#[Type]` to make it a GraphQL interface](inheritance-interfaces.mdx#mapping-interfaces) - You can autowire services in resolvers, thanks to the new `@Autowire` annotation -- Added [user input validation](validation.mdx) (using the Symfony Validator or the Laravel validator or a custom `@Assertion` annotation +- Added [user input validation](validation.mdx) (using the Symfony Validator or the Laravel validator or a custom `#[Assertion]` annotation - Improved security handling: - Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3) - - Added fine-grained security using the `@Security` annotation. A field can now be [marked accessible or not depending on the context](fine-grained-security.mdx). + - Added fine-grained security using the `#[Security]` annotation. A field can now be [marked accessible or not depending on the context](fine-grained-security.mdx). For instance, you can restrict access to the field "viewsCount" of the type `BlogPost` only for post that the current user wrote. - - You can now inject the current logged user in any query / mutation / field using the `@InjectUser` annotation + - You can now inject the current logged user in any query / mutation / field using the `#[InjectUser]` annotation - Performance: - You can inject the [Webonyx query plan in a parameter from a resolver](query-plan.mdx) - You can use the [dataloader pattern to improve performance drastically via the "prefetchMethod" attribute](prefetch-method.mdx) - Customizable error handling has been added: - You can throw [many errors in one exception](error-handling.mdx#many-errors-for-one-exception) with `TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException` - You can force input types using `@UseInputType(for="$id", inputType="ID!")` -- You can extend an input types (just like you could extend an output type in v3) using [the new `@Decorate` annotation](extend-input-type.mdx) +- You can extend an input types (just like you could extend an output type in v3) using [the new `#[Decorate]` annotation](extend-input-type.mdx) - In a factory, you can [exclude some optional parameters from the GraphQL schema](input-types#ignoring-some-parameters) Many extension points have been added diff --git a/website/docs/README.mdx b/website/docs/README.mdx index 0293578109..0df34daa98 100644 --- a/website/docs/README.mdx +++ b/website/docs/README.mdx @@ -5,9 +5,6 @@ slug: / sidebar_label: GraphQLite --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -

GraphQLite logo

@@ -26,14 +23,6 @@ file uploads, security, validation, extendable types and more! First, declare a query in your controller: - - - ```php class ProductController { @@ -45,35 +34,8 @@ class ProductController } ``` - - - -```php -class ProductController -{ - /** - * @Query() - */ - public function product(string $id): Product - { - // Some code that looks for a product and returns it. - } -} -``` - - - - Then, annotate the `Product` class to declare what fields are exposed to the GraphQL API: - - - ```php #[Type] class Product @@ -87,29 +49,6 @@ class Product } ``` - - - -```php -/** - * @Type() - */ -class Product -{ - /** - * @Field() - */ - public function getName(): string - { - return $this->name; - } - // ... -} -``` - - - - That's it, you're good to go! Query and enjoy! ```graphql diff --git a/website/docs/annotations-reference.md b/website/docs/annotations-reference.md index e838cbf987..485b1f561c 100644 --- a/website/docs/annotations-reference.md +++ b/website/docs/annotations-reference.md @@ -1,15 +1,15 @@ --- id: annotations-reference -title: Annotations reference -sidebar_label: Annotations reference +title: Attributes reference +sidebar_label: Attributes reference --- -Note: all annotations are available both in a Doctrine annotation format (`@Query`) and in PHP 8 attribute format (`#[Query]`). +Note: all annotations are available in PHP 8 attribute format (`#[Query]`), support of Doctrine annotation format was dropped. See [Doctrine annotations vs PHP 8 attributes](doctrine-annotations-attributes.mdx) for more details. -## @Query +## #[Query] -The `@Query` annotation is used to declare a GraphQL query. +The `#[Query]` attribute is used to declare a GraphQL query. **Applies on**: controller methods. @@ -18,9 +18,9 @@ Attribute | Compulsory | Type | Definition name | *no* | string | The name of the query. If skipped, the name of the method is used instead. [outputType](custom-types.mdx) | *no* | string | Forces the GraphQL output type of a query. -## @Mutation +## #[Mutation] -The `@Mutation` annotation is used to declare a GraphQL mutation. +The `#[Mutation]` attribute is used to declare a GraphQL mutation. **Applies on**: controller methods. @@ -29,9 +29,9 @@ Attribute | Compulsory | Type | Definition name | *no* | string | The name of the mutation. If skipped, the name of the method is used instead. [outputType](custom-types.mdx) | *no* | string | Forces the GraphQL output type of a query. -## @Subscription +## #[Subscription] -The `@Subscription` annotation is used to declare a GraphQL subscription. +The `#[Subscription]` attribute is used to declare a GraphQL subscription. **Applies on**: controller methods. @@ -40,36 +40,36 @@ Attribute | Compulsory | Type | Definition name | *no* | string | The name of the subscription. If skipped, the name of the method is used instead. [outputType](custom-types.mdx) | *no* | string | Defines the GraphQL output type that will be sent for the subscription. -## @Type +## #[Type] -The `@Type` annotation is used to declare a GraphQL object type. This is used with standard output -types, as well as enum types. For input types, use the [@Input annotation](#input-annotation) directly on the input type or a [@Factory annoation](#factory-annotation) to make/return an input type. +The `#[Type]` attribute is used to declare a GraphQL object type. This is used with standard output +types, as well as enum types. For input types, use the [#[Input] attribute](#input-annotation) directly on the input type or a [#[Factory] attribute](#factory-annotation) to make/return an input type. **Applies on**: classes. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- -class | *no* | string | The targeted class/enum for the actual type. If no "class" attribute is passed, the type applies to the current class/enum. The current class/enum is assumed to be an entity (not service). If the "class" attribute *is passed*, [the class/enum annotated with `@Type` becomes a service](external-type-declaration.mdx). +class | *no* | string | The targeted class/enum for the actual type. If no "class" attribute is passed, the type applies to the current class/enum. The current class/enum is assumed to be an entity (not service). If the "class" attribute *is passed*, [the class/enum annotated with `#[Type]` becomes a service](external-type-declaration.mdx). name | *no* | string | The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed default | *no* | bool | Defaults to *true*. Whether the targeted PHP class should be mapped by default to this type. external | *no* | bool | Whether this is an [external type declaration](external-type-declaration.mdx) or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute. -## @ExtendType +## #[ExtendType] -The `@ExtendType` annotation is used to add fields to an existing GraphQL object type. +The `#[ExtendType]` attribute is used to add fields to an existing GraphQL object type. **Applies on**: classes. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- -class | see below | string | The targeted class. [The class annotated with `@ExtendType` is a service](extend-type.mdx). +class | see below | string | The targeted class. [The class annotated with `#[ExtendType]` is a service](extend-type.mdx). 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 +## #[Input] -The `@Input` annotation is used to declare a GraphQL input type. +The `#[Input]` attribute is used to declare a GraphQL input type. **Applies on**: classes. @@ -80,11 +80,11 @@ description | *no* | string | Description of the input type in the docu default | *no* | bool | Name of the input type represented in your GraphQL schema. Defaults to `true` *only if* the name is not specified. If `name` is specified, this will default to `false`, so must also be included for `true` when `name` is used. 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/subscription. This primarily applies to nullable fields. -## @Field +## #[Field] -The `@Field` annotation is used to declare a GraphQL field. +The `#[Field]` attribute is used to declare a GraphQL field. -**Applies on**: methods or properties of classes annotated with `@Type`, `@ExtendType` or `@Input`. +**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. @@ -96,11 +96,11 @@ description | *no* | string | Field description d [outputType](custom-types.mdx) | *no* | string | Forces the GraphQL output type of a query. [inputType](input-types.mdx) | *no* | string | Forces the GraphQL input type of a query. -## @SourceField +## #[SourceField] -The `@SourceField` annotation is used to declare a GraphQL field. +The `#[SourceField]` attribute is used to declare a GraphQL field. -**Applies on**: classes annotated with `@Type` or `@ExtendType`. +**Applies on**: classes annotated with `#[Type]` or `#[ExtendType]`. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- @@ -109,15 +109,15 @@ name | *yes* | string | The name of the field. phpType | *no* | string | The PHP type of the field (as you would write it in a Docblock) description | *no* | string | Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead. sourceName | *no* | string | The name of the property in the source class. If not set, the `name` will be used to get property value. -annotations | *no* | array\ | A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute) +annotations | *no* | array\ | A set of annotations that apply to this field. You would typically used a "#[Logged]" or "#[Right]" attribute as class here. **Note**: `outputType` and `phpType` are mutually exclusive. -## @MagicField +## #[MagicField] -The `@MagicField` annotation is used to declare a GraphQL field that originates from a PHP magic property (using `__get` magic method). +The `#[MagicField]` attribute is used to declare a GraphQL field that originates from a PHP magic property (using `__get` magic method). -**Applies on**: classes annotated with `@Type` or `@ExtendType`. +**Applies on**: classes annotated with `#[Type]` or `#[ExtendType]`. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- @@ -126,114 +126,114 @@ name | *yes* | string | The name of the field. phpType | *no*(*) | string | The PHP type of the field (as you would write it in a Docblock) description | *no* | string | Field description displayed in the GraphQL docs. If not set, no description will be shown. sourceName | *no* | string | The name of the property in the source class. If not set, the `name` will be used to get property value. -annotations | *no* | array\ | A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute) +annotations | *no* | array\ | A set of annotations that apply to this field. You would typically used a "#[Logged]" or "#[Right]" attribute as class here. (*) **Note**: `outputType` and `phpType` are mutually exclusive. You MUST provide one of them. -## @Prefetch +## #[Prefetch] Marks field parameter to be used for [prefetching](prefetch-method.mdx). -**Applies on**: parameters of methods annotated with `@Query`, `@Mutation` or `@Field`. +**Applies on**: parameters of methods annotated with `#[Query]`, `#[Mutation]` or `#[Field]`. Attribute | Compulsory | Type | Definition ------------------------------|------------|----------|-------- callable | *no* | callable | Name of the prefetch method (in same class) or a full callable, either a static method or regular service from the container -## @Logged +## #[Logged] -The `@Logged` annotation is used to declare a Query/Mutation/Field is only visible to logged users. +The `#[Logged]` attribute is used to declare a Query/Mutation/Field is only visible to logged users. -**Applies on**: methods or properties annotated with `@Query`, `@Mutation` or `@Field`. +**Applies on**: methods or properties annotated with `#[Query]`, `#[Mutation]` or `#[Field]`. -This annotation allows no attributes. +This attribute allows no arguments. -## @Right +## #[Right] -The `@Right` annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right. +The `#[Right]` attribute is used to declare a Query/Mutation/Field is only visible to users with a specific right. -**Applies on**: methods or properties annotated with `@Query`, `@Mutation` or `@Field`. +**Applies on**: methods or properties annotated with `#[Query]`, `#[Mutation]` or `#[Field]`. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- name | *yes* | string | The name of the right. -## @FailWith +## #[FailWith] -The `@FailWith` annotation is used to declare a default value to return in the user is not authorized to see a specific -query/mutation/subscription/field (according to the `@Logged` and `@Right` annotations). +The `#[FailWith]` attribute is used to declare a default value to return in the user is not authorized to see a specific +query/mutation/subscription/field (according to the `#[Logged]` and `#[Right]` attributes). -**Applies on**: methods or properties 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]` attributes. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- value | *yes* | mixed | The value to return if the user is not authorized. -## @HideIfUnauthorized +## #[HideIfUnauthorized] -
This annotation only works when a Schema is used to handle exactly one use request. +
This attribute only works when a Schema is used to handle exactly one use request. If you serve your GraphQL API from long-running standalone servers (like Laravel Octane, Swoole, RoadRunner etc) and -share the same Schema instance between multiple requests, please avoid using @HideIfUnauthorized.
+share the same Schema instance between multiple requests, please avoid using #[HideIfUnauthorized].
-The `@HideIfUnauthorized` annotation is used to completely hide the query/mutation/subscription/field if the user is not authorized -to access it (according to the `@Logged` and `@Right` annotations). +The `#[HideIfUnauthorized]` attribute is used to completely hide the query/mutation/subscription/field if the user is not authorized +to access it (according to the `#[Logged]` and `#[Right]` attributes). -**Applies on**: methods or properties 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]` attributes. -`@HideIfUnauthorized` and `@FailWith` are mutually exclusive. +`#[HideIfUnauthorized]` and `#[FailWith]` are mutually exclusive. -## @InjectUser +## #[InjectUser] -Use the `@InjectUser` annotation to inject an instance of the current user logged in into a parameter of your +Use the `#[InjectUser]` attribute to inject an instance of the current user logged in into a parameter of your query/mutation/subscription/field. See [the authentication and authorization page](authentication-authorization.mdx) for more details. -**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field`. +**Applies on**: methods annotated with `#[Query]`, `#[Mutation]` or `#[Field]`. Attribute | Compulsory | Type | Definition ---------------|------------|--------|-------- *for* | *yes* | string | The name of the PHP parameter -## @Security +## #[Security] -The `@Security` annotation can be used to check fin-grained access rights. +The `#[Security]` attribute can be used to check fin-grained access rights. It is very flexible: it allows you to pass an expression that can contains custom logic. See [the fine grained security page](fine-grained-security.mdx) for more details. -**Applies on**: methods or properties annotated with `@Query`, `@Mutation` or `@Field`. +**Applies on**: methods or properties annotated with `#[Query]`, `#[Mutation]` or `#[Field]`. Attribute | Compulsory | Type | Definition ---------------|------------|--------|-------- *default* | *yes* | string | The security expression -## @Factory +## #[Factory] -The `@Factory` annotation is used to declare a factory that turns GraphQL input types into objects. +The `#[Factory]` attribute is used to declare a factory that turns GraphQL input types into objects. **Applies on**: methods from classes in the "types" namespace. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- name | *no* | string | The name of the input type. If skipped, the name of class returned by the factory is used instead. -default | *no* | bool | If `true`, this factory will be used by default for its PHP return type. If set to `false`, you must explicitly [reference this factory using the `@Parameter` annotation](input-types.mdx#declaring-several-input-types-for-the-same-php-class). +default | *no* | bool | If `true`, this factory will be used by default for its PHP return type. If set to `false`, you must explicitly [reference this factory using the `#[Parameter]` attribute](input-types.mdx#declaring-several-input-types-for-the-same-php-class). -## @UseInputType +## #[UseInputType] Used to override the GraphQL input type of a PHP parameter. -**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field` annotation. +**Applies on**: methods annotated with `#[Query]`, `#[Mutation]` or `#[Field]` attribute. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- *for* | *yes* | string | The name of the PHP parameter *inputType* | *yes* | string | The GraphQL input type to force for this input field -## @Decorate +## #[Decorate] -The `@Decorate` annotation is used [to extend/modify/decorate an input type declared with the `@Factory` annotation](extend-input-type.mdx). +The `#[Decorate]` attribute is used [to extend/modify/decorate an input type declared with the `#[Factory]` attribute](extend-input-type.mdx). **Applies on**: methods from classes in the "types" namespace. @@ -241,20 +241,20 @@ Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- name | *yes* | string | The GraphQL input type name extended by this decorator. -## @Autowire +## #[Autowire] [Resolves a PHP parameter from the container](autowiring.mdx). -Useful to inject services directly into `@Field` method arguments. +Useful to inject services directly into `#[Field]` method arguments. -**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field` annotation. +**Applies on**: methods annotated with `#[Query]`, `#[Mutation]` or `#[Field]` attribute. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- *for* | *yes* | string | The name of the PHP parameter *identifier* | *no* | string | The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern. -## @HideParameter +## #[HideParameter] Removes [an argument from the GraphQL schema](input-types.mdx#ignoring-some-parameters). @@ -262,7 +262,7 @@ Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- *for* | *yes* | string | The name of the PHP parameter to hide -## @Cost +## #[Cost] Sets complexity and multipliers on fields for [automatic query complexity](operation-complexity.md#static-request-analysis). @@ -272,13 +272,13 @@ Attribute | Compulsory | Type | Definition *multipliers* | *no* | array\ | Names of fields by value of which complexity will be multiplied *defaultMultiplier* | *no* | int | Default multiplier value if all multipliers are missing/null -## @Validate +## #[Validate] -
This annotation is only available in the GraphQLite Laravel package
+
This attribute is only available in the GraphQLite Laravel package
[Validates a user input in Laravel](laravel-package-advanced.mdx). -**Applies on**: methods annotated with `@Query`, `@Mutation`, `@Field`, `@Factory` or `@Decorator` annotation. +**Applies on**: methods annotated with `#[Query]`, `#[Mutation]`, `#[Field]`, `#[Factory]` or `#[Decorator]` attribute. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- @@ -288,26 +288,26 @@ Attribute | Compulsory | Type | Definition Sample: ```php -@Validate(for="$email", rule="email|unique:users") +#[Validate(for: "$email", rule: "email|unique:users")] ``` -## @Assertion +## #[Assertion] [Validates a user input](validation.mdx). -The `@Assertion` annotation is available in the *thecodingmachine/graphqlite-symfony-validator-bridge* third party package. +The `#[Assertion]` attribute is available in the *thecodingmachine/graphqlite-symfony-validator-bridge* third party package. It is available out of the box if you use the Symfony bundle. -**Applies on**: methods annotated with `@Query`, `@Mutation`, `@Field`, `@Factory` or `@Decorator` annotation. +**Applies on**: methods annotated with `#[Query]`, `#[Mutation]`, `#[Field]`, `#[Factory]` or `#[Decorator]` attribute. Attribute | Compulsory | Type | Definition ---------------|------------|------|-------- *for* | *yes* | string | The name of the PHP parameter -*constraint* | *yes | annotation | One (or many) Symfony validation annotations. +*constraint* | *yes | annotation | One (or many) Symfony validation attributes. ## ~~@EnumType~~ -*Deprecated: Use [PHP 8.1's native Enums](https://www.php.net/manual/en/language.types.enumerations.php) instead with a [@Type](#type-annotation).* +*Deprecated: Use [PHP 8.1's native Enums](https://www.php.net/manual/en/language.types.enumerations.php) instead with a [#[Type]](#type-annotation).* The `@EnumType` annotation is used to change the name of a "Enum" type. Note that if you do not want to change the name, the annotation is optionnal. Any object extending `MyCLabs\Enum\Enum` diff --git a/website/docs/argument-resolving.md b/website/docs/argument-resolving.md index 35556c66d0..8125964dae 100644 --- a/website/docs/argument-resolving.md +++ b/website/docs/argument-resolving.md @@ -24,8 +24,8 @@ As an example, GraphQLite uses *parameter middlewares* internally to: In the query above, the `$info` argument is filled with the Webonyx `ResolveInfo` class thanks to the [`ResolveInfoParameterHandler parameter middleware`](https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php) -- Inject a service from the container when you use the `@Autowire` annotation -- Perform validation with the `@Validate` annotation (in Laravel package) +- Inject a service from the container when you use the `#[Autowire]` attribute +- Perform validation with the `#[Validate]` attribute (in Laravel package) @@ -54,23 +54,22 @@ interface ParameterMiddlewareInterface Then, resolution actually happen by executing the resolver (this is the second pass). -## Annotations parsing +## Attributes parsing -If you plan to use annotations while resolving arguments, your annotation should extend the [`ParameterAnnotationInterface`](https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php) +If you plan to use attributes while resolving arguments, your attribute class should extend the [`ParameterAnnotationInterface`](https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php) -For instance, if we want GraphQLite to inject a service in an argument, we can use `@Autowire(for="myService")`. +For instance, if we want GraphQLite to inject a service in an argument, we can use `#[Autowire]`. -For PHP 8 attributes, we only need to put declare the annotation can target parameters: `#[Attribute(Attribute::TARGET_PARAMETER)]`. +We only need to put declare the annotation can target parameters: `#[Attribute(Attribute::TARGET_PARAMETER)]`. -The annotation looks like this: +The class looks like this: ```php use Attribute; /** - * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation. + * Use this attribute to autowire a service from the container into a given parameter of a field/query/mutation. * - * @Annotation */ #[Attribute(Attribute::TARGET_PARAMETER)] class Autowire implements ParameterAnnotationInterface diff --git a/website/docs/authentication-authorization.mdx b/website/docs/authentication-authorization.mdx index f716d02d30..9842e2a3d3 100644 --- a/website/docs/authentication-authorization.mdx +++ b/website/docs/authentication-authorization.mdx @@ -4,18 +4,15 @@ title: Authentication and authorization sidebar_label: Authentication and authorization --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations/subscriptions or fields reserved to some users. GraphQLite offers some control over what a user can do with your API. You can restrict access to resources: -- based on authentication using the [`@Logged` annotation](#logged-and-right-annotations) (restrict access to logged users) -- based on authorization using the [`@Right` annotation](#logged-and-right-annotations) (restrict access to logged users with certain rights). -- based on fine-grained authorization using the [`@Security` annotation](fine-grained-security.mdx) (restrict access for some given resources to some users). +- based on authentication using the [`#[Logged]` attribute](#logged-and-right-annotations) (restrict access to logged users) +- based on authorization using the [`#[Right]` attribute](#logged-and-right-annotations) (restrict access to logged users with certain rights). +- based on fine-grained authorization using the [`#[Security]` attribute](fine-grained-security.mdx) (restrict access for some given resources to some users).
GraphQLite does not have its own security mechanism. @@ -23,16 +20,9 @@ resources: See Connecting GraphQLite to your framework's security module.
-## `@Logged` and `@Right` annotations +## `#[Logged]` and `#[Right]` attributes -GraphQLite exposes two annotations (`@Logged` and `@Right`) that you can use to restrict access to a resource. - - +GraphQLite exposes two attributes (`#[Logged]` and `#[Right]`) that you can use to restrict access to a resource. ```php namespace App\Controller; @@ -56,43 +46,14 @@ class UserController } ``` - - - -```php -namespace App\Controller; - -use TheCodingMachine\GraphQLite\Annotations\Query; -use TheCodingMachine\GraphQLite\Annotations\Logged; -use TheCodingMachine\GraphQLite\Annotations\Right; - -class UserController -{ - /** - * @Query - * @Logged - * @Right("CAN_VIEW_USER_LIST") - * @return User[] - */ - public function users(int $limit, int $offset): array - { - // ... - } -} -``` - - - - - In the example above, the query `users` will only be available if the user making the query is logged AND if he has the `CAN_VIEW_USER_LIST` right. -`@Logged` and `@Right` annotations can be used next to: +`#[Logged]` and `#[Right]` attributes can be used next to: -* `@Query` annotations -* `@Mutation` annotations -* `@Field` annotations +* `#[Query]` attributes +* `#[Mutation]` attributes +* `#[Field]` attributes
By default, if a user tries to access an unauthorized query/mutation/subscription/field, an error is @@ -102,17 +63,9 @@ has the `CAN_VIEW_USER_LIST` right. ## Not throwing errors If you do not want an error to be thrown when a user attempts to query a field/query/mutation/subscription -they have no access to, you can use the `@FailWith` annotation. - -The `@FailWith` annotation contains the value that will be returned for users with insufficient rights. +they have no access to, you can use the `#[FailWith]` attribute. - - +The `#[FailWith]` attribute contains the value that will be returned for users with insufficient rights. ```php class UserController @@ -134,43 +87,9 @@ class UserController } ``` - - - -```php -class UserController -{ - /** - * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST", - * the value returned will be "null". - * - * @Query - * @Logged - * @Right("CAN_VIEW_USER_LIST") - * @FailWith(null) - * @return User[] - */ - public function users(int $limit, int $offset): array - { - // ... - } -} -``` - - - - ## Injecting the current user as a parameter -Use the `@InjectUser` annotation to get an instance of the current user logged in. - - - +Use the `#[InjectUser]` attribute to get an instance of the current user logged in. ```php namespace App\Controller; @@ -181,9 +100,9 @@ use TheCodingMachine\GraphQLite\Annotations\InjectUser; class ProductController { /** - * @Query * @return Product */ + #[Query] public function product( int $id, #[InjectUser] @@ -195,41 +114,15 @@ class ProductController } ``` - - - -```php -namespace App\Controller; - -use TheCodingMachine\GraphQLite\Annotations\Query; -use TheCodingMachine\GraphQLite\Annotations\InjectUser; - -class ProductController -{ - /** - * @Query - * @InjectUser(for="$user") - * @return Product - */ - public function product(int $id, User $user): Product - { - // ... - } -} -``` - - - - -The `@InjectUser` annotation can be used next to: +The `#[InjectUser]` attribute can be used next to: -* `@Query` annotations -* `@Mutation` annotations -* `@Field` annotations +* `#[Query]` attributes +* `#[Mutation]` attributes +* `#[Field]` attributes The object injected as the current user depends on your framework. It is in fact the object returned by the ["authentication service" configured in GraphQLite](implementing-security.md). If user is not authenticated and -parameter's type is not nullable, an authorization exception is thrown, similar to `@Logged` annotation. +parameter's type is not nullable, an authorization exception is thrown, similar to `#[Logged]` attribute. ## Hiding fields / queries / mutations / subscriptions @@ -237,15 +130,7 @@ By default, a user analysing the GraphQL schema can see all queries/mutations/su Some will be available to him and some won't. If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users), -you can use the `@HideIfUnauthorized` annotation. Beware of [it's limitations](annotations-reference.md). - - - +you can use the `#[HideIfUnauthorized]` attribute. Beware of [it's limitations](annotations-reference.md). ```php class UserController @@ -268,33 +153,6 @@ class UserController } ``` - - - -```php -class UserController -{ - /** - * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST", - * the schema will NOT contain the "users" query at all (so trying to call the - * "users" query will result in a GraphQL "query not found" error. - * - * @Query - * @Logged - * @Right("CAN_VIEW_USER_LIST") - * @HideIfUnauthorized() - * @return User[] - */ - public function users(int $limit, int $offset): array - { - // ... - } -} -``` - - - - While this is the most secured mode, it can have drawbacks when working with development tools (you need to be logged as admin to fetch the complete schema). diff --git a/website/docs/autowiring.mdx b/website/docs/autowiring.mdx index c38bb73d77..825241afe0 100644 --- a/website/docs/autowiring.mdx +++ b/website/docs/autowiring.mdx @@ -4,14 +4,11 @@ title: Autowiring services sidebar_label: Autowiring services --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - GraphQLite can automatically inject services in your fields/queries/mutations signatures. Some of your fields may be computed. In order to compute these fields, you might need to call a service. -Most of the time, your `@Type` annotation will be put on a model. And models do not have access to services. +Most of the time, your `#[Type]` attribute will be put on a model. And models do not have access to services. Hopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with the service instance. @@ -20,14 +17,6 @@ the service instance. Let's assume you are running an international store. You have a `Product` class. Each product has many names (depending on the language of the user). - - - ```php namespace App\Entities; @@ -53,39 +42,6 @@ class Product } ``` - - - -```php -namespace App\Entities; - -use TheCodingMachine\GraphQLite\Annotations\Autowire; -use TheCodingMachine\GraphQLite\Annotations\Field; -use TheCodingMachine\GraphQLite\Annotations\Type; - -use Symfony\Component\Translation\TranslatorInterface; - -/** - * @Type() - */ -class Product -{ - // ... - - /** - * @Field() - * @Autowire(for="$translator") - */ - public function getName(TranslatorInterface $translator): string - { - return $translator->trans('product_name_'.$this->id); - } -} -``` - - - - When GraphQLite queries the name, it will automatically fetch the translator service.
As with most autowiring solutions, GraphQLite assumes that the service identifier @@ -130,30 +86,10 @@ By type-hinting against an interface, your code remains testable and is decouple Optionally, you can specify the identifier of the service you want to fetch from the controller: - - - ```php #[Autowire(identifier: "translator")] ``` - - - -```php -/** - * @Autowire(for="$translator", identifier="translator") - */ -``` - - - -
While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is highly discouraged. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an diff --git a/website/docs/custom-types.mdx b/website/docs/custom-types.mdx index 800ab2846c..527e91623c 100644 --- a/website/docs/custom-types.mdx +++ b/website/docs/custom-types.mdx @@ -4,21 +4,10 @@ title: Custom types sidebar_label: Custom types --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite. For instance: - - - ```php #[Type(class: Product::class)] class ProductType @@ -31,28 +20,6 @@ class ProductType } ``` - - - -```php -/** - * @Type(class=Product::class) - */ -class ProductType -{ - /** - * @Field - */ - public function getId(Product $source): string - { - return $source->getId(); - } -} -``` - - - - In the example above, GraphQLite will generate a GraphQL schema with a field `id` of type `string`: ```graphql @@ -66,42 +33,22 @@ is an `ID` or not. You can help GraphQLite by manually specifying the output type to use: - - - ```php #[Field(outputType: "ID")] ``` - - - -```php - /** - * @Field(name="id", outputType="ID") - */ -``` - - - - ## Usage The `outputType` attribute will map the return value of the method to the output type passed in parameter. You can use the `outputType` attribute in the following annotations: -* `@Query` -* `@Mutation` -* `@Subscription` -* `@Field` -* `@SourceField` -* `@MagicField` +* `#[Query]` +* `#[Mutation]` +* `#[Subscription]` +* `#[Field]` +* `#[SourceField]` +* `#[MagicField]` ## Registering a custom output type (advanced) diff --git a/website/docs/doctrine-annotations-attributes.mdx b/website/docs/doctrine-annotations-attributes.mdx index 72725a39b2..058c276a95 100644 --- a/website/docs/doctrine-annotations-attributes.mdx +++ b/website/docs/doctrine-annotations-attributes.mdx @@ -4,48 +4,12 @@ title: Doctrine annotations VS PHP8 attributes sidebar_label: Annotations VS Attributes --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+). ## Doctrine annotations -
- Deprecated! Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in a future release. -
- -Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support. This was the purpose of the [doctrine/annotation](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html) library. - -Using Doctrine annotations, you write annotations in your docblocks: - -```php -use TheCodingMachine\GraphQLite\Annotations\Type; - -/** - * @Type - */ -class MyType -{ -} -``` - -Please note that: - -- The annotation is added in a **docblock** (a comment starting with "`/**`") -- The `Type` part is actually a class. It must be declared in the `use` statements at the top of your file. - - -
- Heads up! -

Some IDEs provide support for Doctrine annotations:

- - - We strongly recommend using an IDE that has Doctrine annotations support. +
+ Unsupported! Doctrine annotations are replaced in favor of native PHP 8 attributes.
## PHP 8 attributes @@ -71,19 +35,10 @@ They support the same attributes too. A few notable differences: -- PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the `annotations` attribute of `@MagicField` and `@SourceField`. - PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level. Let's take an example with the [`#Autowire` attribute](autowiring.mdx): - - - ```php #[Field] public function getProduct(#[Autowire] ProductRepository $productRepository) : Product { @@ -91,23 +46,6 @@ public function getProduct(#[Autowire] ProductRepository $productRepository) : P } ``` - - - -```php -/** - * @Field - * @Autowire(for="$productRepository") - */ -public function getProduct(ProductRepository $productRepository) : Product { - //... -} -``` - - - - - ## Migrating from Doctrine annotations to PHP 8 attributes The good news is that you can easily migrate from Doctrine annotations to PHP 8 attributes using the amazing, [Rector library](https://github.com/rectorphp/rector). To do so, you'll want to use the following rector configuration: diff --git a/website/docs/error-handling.mdx b/website/docs/error-handling.mdx index 231f07db72..549a9c15c9 100644 --- a/website/docs/error-handling.mdx +++ b/website/docs/error-handling.mdx @@ -4,9 +4,6 @@ title: Error handling sidebar_label: Error handling --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - In GraphQL, when an error occurs, the server must add an "error" entry in the response. ```json @@ -142,14 +139,6 @@ throw only one exception. If you want to display several exceptions, you can bundle these exceptions in a `GraphQLAggregateException` that you can throw. - - - ```php use TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException; @@ -171,35 +160,6 @@ public function createProduct(string $name, float $price): Product } ``` - - - -```php -use TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException; - -/** - * @Query - */ -public function createProduct(string $name, float $price): Product -{ - $exceptions = new GraphQLAggregateException(); - - if ($name === '') { - $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION')); - } - if ($price <= 0) { - $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION')); - } - - if ($exceptions->hasExceptions()) { - throw $exceptions; - } -} -``` - - - - ## Webonyx exceptions GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can diff --git a/website/docs/extend-input-type.mdx b/website/docs/extend-input-type.mdx index 767856e32f..abe2818dd9 100644 --- a/website/docs/extend-input-type.mdx +++ b/website/docs/extend-input-type.mdx @@ -4,36 +4,25 @@ title: Extending an input type sidebar_label: Extending an input type --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - Available in GraphQLite 4.0+ -
If you are not familiar with the @Factory tag, read first the "input types" guide.
+
If you are not familiar with the #[Factory] tag, read first the "input types" guide.
Fields exposed in a GraphQL input type do not need to be all part of the factory method. -Just like with output type (that can be [extended using the `ExtendType` annotation](extend-type.mdx)), you can extend/modify -an input type using the `@Decorate` annotation. +Just like with output type (that can be [extended using the `ExtendType` attribute](extend-type.mdx)), you can extend/modify +an input type using the `#[Decorate]` attribute. -Use the `@Decorate` annotation to add additional fields to an input type that is already declared by a `@Factory` annotation, +Use the `#[Decorate]` attribute to add additional fields to an input type that is already declared by a `#[Factory]` attribute, or to modify the returned object.
- The @Decorate annotation is very useful in scenarios where you cannot touch the @Factory method. - This can happen if the @Factory method is defined in a third-party library or if the @Factory method is part + The #[Decorate] attribute is very useful in scenarios where you cannot touch the #[Factory] method. + This can happen if the #[Factory] method is defined in a third-party library or if the #[Factory] method is part of auto-generated code.
-Let's assume you have a `Filter` class used as an input type. You most certainly have a `@Factory` to create the input type. - - - +Let's assume you have a `Filter` class used as an input type. You most certainly have a `#[Factory]` to create the input type. ```php class MyFactory @@ -49,39 +38,9 @@ class MyFactory } ``` - - - -```php -class MyFactory -{ - /** - * @Factory() - */ - public function createFilter(string $name): Filter - { - // Let's assume you have a flexible 'Filter' class that can accept any kind of filter - $filter = new Filter(); - $filter->addFilter('name', $name); - return $filter; - } -} -``` - - - - Assuming you **cannot** modify the code of this factory, you can still modify the GraphQL input type generated by adding a "decorator" around the factory. - - - ```php class MyDecorator { @@ -94,26 +53,6 @@ class MyDecorator } ``` - - - -```php -class MyDecorator -{ - /** - * @Decorate(inputTypeName="FilterInput") - */ - public function addTypeFilter(Filter $filter, string $type): Filter - { - $filter->addFilter('type', $type); - return $filter; - } -} -``` - - - - In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type. A few things to notice: @@ -121,8 +60,8 @@ A few things to notice: - The decorator takes the object generated by the factory as first argument - The decorator MUST return an object of the same type (or a sub-type) - The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type. -- The `@Decorate` annotation must contain a `inputTypeName` attribute that contains the name of the GraphQL input type - that is decorated. If you did not specify this name in the `@Factory` annotation, this is by default the name of the +- The `#[Decorate]` attribute must contain a `inputTypeName` attribute that contains the name of the GraphQL input type + that is decorated. If you did not specify this name in the `#[Factory]` attribute, this is by default the name of the PHP class + "Input" (for instance: "Filter" => "FilterInput") diff --git a/website/docs/extend-type.mdx b/website/docs/extend-type.mdx index c8efb8d920..44d5ac9327 100644 --- a/website/docs/extend-type.mdx +++ b/website/docs/extend-type.mdx @@ -4,12 +4,10 @@ title: Extending a type sidebar_label: Extending a type --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; Fields exposed in a GraphQL type do not need to be all part of the same class. -Use the `@ExtendType` annotation to add additional fields to a type that is already declared. +Use the `#[ExtendType]` attribute to add additional fields to a type that is already declared.
Extending a type has nothing to do with type inheritance. @@ -20,14 +18,6 @@ Use the `@ExtendType` annotation to add additional fields to a type that is alre Let's assume you have a `Product` class. In order to get the name of a product, there is no `getName()` method in the product because the name needs to be translated in the correct language. You have a `TranslationService` to do that. - - - ```php namespace App\Entities; @@ -53,57 +43,12 @@ class Product } ``` - - - -```php -namespace App\Entities; - -use TheCodingMachine\GraphQLite\Annotations\Field; -use TheCodingMachine\GraphQLite\Annotations\Type; - -/** - * @Type() - */ -class Product -{ - // ... - - /** - * @Field() - */ - public function getId(): string - { - return $this->id; - } - - /** - * @Field() - */ - public function getPrice(): ?float - { - return $this->price; - } -} -``` - - - - ```php // You need to use a service to get the name of the product in the correct language. $name = $translationService->getProductName($productId, $language); ``` -Using `@ExtendType`, you can add an additional `name` field to your product: - - - +Using `#[ExtendType]`, you can add an additional `name` field to your product: ```php namespace App\Types; @@ -130,68 +75,13 @@ class ProductType } ``` - - - -```php -namespace App\Types; - -use TheCodingMachine\GraphQLite\Annotations\ExtendType; -use TheCodingMachine\GraphQLite\Annotations\Field; -use App\Entities\Product; - -/** - * @ExtendType(class=Product::class) - */ -class ProductType -{ - private $translationService; - - public function __construct(TranslationServiceInterface $translationService) - { - $this->translationService = $translationService; - } - - /** - * @Field() - */ - public function getName(Product $product, string $language): string - { - return $this->translationService->getProductName($product->getId(), $language); - } -} -``` - - - - Let's break this sample: - - - ```php #[ExtendType(class: Product::class)] ``` - - - -```php -/** - * @ExtendType(class=Product::class) - */ -``` - - - - -With the `@ExtendType` annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to +With the `#[ExtendType]` attribute, we tell GraphQLite that we want to add fields in the GraphQL type mapped to the `Product` PHP class. ```php @@ -218,14 +108,6 @@ If you are using the Symfony bundle (or a framework with autowiring like Laravel is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it.
- - - ```php #[Field] public function getName(Product $product, string $language): string @@ -234,23 +116,7 @@ public function getName(Product $product, string $language): string } ``` - - - -```php -/** - * @Field() - */ -public function getName(Product $product, string $language): string -{ - return $this->translationService->getProductName($product->getId(), $language); -} -``` - - - - -The `@Field` annotation is used to add the "name" field to the `Product` type. +The `#[Field]` attribute is used to add the "name" field to the `Product` type. Take a close look at the signature. The first parameter is the "resolved object" we are working on. Any additional parameters are used as arguments. diff --git a/website/docs/external-type-declaration.mdx b/website/docs/external-type-declaration.mdx index d11cd9ad77..428fad7f8e 100644 --- a/website/docs/external-type-declaration.mdx +++ b/website/docs/external-type-declaration.mdx @@ -4,28 +4,17 @@ title: External type declaration sidebar_label: External type declaration --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -In some cases, you cannot or do not want to put an annotation on a domain class. +In some cases, you cannot or do not want to put an attribute on a domain class. For instance: * The class you want to annotate is part of a third party library and you cannot modify it -* You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer +* You are doing domain-driven design and don't want to clutter your domain object with attributes from the view layer * etc. -## `@Type` annotation with the `class` attribute - -GraphQLite allows you to use a *proxy* class thanks to the `@Type` annotation with the `class` attribute: +## `#[Type]` attribute with the `class` attribute - - +GraphQLite allows you to use a *proxy* class thanks to the `#[Type]` attribute with the `class` attribute: ```php namespace App\Types; @@ -45,34 +34,6 @@ class ProductType } ``` - - - -```php -namespace App\Types; - -use TheCodingMachine\GraphQLite\Annotations\Type; -use TheCodingMachine\GraphQLite\Annotations\Field; -use App\Entities\Product; - -/** - * @Type(class=Product::class) - */ -class ProductType -{ - /** - * @Field() - */ - public function getId(Product $product): string - { - return $product->getId(); - } -} -``` - - - - The `ProductType` class must be in the *types* namespace. You configured this namespace when you installed GraphQLite. The `ProductType` class is actually a **service**. You can therefore inject dependencies in it. @@ -82,19 +43,11 @@ If you are using the Symfony bundle (or a framework with autowiring like Laravel is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it.
-In methods with a `@Field` annotation, the first parameter is the *resolved object* we are working on. Any additional parameters are used as arguments. - -## `@SourceField` annotation +In methods with a `#[Field]` attribute, the first parameter is the *resolved object* we are working on. Any additional parameters are used as arguments. -If you don't want to rewrite all *getters* of your base class, you may use the `@SourceField` annotation: +## `#[SourceField]` attribute - - +If you don't want to rewrite all *getters* of your base class, you may use the `#[SourceField]` attribute: ```php use TheCodingMachine\GraphQLite\Annotations\Type; @@ -109,43 +62,14 @@ class ProductType } ``` - - - -```php -use TheCodingMachine\GraphQLite\Annotations\Type; -use TheCodingMachine\GraphQLite\Annotations\SourceField; -use App\Entities\Product; - -/** - * @Type(class=Product::class) - * @SourceField(name="name") - * @SourceField(name="price") - */ -class ProductType -{ -} -``` - - - - By doing so, you let GraphQLite know that the type exposes the `getName` method of the underlying `Product` object. Internally, GraphQLite will look for methods named `name()`, `getName()` and `isName()`). You can set different name to look for with `sourceName` attribute. -## `@MagicField` annotation - -If your object has no getters, but instead uses magic properties (using the magic `__get` method), you should use the `@MagicField` annotation: +## `#[MagicField]` attribute - - +If your object has no getters, but instead uses magic properties (using the magic `__get` method), you should use the `#[MagicField]` attribute: ```php use TheCodingMachine\GraphQLite\Annotations\Type; @@ -163,30 +87,6 @@ class ProductType } ``` - - - -```php -use TheCodingMachine\GraphQLite\Annotations\Type; -use TheCodingMachine\GraphQLite\Annotations\SourceField; -use App\Entities\Product; - -/** - * @Type() - * @MagicField(name="name", outputType="String!") - * @MagicField(name="price", outputType="Float") - */ -class ProductType -{ - public function __get(string $property) { - // return some magic property - } -} -``` - - - - By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying `Product` object. You can set different name to look for with `sourceName` attribute. @@ -197,7 +97,7 @@ of each property manually. ### Authentication and authorization -You may also check for logged users or users with a specific right using the "annotations" property. +You may also check for logged users or users with a specific right using the "annotations" argument. ```php use TheCodingMachine\GraphQLite\Annotations\Type; @@ -207,32 +107,20 @@ use TheCodingMachine\GraphQLite\Annotations\Right; use TheCodingMachine\GraphQLite\Annotations\FailWith; use App\Entities\Product; -/** - * @Type(class=Product::class) - * @SourceField(name="name") - * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)})) - */ +#[Type(class: Product::class)] +#[SourceField(name: "name")] +#[SourceField(name: "price", annotations: [new Logged(), new Right("CAN_ACCESS_Price"), new FailWith(null)])] class ProductType extends AbstractAnnotatedObjectType { } ``` -Any annotations described in the [Authentication and authorization page](authentication-authorization.mdx), or any annotation this is actually a ["field middleware"](field-middlewares.md) can be used in the `@SourceField` "annotations" attribute. - -
Heads up! The "annotation" attribute in @SourceField and @MagicField is only available as a Doctrine annotations. You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)
+Any attributes described in the [Authentication and authorization page](authentication-authorization.mdx), or any attribute this is actually a ["field middleware"](field-middlewares.md) can be used in the `#[SourceField]` "annotations" argument. -## Declaring fields dynamically (without annotations) +## Declaring fields dynamically (without attributes) -In some very particular cases, you might not know exactly the list of `@SourceField` annotations at development time. -If you need to decide the list of `@SourceField` at runtime, you can implement the `FromSourceFieldsInterface`: - - - +In some very particular cases, you might not know exactly the list of `#[SourceField]` attributes at development time. +If you need to decide the list of `#[SourceField]` at runtime, you can implement the `FromSourceFieldsInterface`: ```php use TheCodingMachine\GraphQLite\FromSourceFieldsInterface; @@ -251,38 +139,7 @@ class ProductType implements FromSourceFieldsInterface // You may want to enable fields conditionally based on feature flags... if (ENABLE_STATUS_GLOBALLY) { return [ - new SourceField(['name'=>'status', 'logged'=>true]), - ]; - } else { - return []; - } - } -} -``` - - - - -```php -use TheCodingMachine\GraphQLite\FromSourceFieldsInterface; - -/** - * @Type(class=Product::class) - */ -class ProductType implements FromSourceFieldsInterface -{ - /** - * Dynamically returns the array of source fields - * to be fetched from the original object. - * - * @return SourceFieldInterface[] - */ - public function getSourceFields(): array - { - // You may want to enable fields conditionally based on feature flags... - if (ENABLE_STATUS_GLOBALLY) { - return [ - new SourceField(['name'=>'status', 'logged'=>true]), + new SourceField(['name'=>'status', 'annotations'=>[new Logged()]]), ]; } else { return []; @@ -290,6 +147,3 @@ class ProductType implements FromSourceFieldsInterface } } ``` - - - diff --git a/website/docs/field-middlewares.md b/website/docs/field-middlewares.md index 18eac15b30..df7b2bfaee 100644 --- a/website/docs/field-middlewares.md +++ b/website/docs/field-middlewares.md @@ -1,15 +1,15 @@ --- id: field-middlewares -title: Adding custom annotations with Field middlewares -sidebar_label: Custom annotations +title: Adding custom attributes with Field middlewares +sidebar_label: Custom attributes --- Available in GraphQLite 4.0+ -Just like the `@Logged` or `@Right` annotation, you can develop your own annotation that extends/modifies the behaviour of a field/query/mutation. +Just like the `#[Logged]` or `#[Right]` attribute, you can develop your own attribute that extends/modifies the behaviour of a field/query/mutation.
- If you want to create an annotation that targets a single argument (like @AutoWire(for="$service")), you should rather check the documentation about custom argument resolving + If you want to create an attribute that targets a single argument (like #[AutoWire]), you should rather check the documentation about custom argument resolving
## Field middlewares @@ -63,16 +63,16 @@ If you want the field to purely disappear, your middleware can return `null`, al field middlewares only get called once per Schema instance. If you use a long-running server (like Laravel Octane, Swoole, RoadRunner etc) and share the same Schema instance across requests, you will not be able to hide fields based on request data. -## Annotations parsing +## Attributes parsing Take a look at the `QueryFieldDescriptor::getMiddlewareAnnotations()`. -It returns the list of annotations applied to your field that implements the `MiddlewareAnnotationInterface`. +It returns the list of attributes applied to your field that implements the `MiddlewareAnnotationInterface`. -Let's imagine you want to add a `@OnlyDebug` annotation that displays a field/query/mutation only in debug mode (and +Let's imagine you want to add a `#[OnlyDebug]` attribute that displays a field/query/mutation only in debug mode (and hides the field in production). That could be useful, right? -First, we have to define the annotation. Annotations are handled by the great [doctrine/annotations](https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html) library (for PHP 7+) and/or by PHP 8 attributes. +First, we have to define the attribute. ```php title="OnlyDebug.php" namespace App\Annotations; @@ -80,19 +80,15 @@ namespace App\Annotations; use Attribute; use TheCodingMachine\GraphQLite\Annotations\MiddlewareAnnotationInterface; -/** - * @Annotation - * @Target({"METHOD", "ANNOTATION"}) - */ #[Attribute(Attribute::TARGET_METHOD)] class OnlyDebug implements MiddlewareAnnotationInterface { } ``` -Apart from being a classical annotation/attribute, this class implements the `MiddlewareAnnotationInterface`. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation is to be used by middlewares. +Apart from being a classical attribute, this class implements the `MiddlewareAnnotationInterface`. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this attribute is to be used by middlewares. -Now, we can write a middleware that will act upon this annotation. +Now, we can write a middleware that will act upon this attribute. ```php namespace App\Middlewares; @@ -103,7 +99,7 @@ use GraphQL\Type\Definition\FieldDefinition; use TheCodingMachine\GraphQLite\QueryFieldDescriptor; /** - * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set + * Middleware in charge of hiding a field if it is annotated with #[OnlyDebug] and the DEBUG constant is not set */ class OnlyDebugFieldMiddleware implements FieldMiddlewareInterface { @@ -117,7 +113,7 @@ class OnlyDebugFieldMiddleware implements FieldMiddlewareInterface $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class); if ($onlyDebug !== null && !DEBUG) { - // If the onlyDebug annotation is present, returns null. + // If the onlyDebug attribute is present, returns null. // Returning null will hide the field. return null; } diff --git a/website/docs/file-uploads.mdx b/website/docs/file-uploads.mdx index 8c0782f89b..487a413948 100644 --- a/website/docs/file-uploads.mdx +++ b/website/docs/file-uploads.mdx @@ -4,9 +4,6 @@ title: File uploads sidebar_label: File uploads --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed to add support for [multipart requests](https://github.com/jaydenseric/graphql-multipart-request-spec). @@ -40,14 +37,6 @@ for more information on how to integrate it in your framework. To handle an uploaded file, you type-hint against the PSR-7 `UploadedFileInterface`: - - - ```php class MyController { @@ -60,26 +49,6 @@ class MyController } ``` - - - -```php -class MyController -{ - /** - * @Mutation - */ - public function saveDocument(string $name, UploadedFileInterface $file): Document - { - // Some code that saves the document. - $file->moveTo($someDir); - } -} -``` - - - - Of course, you need to use a GraphQL client that is compatible with multipart requests. See [jaydenseric/graphql-multipart-request-spec](https://github.com/jaydenseric/graphql-multipart-request-spec#client) for a list of compatible clients. The GraphQL client must send the file using the Upload type. diff --git a/website/docs/fine-grained-security.mdx b/website/docs/fine-grained-security.mdx index f5d2fff032..3fac998ff4 100644 --- a/website/docs/fine-grained-security.mdx +++ b/website/docs/fine-grained-security.mdx @@ -4,29 +4,19 @@ title: Fine grained security sidebar_label: Fine grained security --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -If the [`@Logged` and `@Right` annotations](authentication-authorization.mdx#logged-and-right-annotations) are not -granular enough for your needs, you can use the advanced `@Security` annotation. +If the [`#[Logged]` and `#[Right]` attributes](authentication-authorization.mdx#logged-and-right-annotations) are not +granular enough for your needs, you can use the advanced `#[Security]` attribute. -Using the `@Security` annotation, you can write an *expression* that can contain custom logic. For instance: +Using the `#[Security]` attribute, you can write an *expression* that can contain custom logic. For instance: - Check that a user can access a given resource - Check that a user has one right or another right - ... -## Using the @Security annotation +## Using the #[Security] attribute -The `@Security` annotation is very flexible: it allows you to pass an expression that can contains custom logic: - - - +The `#[Security]` attribute is very flexible: it allows you to pass an expression that can contains custom logic: ```php use TheCodingMachine\GraphQLite\Annotations\Security; @@ -41,33 +31,12 @@ public function getPost(Post $post): array } ``` - - - -```php -use TheCodingMachine\GraphQLite\Annotations\Security; - -// ... - -/** - * @Query - * @Security("is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)") - */ -public function getPost(Post $post): array -{ - // ... -} -``` - - - - -The *expression* defined in the `@Security` annotation must conform to [Symfony's Expression Language syntax](https://symfony.com/doc/4.4/components/expression_language/syntax.html) +The *expression* defined in the `#[Security]` attribute must conform to [Symfony's Expression Language syntax](https://symfony.com/doc/4.4/components/expression_language/syntax.html)
- If you are a Symfony user, you might already be used to the @Security annotation. Most of the inspiration - of this annotation comes from Symfony. Warning though! GraphQLite's @Security annotation and - Symfony's @Security annotation are slightly different. Especially, the two annotations do not live + If you are a Symfony user, you might already be used to the #[Security] attribute. Most of the inspiration + of this attribute comes from Symfony. Warning though! GraphQLite's #[Security] attribute and + Symfony's #[Security] attribute are slightly different. Especially, the two attributes do not live in the same namespace!
@@ -75,62 +44,18 @@ The *expression* defined in the `@Security` annotation must conform to [Symfony' Use the `is_granted` function to check if a user has a special right. - - - ```php #[Security("is_granted('ROLE_ADMIN')")] ``` - - - -```php -@Security("is_granted('ROLE_ADMIN')") -``` - - - - is similar to - - - ```php #[Right("ROLE_ADMIN")] ``` - - - -```php -@Right("ROLE_ADMIN") -``` - - - - In addition, the `is_granted` function accepts a second optional parameter: the "scope" of the right. - - - ```php #[Query] #[Security("is_granted('POST_SHOW', post)")] @@ -140,37 +65,12 @@ public function getPost(Post $post): array } ``` - - - -```php -/** - * @Query - * @Security("is_granted('POST_SHOW', post)") - */ -public function getPost(Post $post): array -{ - // ... -} -``` - - - - In the example above, the `getPost` method can be called only if the logged user has the 'POST_SHOW' permission on the `$post` object. You can notice that the `$post` object comes from the parameters. ## Accessing method parameters -All parameters passed to the method can be accessed in the `@Security` expression. - - - +All parameters passed to the method can be accessed in the `#[Security]` expression. ```php #[Query] @@ -181,38 +81,12 @@ public function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDat } ``` - - - -```php -/** - * @Query - * @Security("startDate < endDate", statusCode=400, message="End date must be after start date") - */ -public function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array -{ - // ... -} -``` - - - - - -In the example above, we tweak a bit the Security annotation purpose to do simple input validation. +In the example above, we tweak a bit the Security attribute purpose to do simple input validation. ## Setting HTTP code and error message You can use the `statusCode` and `message` attributes to set the HTTP code and GraphQL error message. - - - ```php #[Query] #[Security(expression: "is_granted('POST_SHOW', post)", statusCode: 404, message: "Post not found (let's pretend the post does not exists!)")] @@ -222,23 +96,6 @@ public function getPost(Post $post): array } ``` - - - -```php -/** - * @Query - * @Security("is_granted('POST_SHOW', post)", statusCode=404, message="Post not found (let's pretend the post does not exists!)") - */ -public function getPost(Post $post): array -{ - // ... -} -``` - - - - Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code. The resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the higher error code will be returned. @@ -248,14 +105,6 @@ higher error code will be returned. If you do not want an error to be thrown when the security condition is not met, you can use the `failWith` attribute to set a default value. - - - ```php #[Query] #[Security(expression: "is_granted('CAN_SEE_MARGIN', this)", failWith: null)] @@ -265,25 +114,8 @@ public function getMargin(): float } ``` - - - -```php -/** - * @Field - * @Security("is_granted('CAN_SEE_MARGIN', this)", failWith=null) - */ -public function getMargin(): float -{ - // ... -} -``` - - - - -The `failWith` attribute behaves just like the [`@FailWith` annotation](authentication-authorization.mdx#not-throwing-errors) -but for a given `@Security` annotation. +The `failWith` attribute behaves just like the [`#[FailWith]` attribute](authentication-authorization.mdx#not-throwing-errors) +but for a given `#[Security]` attribute. You cannot use the `failWith` attribute along `statusCode` or `message` attributes. @@ -292,13 +124,6 @@ You cannot use the `failWith` attribute along `statusCode` or `message` attribut You can use the `user` variable to access the currently logged user. You can use the `is_logged()` function to check if a user is logged or not. - - ```php #[Query] @@ -309,35 +134,10 @@ public function getNSFWImages(): array } ``` - - - -```php -/** - * @Query - * @Security("is_logged() && user.age > 18") - */ -public function getNSFWImages(): array -{ - // ... -} -``` - - - - ## Accessing the current object You can use the `this` variable to access any (public) property / method of the current class. - - - ```php class Post { #[Field] @@ -354,61 +154,19 @@ class Post { } ``` - - - -```php -class Post { - /** - * @Field - * @Security("this.canAccessBody(user)") - */ - public function getBody(): array - { - // ... - } - - public function canAccessBody(User $user): bool - { - // Some custom logic here - } -} -``` - - - - ## Available scope -The `@Security` annotation can be used in any query, mutation or field, so anywhere you have a `@Query`, `@Mutation` -or `@Field` annotation. +The `#[Security]` attribute can be used in any query, mutation or field, so anywhere you have a `#[Query]`, `#[Mutation]` +or `#[Field]` attribute. ## How to restrict access to a given resource The `is_granted` method can be used to restrict access to a specific resource. - - - ```php #[Security("is_granted('POST_SHOW', post)")] ``` - - - -```php -@Security("is_granted('POST_SHOW', post)") -``` - - - - If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles itself. Instead, this depends on the framework you are using. diff --git a/website/docs/implementing-security.md b/website/docs/implementing-security.md index ce3c8fb43f..88bbd8f7f2 100644 --- a/website/docs/implementing-security.md +++ b/website/docs/implementing-security.md @@ -50,8 +50,8 @@ You need to write classes that implement these interfaces. Then, you must regist It you are [using the `SchemaFactory`](other-frameworks.mdx), you can register your classes using: ```php -// Configure an authentication service (to resolve the @Logged annotations). +// Configure an authentication service (to resolve the #[Logged] attribute). $schemaFactory->setAuthenticationService($myAuthenticationService); -// Configure an authorization service (to resolve the @Right annotations). +// Configure an authorization service (to resolve the #[Right] attribute). $schemaFactory->setAuthorizationService($myAuthorizationService); ``` diff --git a/website/docs/inheritance-interfaces.mdx b/website/docs/inheritance-interfaces.mdx index 05f0404a0e..5467492c52 100644 --- a/website/docs/inheritance-interfaces.mdx +++ b/website/docs/inheritance-interfaces.mdx @@ -4,23 +4,12 @@ title: Inheritance and interfaces sidebar_label: Inheritance and interfaces --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - ## Modeling inheritance Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces. Let's say you have two classes, `Contact` and `User` (which extends `Contact`): - - - ```php #[Type] class Contact @@ -35,40 +24,8 @@ class User extends Contact } ``` - - - -```php -/** - * @Type - */ -class Contact -{ - // ... -} - -/** - * @Type - */ -class User extends Contact -{ - // ... -} -``` - - - - Now, let's assume you have a query that returns a contact: - - - ```php class ContactController { @@ -80,25 +37,6 @@ class ContactController } ``` - - - -```php -class ContactController -{ - /** - * @Query() - */ - public function getContact(): Contact - { - // ... - } -} -``` - - - - When writing your GraphQL query, you are able to use fragments to retrieve fields from the `User` type: ```graphql @@ -135,15 +73,7 @@ available in the `Contact` type. ## Mapping interfaces -If you want to create a pure GraphQL interface, you can also add a `@Type` annotation on a PHP interface. - - - +If you want to create a pure GraphQL interface, you can also add a `#[Type]` attribute on a PHP interface. ```php #[Type] @@ -154,25 +84,6 @@ interface UserInterface } ``` - - - -```php -/** - * @Type - */ -interface UserInterface -{ - /** - * @Field - */ - public function getUserName(): string; -} -``` - - - - This will automatically create a GraphQL interface whose description is: ```graphql @@ -186,14 +97,6 @@ interface UserInterface { You don't have to do anything special to implement an interface in your GraphQL types. Simply "implement" the interface in PHP and you are done! - - - ```php #[Type] class User implements UserInterface @@ -202,22 +105,6 @@ class User implements UserInterface } ``` - - - -```php -/** - * @Type - */ -class User implements UserInterface -{ - public function getUserName(): string; -} -``` - - - - This will translate in GraphQL schema as: ```graphql @@ -230,21 +117,13 @@ type User implements UserInterface { } ``` -Please note that you do not need to put the `@Field` annotation again in the implementing class. +Please note that you do not need to put the `#[Field]` attribute again in the implementing class. ### Interfaces without an explicit implementing type -You don't have to explicitly put a `@Type` annotation on the class implementing the interface (though this +You don't have to explicitly put a `#[Type]` attribute on the class implementing the interface (though this is usually a good idea). - - - ```php /** * Look, this class has no #Type attribute @@ -266,39 +145,10 @@ class UserController } ``` - - - -```php -/** - * Look, this class has no @Type annotation - */ -class User implements UserInterface -{ - public function getUserName(): string; -} -``` - -```php -class UserController -{ - /** - * @Query() - */ - public function getUser(): UserInterface // This will work! - { - // ... - } -} -``` - - - -
If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".
-In the example above, because the `User` class has no `@Type` annotations, GraphQLite will +In the example above, because the `User` class has no `#[Type]` attribute, GraphQLite will create a `UserImpl` type that implements `UserInterface`. ```graphql diff --git a/website/docs/input-types.mdx b/website/docs/input-types.mdx index f2c62afd40..c0efa59e87 100644 --- a/website/docs/input-types.mdx +++ b/website/docs/input-types.mdx @@ -4,21 +4,10 @@ title: Input types sidebar_label: Input types --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - Let's assume you are developing an API that returns a list of cities around a location. Your GraphQL query might look like this: - - - ```php class MyController { @@ -56,49 +45,6 @@ class Location } ``` - - - -```php -class MyController -{ - /** - * @Query - * @return City[] - */ - public function getCities(Location $location, float $radius): array - { - // Some code that returns an array of cities. - } -} - -// Class Location is a simple value-object. -class Location -{ - private $latitude; - 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; - } -} -``` - - - - If you try to run this code, you will get the following error: ``` @@ -115,14 +61,6 @@ There are two ways for declaring that type, in GraphQLite: using the [`#[Input]` Using the `#[Input]` attribute, we can transform the `Location` class, in the example above, into an input type. Just add the `#[Field]` attribute to the corresponding properties: - - - ```php #[Input] class Location @@ -160,88 +98,25 @@ class Location } ``` - - - -```php -/** - * @Input - */ -class Location -{ - - /** - * @Field - * @var string|null - */ - private ?string $name = null; - - /** - * @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 setName(string $name): void - { - $this->name = $name; - } - - public function getLatitude(): float - { - return $this->latitude; - } - - public function getLongitude(): float - { - return $this->longitude; - } -} -``` - - - - Now if you call the `getCities` query, from the controller in the first example, the `Location` object will be automatically instantiated with the user provided, `latitude` / `longitude` properties, and passed to the controller as a parameter. There are some important things to notice: -- The `@Field` annotation is recognized on properties for Input Type, as well as setters. +- The `#[Field]` attribute is recognized on properties for Input Type, as well as setters. - 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 - no constructor required. - - For private or protected properties implemented, a public setter is required (if they are not set via the constructor). For example `setLatitude(float $latitude)`. You can also put the `@Field` annotation on the setter, instead of the property, allowing you to have use many other attributes (`Security`, `Right`, `Autowire`, etc.). + - For private or protected properties implemented, a public setter is required (if they are not set via the constructor). For example `setLatitude(float $latitude)`. You can also put the `#[Field]` attribute on the setter, instead of the property, allowing you to have use many other attributes (`Security`, `Right`, `Autowire`, etc.). - For validation of these Input Types, see the [Custom InputType Validation section](validation#custom-inputtype-validation). - It's advised to use the `#[Input]` attribute on DTO style input type objects and not directly on your model objects. Using it on your model objects can cause coupling in undesirable ways. ### Multiple Input Types from the same class -Simple usage of the `@Input` annotation on a class creates a GraphQL input named by class name + "Input" suffix if a class name does not end with it already. Ex. `LocationInput` for `Location` class. +Simple usage of the `#[Input]` attribute on a class creates a GraphQL input named by class name + "Input" suffix if a class name does not end with it already. Ex. `LocationInput` for `Location` class. -You can add multiple `@Input` annotations to the same class, give them different names and link different fields. +You can add multiple `#[Input]` attributed to the same class, give them different names and link different fields. Consider the following example: - - - ```php #[Input(name: 'CreateUserInput', default: true)] #[Input(name: 'UpdateUserInput', update: true)] @@ -269,53 +144,6 @@ class UserInput } ``` - - - -```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; - - /** @var int|null */ - protected $age; - - /** - * @Field() - * @param int|null $age - */ - public function setAge(?int $age): void - { - $this->age = $age; - } -} -``` - - - - There are 2 input types added to the `UserInput` 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. @@ -335,19 +163,11 @@ A **Factory** is a method that takes in parameter all the fields of the input ty Here is an example of factory: - - - ```php class MyFactory { /** - * The Factory annotation will create automatically a LocationInput input type in GraphQL. + * The Factory attribute will create automatically a LocationInput input type in GraphQL. */ #[Factory] public function createLocation(float $latitude, float $longitude): Location @@ -357,27 +177,6 @@ class MyFactory } ``` - - - -```php -class MyFactory -{ - /** - * The Factory annotation will create automatically a LocationInput input type in GraphQL. - * - * @Factory() - */ - public function createLocation(float $latitude, float $longitude): Location - { - return new Location($latitude, $longitude); - } -} -``` - - - - and now, you can run query like this: ```graphql @@ -394,7 +193,7 @@ query { } ``` -- Factories must be declared with the **@Factory** annotation. +- Factories must be declared with the **#[Factory]** attribute. - The parameters of the factories are the field of the GraphQL input type A few important things to notice: @@ -409,14 +208,6 @@ The GraphQL input type name is derived from the return type of the factory. Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput". - - - ```php #[Factory] public function createLocation(float $latitude, float $longitude): Location @@ -425,48 +216,12 @@ public function createLocation(float $latitude, float $longitude): Location } ``` - - - -```php -/** - * @Factory() - */ -public function createLocation(float $latitude, float $longitude): Location -{ - return new Location($latitude, $longitude); -} -``` - - - - -In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation: - - - +In case you want to override the input type name, you can use the "name" attribute of the #[Factory] attribute: ```php #[Factory(name: 'MyNewInputName', default: true)] ``` - - - -```php -/** - * @Factory(name="MyNewInputName", default=true) - */ -``` - - - - Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in the next chapter). @@ -475,18 +230,10 @@ to you, so there is no real reason to customize it. ### Forcing an input type -You can use the `@UseInputType` annotation to force an input type of a parameter. +You can use the `#[UseInputType]` attribute to force an input type of a parameter. Let's say you want to force a parameter to be of type "ID", you can use this: - - - ```php #[Factory] #[UseInputType(for: "$id", inputType:"ID!")] @@ -496,41 +243,16 @@ public function getProductById(string $id): Product } ``` - - - -```php -/** - * @Factory() - * @UseInputType(for="$id", inputType="ID!") - */ -public function getProductById(string $id): Product -{ - return $this->productRepository->findById($id); -} -``` - - - - ### Declaring several input types for the same PHP class Available in GraphQLite 4.0+ There are situations where a given PHP class might use one factory or another depending on the context. This is often the case when your objects map database entities. -In these cases, you can use combine the use of `@UseInputType` and `@Factory` annotation to achieve your goal. +In these cases, you can use combine the use of `#[UseInputType]` and `#[Factory]` attribute to achieve your goal. Here is an annotated sample: - - - ```php /** * This class contains 2 factories to create Product objects. @@ -584,66 +306,6 @@ class ProductController } ``` - - - -```php -/** - * This class contains 2 factories to create Product objects. - * The "getProduct" method is used by default to map "Product" classes. - * The "createProduct" method will generate another input type named "CreateProductInput" - */ -class ProductFactory -{ - // ... - - /** - * This factory will be used by default to map "Product" classes. - * @Factory(name="ProductRefInput", default=true) - */ - public function getProduct(string $id): Product - { - return $this->productRepository->findById($id); - } - /** - * We specify a name for this input type explicitly. - * @Factory(name="CreateProductInput", default=false) - */ - public function createProduct(string $name, string $type): Product - { - return new Product($name, $type); - } -} - -class ProductController -{ - /** - * The "createProduct" factory will be used for this mutation. - * - * @Mutation - * @UseInputType(for="$product", inputType="CreateProductInput!") - */ - public function saveProduct(Product $product): Product - { - // ... - } - - /** - * The default "getProduct" factory will be used for this query. - * - * @Query - * @return Color[] - */ - public function availableColors(Product $product): array - { - // ... - } -} -``` - - - - ### Ignoring some parameters Available in GraphQLite 4.0+ @@ -654,14 +316,6 @@ Image your `getProductById` has an additional `lazyLoad` parameter. This paramet directly the function in PHP because you can have some level of optimisation on your code. But it is not something that you want to expose in the GraphQL API. Let's hide it! - - - ```php #[Factory] public function getProductById( @@ -674,23 +328,6 @@ public function getProductById( } ``` - - - -```php -/** - * @Factory() - * @HideParameter(for="$lazyLoad") - */ -public function getProductById(string $id, bool $lazyLoad = true): Product -{ - return $this->productRepository->findById($id, $lazyLoad); -} -``` - - - - -With the `@HideParameter` annotation, you can choose to remove from the GraphQL schema any argument. +With the `#[HideParameter]` attribute, 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. diff --git a/website/docs/internals.md b/website/docs/internals.md index 65624fcc49..93c7e958b8 100644 --- a/website/docs/internals.md +++ b/website/docs/internals.md @@ -92,7 +92,7 @@ Class type mappers are mapping PHP classes to GraphQL object types. GraphQLite provide 3 default implementations: - `CompositeTypeMapper`: a type mapper that delegates mapping to other type mappers using the Composite Design Pattern. -- `GlobTypeMapper`: scans classes in a directory for the `@Type` or `@ExtendType` annotation and maps those to GraphQL types +- `GlobTypeMapper`: scans classes in a directory for the `#[Type]` or `#[ExtendType]` attribute and maps those to GraphQL types - `PorpaginasTypeMapper`: maps and class implementing the Porpaginas `Result` interface to a [special paginated type](pagination.mdx). ### Registering a type mapper in Symfony @@ -124,9 +124,9 @@ Let's have a look at a simple query: ```php /** - * @Query * @return Product[] */ +#[Query] public function products(ResolveInfo $info): array ``` diff --git a/website/docs/laravel-package-advanced.mdx b/website/docs/laravel-package-advanced.mdx index 4f63bedcf8..f4f9918427 100644 --- a/website/docs/laravel-package-advanced.mdx +++ b/website/docs/laravel-package-advanced.mdx @@ -4,9 +4,6 @@ title: "Laravel package: advanced usage" sidebar_label: Laravel specific features --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -
Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.
@@ -15,17 +12,9 @@ The Laravel package comes with a number of features to ease the integration of G ## Support for Laravel validation rules -The GraphQLite Laravel package comes with a special `@Validate` annotation to use Laravel validation rules in your +The GraphQLite Laravel package comes with a special `#[Validate]` attribute to use Laravel validation rules in your input types. - - - ```php use TheCodingMachine\GraphQLite\Laravel\Annotations\Validate; @@ -44,30 +33,7 @@ class MyController } ``` - - - -```php -use TheCodingMachine\GraphQLite\Laravel\Annotations\Validate; - -class MyController -{ - /** - * @Mutation - * @Validate(for="$email", rule="email|unique:users") - * @Validate(for="$password", rule="gte:8") - */ - public function createUser(string $email, string $password): User - { - // ... - } -} -``` - - - - -You can use the `@Validate` annotation in any query / mutation / field / factory / decorator. +You can use the `#[Validate]` attribute in any query / mutation / field / factory / decorator. If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code: @@ -99,14 +65,6 @@ You can use any validation rule described in [the Laravel documentation](https:/ In your query, if you explicitly return an object that extends the `Illuminate\Pagination\LengthAwarePaginator` class, the query result will be wrapped in a "paginator" type. - - - ```php class MyController { @@ -121,27 +79,6 @@ class MyController } ``` - - - -```php -class MyController -{ - /** - * @Query - * @return Product[] - */ - public function products(): Illuminate\Pagination\LengthAwarePaginator - { - return Product::paginate(15); - } -} -``` - - - - - Notice that: - the method return type MUST BE `Illuminate\Pagination\LengthAwarePaginator` or a class extending `Illuminate\Pagination\LengthAwarePaginator` @@ -177,14 +114,6 @@ products { Note: if you are using `simplePaginate` instead of `paginate`, you can type hint on the `Illuminate\Pagination\Paginator` class. - - - ```php class MyController { @@ -199,46 +128,18 @@ class MyController } ``` - - - -```php -class MyController -{ - /** - * @Query - * @return Product[] - */ - public function products(): Illuminate\Pagination\Paginator - { - return Product::simplePaginate(15); - } -} -``` - - - - The behaviour will be exactly the same except you will be missing the `totalCount` and `lastPage` fields. ## Using GraphQLite with Eloquent efficiently -In GraphQLite, you are supposed to put a `@Field` annotation on each getter. +In GraphQLite, you are supposed to put a `#[Field]` attribute on each getter. Eloquent uses PHP magic properties to expose your database records. Because Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters. -So we need to find a workaround. GraphQLite comes with a `@MagicField` annotation to help you +So we need to find a workaround. GraphQLite comes with a `#[MagicField]` attribute to help you working with magic properties. - - - ```php #[Type] #[MagicField(name: "id", outputType: "ID!")] @@ -249,24 +150,6 @@ class Product extends Model } ``` - - - -```php -/** - * @Type() - * @MagicField(name="id", outputType="ID!") - * @MagicField(name="name", phpType="string") - * @MagicField(name="categories", phpType="Category[]") - */ -class Product extends Model -{ -} -``` - - - - Please note that since the properties are "magic", they don't have a type. Therefore, you need to pass either the "outputType" attribute with the GraphQL type matching the property, or the "phpType" attribute with the PHP type matching the property. @@ -288,7 +171,7 @@ class User extends Model } ``` -It would be tempting to put a `@Field` annotation on the `phone()` method, but this will not work. Indeed, +It would be tempting to put a `#[Field]` attribute on the `phone()` method, but this will not work. Indeed, the `phone()` method does not return a `App\Phone` object. It is the `phone` magic property that returns it. In short: @@ -299,9 +182,8 @@ In short: ```php class User extends Model { - /** - * @Field - */ + + #[Field] public function phone() { return $this->hasOne('App\Phone'); @@ -315,9 +197,7 @@ class User extends Model This works: ```php -/** - * @MagicField(name="phone", phpType="App\\Phone") - */ +#[MagicField(name: "phone", phpType:"App\\Phone")] class User extends Model { public function phone() diff --git a/website/docs/migrating.md b/website/docs/migrating.md index aa181f7fcc..2753878265 100644 --- a/website/docs/migrating.md +++ b/website/docs/migrating.md @@ -21,14 +21,14 @@ $ composer require ecodev/graphql-upload If you are a "regular" GraphQLite user, migration to v4 should be straightforward: -- Annotations are mostly untouched. The only annotation that is changed is the `@SourceField` annotation. - - Check your code for every places where you use the `@SourceField` annotation: +- Annotations are mostly untouched. The only annotation that is changed is the `#[SourceField]` annotation. + - Check your code for every places where you use the `#[SourceField]` annotation: - The "id" attribute has been remove (`@SourceField(id=true)`). Instead, use `@SourceField(outputType="ID")` - The "logged", "right" and "failWith" attributes have been removed (`@SourceField(logged=true)`). - Instead, use the annotations attribute with the same annotations you use for the `@Field` annotation: + Instead, use the annotations attribute with the same annotations you use for the `#[Field]` annotation: `@SourceField(annotations={@Logged, @FailWith(null)})` - - If you use magic property and were creating a getter for every magic property (to put a `@Field` annotation on it), - you can now replace this getter with a `@MagicField` annotation. + - If you use magic property and were creating a getter for every magic property (to put a `#[Field]` annotation on it), + you can now replace this getter with a `#[MagicField]` annotation. - In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it. In GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it (this way, the schema is the same for all users). If you want the old mode, use the new diff --git a/website/docs/multiple-output-types.mdx b/website/docs/multiple-output-types.mdx index c404c9a5b6..2f0d5b1e8f 100644 --- a/website/docs/multiple-output-types.mdx +++ b/website/docs/multiple-output-types.mdx @@ -4,9 +4,6 @@ title: Mapping multiple output types for the same class sidebar_label: Class with multiple output types --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - Available in GraphQLite 4.0+ In most cases, you have one PHP class and you want to map it to one GraphQL output type. @@ -14,23 +11,13 @@ In most cases, you have one PHP class and you want to map it to one GraphQL outp But in very specific cases, you may want to use different GraphQL output type for the same class. For instance, depending on the context, you might want to prevent the user from accessing some fields of your object. -To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the `@Type` annotation. +To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the `#[Type]` attribute. ## Example Here is an example. Say we are manipulating products. When I query a `Product` details, I want to have access to all fields. But for some reason, I don't want to expose the price field of a product if I query the list of all products. - - - - - ```php #[Type] class Product @@ -51,51 +38,8 @@ class Product } ``` - - - - - -```php -/** - * @Type() - */ -class Product -{ - // ... - - /** - * @Field() - */ - public function getName(): string - { - return $this->name; - } - - /** - * @Field() - */ - public function getPrice(): ?float - { - return $this->price; - } -} -``` - - - - - The `Product` class is declaring a classic GraphQL output type named "Product". - - - ```php #[Type(class: Product::class, name: "LimitedProduct", default: false)] #[SourceField(name: "name")] @@ -105,27 +49,8 @@ class LimitedProductType } ``` - - - -```php -/** - * @Type(class=Product::class, name="LimitedProduct", default=false) - * @SourceField(name="name") - */ -class LimitedProductType -{ - // ... -} -``` - - - - - - The `LimitedProductType` also declares an ["external" type](external-type-declaration.mdx) mapping the `Product` class. -But pay special attention to the `@Type` annotation. +But pay special attention to the `#[Type]` attribute. First of all, we specify `name="LimitedProduct"`. This is useful to avoid having colliding names with the "Product" GraphQL output type that is already declared. @@ -135,14 +60,6 @@ This type will only be used when we explicitly request it. Finally, we can write our requests: - - - ```php class ProductController { @@ -162,35 +79,7 @@ class ProductController } ``` - - - -```php -class ProductController -{ - /** - * This field will use the default type. - * - * @Field - */ - public function getProduct(int $id): Product { /* ... */ } - - /** - * Because we use the "outputType" attribute, this field will use the other type. - * - * @Field(outputType="[LimitedProduct!]!") - * @return Product[] - */ - public function getProducts(): array { /* ... */ } -} -``` - - - - - - -Notice how the "outputType" attribute is used in the `@Field` annotation to force the output type. +Notice how the "outputType" attribute is used in the `#[Field]` attribute to force the output type. Is a result, when the end user calls the `product` query, we will have the possibility to fetch the `name` and `price` fields, but if he calls the `products` query, each product in the list will have a `name` field but no `price` field. We managed @@ -198,59 +87,19 @@ to successfully expose a different set of fields based on the query context. ## Extending a non-default type -If you want to extend a type using the `@ExtendType` annotation and if this type is declared as non-default, +If you want to extend a type using the `#[ExtendType]` attribute and if this type is declared as non-default, you need to target the type by name instead of by class. So instead of writing: - - - ```php #[ExtendType(class: Product::class)] ``` - - - -```php -/** - * @ExtendType(class=Product::class) - */ -``` - - - - you will write: - - - ```php #[ExtendType(name: "LimitedProduct")] ``` - - - -```php -/** - * @ExtendType(name="LimitedProduct") - */ -``` - - - - -Notice how we use the "name" attribute instead of the "class" attribute in the `@ExtendType` annotation. +Notice how we use the "name" attribute instead of the "class" attribute in the `#[ExtendType]` attribute. diff --git a/website/docs/mutations.mdx b/website/docs/mutations.mdx index 16ab03a72a..614705e6f6 100644 --- a/website/docs/mutations.mdx +++ b/website/docs/mutations.mdx @@ -4,23 +4,12 @@ title: Mutations sidebar_label: Mutations --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - In GraphQLite, mutations are created [like queries](queries.mdx). -To create a mutation, you must annotate a method in a controller with the `@Mutation` annotation. +To create a mutation, you must annotate a method in a controller with the `#[Mutation]` attribute. For instance: - - - ```php namespace App\Controller; @@ -36,25 +25,3 @@ class ProductController } ``` - - - -```php -namespace App\Controller; - -use TheCodingMachine\GraphQLite\Annotations\Mutation; - -class ProductController -{ - /** - * @Mutation - */ - public function saveProduct(int $id, string $name, ?float $price = null): Product - { - // Some code that saves a product. - } -} -``` - - - diff --git a/website/docs/other-frameworks.mdx b/website/docs/other-frameworks.mdx index 9402451116..39850f13a3 100644 --- a/website/docs/other-frameworks.mdx +++ b/website/docs/other-frameworks.mdx @@ -4,9 +4,6 @@ title: Getting started with any framework sidebar_label: "Other frameworks / No framework" --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - ## Installation Open a terminal in your current project directory and run: @@ -50,9 +47,9 @@ or the [StandardServer class](https://webonyx.github.io/graphql-php/executing-qu The `SchemaFactory` class also comes with a number of methods that you can use to customize your GraphQLite settings. ```php -// Configure an authentication service (to resolve the @Logged annotations). +// Configure an authentication service (to resolve the #[Logged] attributes). $factory->setAuthenticationService(new VoidAuthenticationService()); -// Configure an authorization service (to resolve the @Right annotations). +// Configure an authorization service (to resolve the #[Right] attributes). $factory->setAuthorizationService(new VoidAuthorizationService()); // Change the naming convention of GraphQL types globally. $factory->setNamingStrategy(new NamingStrategy()); @@ -302,15 +299,6 @@ The application will look into the `App\Controllers` namespace for GraphQLite co It assumes that the container has an entry whose name is the controller's fully qualified class name. - - - - ```php title="src/Controllers/MyController.php" namespace App\Controllers; @@ -326,30 +314,6 @@ class MyController } ``` - - - -```php title="src/Controllers/MyController.php" -namespace App\Controllers; - -use TheCodingMachine\GraphQLite\Annotations\Query; - -class MyController -{ - /** - * @Query - */ - public function hello(string $name): string - { - return 'Hello '.$name; - } -} -``` - - - - - ```php title="config/container.php" use App\Controllers\MyController; diff --git a/website/docs/pagination.mdx b/website/docs/pagination.mdx index 66b58c863b..44ad427c53 100644 --- a/website/docs/pagination.mdx +++ b/website/docs/pagination.mdx @@ -4,9 +4,6 @@ title: Paginating large result sets sidebar_label: Pagination --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - It is quite common to have to paginate over large result sets. GraphQLite offers a simple way to do that using [Porpaginas](https://github.com/beberlei/porpaginas). @@ -29,14 +26,6 @@ $ composer require beberlei/porpaginas In your query, simply return a class that implements `Porpaginas\Result`: - - - ```php class MyController { @@ -54,29 +43,6 @@ class MyController } ``` - - - -```php -class MyController -{ - /** - * @Query - * @return Product[] - */ - public function products(): Porpaginas\Result - { - // Some code that returns a list of products - - // If you are using Doctrine, something like: - return new Porpaginas\Doctrine\ORM\ORMQueryResult($doctrineQuery); - } -} -``` - - - - Notice that: - the method return type MUST BE `Porpaginas\Result` or a class implementing `Porpaginas\Result` diff --git a/website/docs/prefetch-method.mdx b/website/docs/prefetch-method.mdx index 1c24e7099f..a12cb50891 100644 --- a/website/docs/prefetch-method.mdx +++ b/website/docs/prefetch-method.mdx @@ -4,9 +4,6 @@ title: Prefetching records sidebar_label: Prefetching records --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - ## The problem GraphQL naive implementations often suffer from the "N+1" problem. @@ -43,14 +40,6 @@ Instead, GraphQLite offers an easier to implement solution: the ability to fetch ## The "prefetch" method - - - ```php #[Type] class PostType { @@ -80,45 +69,7 @@ class PostType { } ``` - - - -```php -/** - * @Type - */ -class PostType { - /** - * @Field(prefetchMethod="prefetchUsers") - * @param mixed $prefetchedUsers - * @return User - */ - public function getUser($prefetchedUsers): User - { - // This method will receive the $prefetchedUsers as first argument. This is the return value of the "prefetchUsers" method below. - // Using this prefetched list, it should be easy to map it to the post - } - - /** - * @param Post[] $posts - * @return mixed - */ - public function prefetchUsers(iterable $posts) - { - // This function is called only once per GraphQL request - // with the list of posts. You can fetch the list of users - // associated with this posts in a single request, - // for instance using a "IN" query in SQL or a multi-fetch - // in your cache back-end. - } -} -``` - - - - - -When a "#[Prefetch]" attribute is detected on a parameter of "@Field" annotation, the method is called automatically. +When a `#[Prefetch]` attribute is detected on a parameter of `#[Field]` attribute, the method is called automatically. The prefetch callable must be one of the following: - a static method in the same class: `#[Prefetch('prefetchMethod')]` - a static method in a different class: `#[Prefetch([OtherClass::class, 'prefetchMethod')]` @@ -127,18 +78,10 @@ The first argument of the method is always an array of instances of the main typ ## Input arguments -Field arguments can be set either on the @Field annotated method OR/AND on the prefetch methods. +Field arguments can be set either on the `#[Field]` annotated method OR/AND on the prefetch methods. For instance: - - - ```php #[Type] class PostType { @@ -163,36 +106,3 @@ class PostType { } } ``` - - - - -```php -/** - * @Type - */ -class PostType { - /** - * @Field(prefetchMethod="prefetchComments") - * @param mixed $prefetchedComments - * @return Comment[] - */ - public function getComments($prefetchedComments): array - { - // ... - } - - /** - * @param Post[] $posts - * @return mixed - */ - public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore) - { - // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed - // as GraphQL arguments for the "comments" field. - } -} -``` - - - diff --git a/website/docs/queries.mdx b/website/docs/queries.mdx index 34abab54dd..138e4812eb 100644 --- a/website/docs/queries.mdx +++ b/website/docs/queries.mdx @@ -4,9 +4,6 @@ title: Queries sidebar_label: Queries --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - In GraphQLite, GraphQL queries are created by writing methods in *controller* classes. Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite. @@ -14,15 +11,7 @@ For instance, in Symfony, the controllers namespace is `App\Controller` by defau ## Simple query -In a controller class, each query method must be annotated with the `@Query` annotation. For instance: - - - +In a controller class, each query method must be annotated with the `#[Query]` attribute. For instance: ```php namespace App\Controller; @@ -39,29 +28,6 @@ class MyController } ``` - - - -```php -namespace App\Controller; - -use TheCodingMachine\GraphQLite\Annotations\Query; - -class MyController -{ - /** - * @Query - */ - public function hello(string $name): string - { - return 'Hello ' . $name; - } -} -``` - - - - This query is equivalent to the following [GraphQL type language](https://graphql.org/learn/schema/#type-language): ```graphql @@ -74,13 +40,11 @@ As you can see, GraphQLite will automatically do the mapping between PHP types a
Heads up! If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the MyController class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller.
-## About annotations / attributes +## About attributes -GraphQLite relies a lot on annotations (we call them attributes since PHP 8). +GraphQLite relies a lot on attributes. -It supports both the old "Doctrine annotations" style (`@Query`) and the new PHP 8 attributes (`#[Query]`). - -Read the [Doctrine annotations VS attributes](doctrine-annotations-attributes.mdx) documentation if you are not familiar with this concept. +It supports the new PHP 8 attributes (`#[Query]`), the "Doctrine annotations" style (`#[Query]`) was dropped. ## Testing the query @@ -104,14 +68,6 @@ So far, we simply declared a query. But we did not yet declare a type. Let's assume you want to return a product: - - - ```php namespace App\Controller; @@ -127,41 +83,9 @@ class ProductController } ``` - - - -```php -namespace App\Controller; - -use TheCodingMachine\GraphQLite\Annotations\Query; - -class ProductController -{ - /** - * @Query - */ - public function product(string $id): Product - { - // Some code that looks for a product and returns it. - } -} -``` - - - - - As the `Product` class is not a scalar type, you must tell GraphQLite how to handle it: - - - ```php namespace App\Entities; @@ -187,46 +111,9 @@ class Product } ``` - - - -```php -namespace App\Entities; - -use TheCodingMachine\GraphQLite\Annotations\Field; -use TheCodingMachine\GraphQLite\Annotations\Type; - -/** - * @Type() - */ -class Product -{ - // ... - - /** - * @Field() - */ - public function getName(): string - { - return $this->name; - } - - /** - * @Field() - */ - public function getPrice(): ?float - { - return $this->price; - } -} -``` - - - - -The `@Type` annotation is used to inform GraphQLite that the `Product` class is a GraphQL type. +The `#[Type]` attribute is used to inform GraphQLite that the `Product` class is a GraphQL type. -The `@Field` annotation is used to define the GraphQL fields. This annotation must be put on a **public method**. +The `#[Field]` attribute is used to define the GraphQL fields. This attribute must be put on a **public method**. The `Product` class must be in one of the *types* namespaces. As for *controller* classes, you configured this namespace when you installed GraphQLite. By default, in Symfony, the allowed types namespaces are `App\Entity` and `App\Types`. @@ -243,9 +130,9 @@ Type Product {

If you are used to Domain driven design, you probably realize that the Product class is part of your domain.

-

GraphQL annotations are adding some serialization logic that is out of scope of the domain. - These are just annotations and for most project, this is the fastest and easiest route.

-

If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class +

GraphQL attributes are adding some serialization logic that is out of scope of the domain. + These are just attributes and for most project, this is the fastest and easiest route.

+

If you feel that GraphQL attributes do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.

diff --git a/website/docs/query-plan.mdx b/website/docs/query-plan.mdx index 98838399d0..094b77c55e 100644 --- a/website/docs/query-plan.mdx +++ b/website/docs/query-plan.mdx @@ -4,9 +4,6 @@ title: Query plan sidebar_label: Query plan --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - ## The problem GraphQL naive implementations often suffer from the "N+1" problem. @@ -43,14 +40,6 @@ With GraphQLite, you can answer this question by tapping into the `ResolveInfo` Available in GraphQLite 4.0+ - - - ```php use GraphQL\Type\Definition\ResolveInfo; @@ -72,34 +61,6 @@ class ProductsController } ``` - - - -```php -use GraphQL\Type\Definition\ResolveInfo; - -class ProductsController -{ - /** - * @Query - * @return Product[] - */ - public function products(ResolveInfo $info): array - { - if (isset($info->getFieldSelection()['manufacturer']) { - // Let's perform a request with a JOIN on manufacturer - } else { - // Let's perform a request without a JOIN on manufacturer - } - // ... - } -} -``` - - - - - `ResolveInfo` is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite). It contains info about the query and what fields are requested. Using `ResolveInfo::getFieldSelection` you can analyze the query and decide whether you should perform additional "JOINS" in your query or not. diff --git a/website/docs/symfony-bundle-advanced.mdx b/website/docs/symfony-bundle-advanced.mdx index d96b9fae68..c34c6d7628 100644 --- a/website/docs/symfony-bundle-advanced.mdx +++ b/website/docs/symfony-bundle-advanced.mdx @@ -4,9 +4,6 @@ title: "Symfony bundle: advanced usage" sidebar_label: Symfony specific features --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -
Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.
@@ -114,15 +111,7 @@ This interface is automatically mapped to a type with 2 fields: - `userName: String!` - `roles: [String!]!` -If you want to get more fields, just add the `@Type` annotation to your user class: - - - +If you want to get more fields, just add the `#[Type]` attribute to your user class: ```php #[Type] @@ -137,29 +126,6 @@ class User implements UserInterface } ``` - - - -```php -/** - * @Type - */ -class User implements UserInterface -{ - /** - * @Field - */ - public function getEmail() : string - { - // ... - } - -} -``` - - - - You can now query this field using an [inline fragment](https://graphql.org/learn/queries/#inline-fragments): ```graphql @@ -192,14 +158,6 @@ Most of the time, getting the request object is irrelevant. Indeed, it is GraphQ manage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request in any parameter of your query/mutation/field. - - - ```php use Symfony\Component\HttpFoundation\Request; @@ -208,22 +166,4 @@ public function getUser(int $id, Request $request): User { // The $request object contains the Symfony Request. } -``` - - - - -```php -use Symfony\Component\HttpFoundation\Request; - -/** - * @Query - */ -public function getUser(int $id, Request $request): User -{ - // The $request object contains the Symfony Request. -} -``` - - - +``` \ No newline at end of file diff --git a/website/docs/type-mapping.mdx b/website/docs/type-mapping.mdx index 2051dd9559..26127719d6 100644 --- a/website/docs/type-mapping.mdx +++ b/website/docs/type-mapping.mdx @@ -4,9 +4,6 @@ title: Type mapping sidebar_label: Type mapping --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - As explained in the [queries](queries.mdx) section, the job of GraphQLite is to create GraphQL types from PHP types. ## Scalar mapping @@ -20,14 +17,6 @@ Scalar PHP types can be type-hinted to the corresponding GraphQL types: For instance: - - - ```php namespace App\Controller; @@ -43,40 +32,9 @@ class MyController } ``` - - - -```php -namespace App\Controller; - -use TheCodingMachine\GraphQLite\Annotations\Query; - -class MyController -{ - /** - * @Query - */ - public function hello(string $name): string - { - return 'Hello ' . $name; - } -} -``` - - - - ## Class mapping -When returning a PHP class in a query, you must annotate this class using `@Type` and `@Field` annotations: - - - +When returning a PHP class in a query, you must annotate this class using `#[Type]` and `#[Field]` attributes: ```php namespace App\Entities; @@ -103,90 +61,24 @@ class Product } ``` - - - -```php -namespace App\Entities; - -use TheCodingMachine\GraphQLite\Annotations\Field; -use TheCodingMachine\GraphQLite\Annotations\Type; - -/** - * @Type() - */ -class Product -{ - // ... - - /** - * @Field() - */ - public function getName(): string - { - return $this->name; - } - - /** - * @Field() - */ - public function getPrice(): ?float - { - return $this->price; - } -} -``` - - - - **Note:** The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your PHP class is `App\Entities\Product`, then the GraphQL type will be named "Product". In case you have several types with the same class name in different namespaces, you will face a naming collision. Hopefully, you can force the name of the GraphQL output type using the "name" attribute: - - - ```php #[Type(name: "MyProduct")] class Product { /* ... */ } ``` - - - -```php -/** - * @Type(name="MyProduct") - */ -class Product { /* ... */ } -``` - - - - -
You can also put a @Type annotation on a PHP interface + ## Array mapping You can type-hint against arrays (or iterators) as long as you add a detailed `@return` statement in the PHPDoc. - - - ```php /** * @return User[] <=== we specify that the array is an array of User objects. @@ -198,23 +90,6 @@ public function users(int $limit, int $offset): array } ``` - - - -```php -/** - * @Query - * @return User[] <=== we specify that the array is an array of User objects. - */ -public function users(int $limit, int $offset): array -{ - // Some code that returns an array of "users". -} -``` - - - - ## ID mapping GraphQL comes with a native `ID` type. PHP has no such type. @@ -223,14 +98,6 @@ There are two ways with GraphQLite to handle such type. ### Force the outputType - - - ```php #[Field(outputType: "ID")] public function getId(): string @@ -239,36 +106,12 @@ public function getId(): string } ``` - - - -```php -/** - * @Field(outputType="ID") - */ -public function getId(): string -{ - // ... -} -``` - - - - -Using the `outputType` attribute of the `@Field` annotation, you can force the output type to `ID`. +Using the `outputType` attribute of the `#[Field]` attribute, you can force the output type to `ID`. You can learn more about forcing output types in the [custom types section](custom-types.mdx). ### ID class - - - ```php use TheCodingMachine\GraphQLite\Types\ID; @@ -279,34 +122,8 @@ public function getId(): ID } ``` - - - -```php -use TheCodingMachine\GraphQLite\Types\ID; - -/** - * @Field - */ -public function getId(): ID -{ - // ... -} -``` - - - - Note that you can also use the `ID` class as an input type: - - - ```php use TheCodingMachine\GraphQLite\Types\ID; @@ -317,24 +134,6 @@ public function save(ID $id, string $name): Product } ``` - - - -```php -use TheCodingMachine\GraphQLite\Types\ID; - -/** - * @Mutation - */ -public function save(ID $id, string $name): Product -{ - // ... -} -``` - - - - ## Date mapping Out of the box, GraphQL does not have a `DateTime` type, but we took the liberty to add one, with sensible defaults. @@ -342,14 +141,6 @@ Out of the box, GraphQL does not have a `DateTime` type, but we took the liberty When used as an output type, `DateTimeImmutable` or `DateTimeInterface` PHP classes are automatically mapped to this `DateTime` GraphQL type. - - - ```php #[Field] public function getDate(): \DateTimeInterface @@ -358,22 +149,6 @@ public function getDate(): \DateTimeInterface } ``` - - - -```php -/** - * @Field - */ -public function getDate(): \DateTimeInterface -{ - return $this->date; -} -``` - - - - The `date` field will be of type `DateTime`. In the returned JSON response to a query, the date is formatted as a string in the **ISO8601** format (aka ATOM format). @@ -385,14 +160,6 @@ in the **ISO8601** format (aka ATOM format). Union types for return are supported in GraphQLite as of version 6.0: - - - ```php #[Query] public function companyOrContact(int $id): Company|Contact @@ -401,23 +168,6 @@ public function companyOrContact(int $id): Company|Contact } ``` - - - -```php -/** - * @Query - * @return Company|Contact - */ -public function companyOrContact(int $id) -{ - // Some code that returns a company or a contact. -} -``` - - - - ## Enum types PHP 8.1 introduced native support for Enums. GraphQLite now also supports native enums as of version 5.1. @@ -455,7 +205,7 @@ query users($status: Status!) {} ``` By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes -that live in different namespaces with the same class name), you can solve it using the `name` property on the `@Type` annotation: +that live in different namespaces with the same class name), you can solve it using the `name` property on the `#[Type]` attribute: ```php namespace Model\User; @@ -467,7 +217,6 @@ enum Status: string } ``` - ### Enum types with myclabs/php-enum
@@ -482,14 +231,6 @@ $ composer require myclabs/php-enum Now, any class extending the `MyCLabs\Enum\Enum` class will be mapped to a GraphQL enum: - - - ```php use MyCLabs\Enum\Enum; @@ -517,39 +258,6 @@ public function users(StatusEnum $status): array } ``` - - - -```php -use MyCLabs\Enum\Enum; - -class StatusEnum extends Enum -{ - private const ON = 'on'; - private const OFF = 'off'; - private const PENDING = 'pending'; -} -``` - -```php -/** - * @Query - * @return User[] - */ -public function users(StatusEnum $status): array -{ - if ($status == StatusEnum::ON()) { - // Note that the "magic" ON() method returns an instance of the StatusEnum class. - // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here) - // ... - } - // ... -} -``` - - - - ```graphql query users($status: StatusEnum!) {} users(status: $status) { @@ -559,15 +267,7 @@ query users($status: StatusEnum!) {} ``` By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes -that live in different namespaces with the same class name), you can solve it using the `@EnumType` annotation: - - - +that live in different namespaces with the same class name), you can solve it using the `#[EnumType]` attribute: ```php use TheCodingMachine\GraphQLite\Annotations\EnumType; @@ -579,24 +279,6 @@ class StatusEnum extends Enum } ``` - - - -```php -use TheCodingMachine\GraphQLite\Annotations\EnumType; - -/** - * @EnumType(name="UserStatus") - */ -class StatusEnum extends Enum -{ - // ... -} -``` - - - -
GraphQLite must be able to find all the classes extending the "MyCLabs\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite @@ -612,25 +294,21 @@ namespace App\Entities; use TheCodingMachine\GraphQLite\Annotations\Field; use TheCodingMachine\GraphQLite\Annotations\Type; -/** - * @Type() - */ +#[Type] class Product { // ... - /** - * @Field() - */ + #[Field] public function getName(): string { return $this->name; } /** - * @Field() * @deprecated use field `name` instead */ + #[Field] public function getProductName(): string { return $this->name; diff --git a/website/docs/validation.mdx b/website/docs/validation.mdx index dfb2c925dd..3b8ac86ac2 100644 --- a/website/docs/validation.mdx +++ b/website/docs/validation.mdx @@ -4,9 +4,6 @@ title: Validation sidebar_label: User input validation --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - GraphQLite does not handle user input validation by itself. It is out of its scope. However, it can integrate with your favorite framework validation mechanism. The way you validate user input will @@ -31,18 +28,9 @@ GraphQLite provides a bridge to use the [Symfony validator](https://symfony.com/ ### Using the Symfony validator bridge -Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities +Usually, when you use the Symfony validator component, you put attributes in your entities and you validate those entities using the `Validator` object. - - - ```php title="UserController.php" use Symfony\Component\Validator\Validator\ValidatorInterface; use TheCodingMachine\GraphQLite\Validator\ValidationFailedException @@ -73,55 +61,8 @@ class UserController } ``` - - - -```php title="UserController.php" -use Symfony\Component\Validator\Validator\ValidatorInterface; -use TheCodingMachine\GraphQLite\Validator\ValidationFailedException - -class UserController -{ - private $validator; - - public function __construct(ValidatorInterface $validator) - { - $this->validator = $validator; - } - - /** - * @Mutation - */ - public function createUser(string $email, string $password): User - { - $user = new User($email, $password); - - // Let's validate the user - $errors = $this->validator->validate($user); - - // Throw an appropriate GraphQL exception if validation errors are encountered - ValidationFailedException::throwException($errors); - - // No errors? Let's continue and save the user - // ... - } -} -``` - - - - Validation rules are added directly to the object in the domain model: - - - ```php title="User.php" use Symfony\Component\Validator\Constraints as Assert; @@ -146,41 +87,6 @@ class User } ``` - - - -```php title="User.php" -use Symfony\Component\Validator\Constraints as Assert; - -class User -{ - /** - * @Assert\Email( - * message = "The email '{{ value }}' is not a valid email.", - * checkMX = true - * ) - */ - private $email; - - /** - * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not. - * @Assert\NotCompromisedPassword - */ - private $password; - - public function __construct(string $email, string $password) - { - $this->email = $email; - $this->password = $password; - } - - // ... -} -``` - - - - If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response: ```json @@ -212,35 +118,32 @@ on your domain objects. Only use this technique if you want to validate user inp in a domain object.
-Use the `@Assertion` annotation to validate directly the user input. +Use the `#[Assertion]` attribute to validate directly the user input. ```php use Symfony\Component\Validator\Constraints as Assert; use TheCodingMachine\GraphQLite\Validator\Annotations\Assertion; +use TheCodingMachine\GraphQLite\Annotations\Query; -/** - * @Query - * @Assertion(for="email", constraint=@Assert\Email()) - */ +#[Query] +#[Assertion(for: "email", constraint: new Assert\Email())] public function findByMail(string $email): User { // ... } ``` -Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation). +Notice that the "constraint" parameter contains an attribute (it is an attribute wrapped in an attribute). You can also pass an array to the `constraint` parameter: ```php -@Assertion(for="email", constraint={@Assert\NotBlank(), @Assert\Email()}) +#[Assertion(for: "email", constraint: [new Assert\NotBlack(), new Assert\Email()])] ``` -
Heads up! The "@Assertion" annotation is only available as a Doctrine annotations. You cannot use it as a PHP 8 attributes
- ## Custom InputType Validation -GraphQLite also supports a fully custom validation implementation for all input types defined with an `@Input` annotation or PHP8 `#[Input]` attribute. This offers a way to validate input types before they're available as a method parameter of your query and mutation controllers. This way, when you're using your query or mutation controllers, you can feel confident that your input type objects have already been validated. +GraphQLite also supports a fully custom validation implementation for all input types defined with an `#[Input]` attribute. This offers a way to validate input types before they're available as a method parameter of your query and mutation controllers. This way, when you're using your query or mutation controllers, you can feel confident that your input type objects have already been validated.

It's important to note that this validation implementation does not validate input types created with a factory. If you are creating an input type with a factory, or using primitive parameters in your query/mutation controllers, you should be sure to validate these independently. This is strictly for input type objects.

@@ -248,7 +151,7 @@ GraphQLite also supports a fully custom validation implementation for all input

You can use one of the framework validation libraries listed above or implement your own validation for these cases. If you're using input type objects for most all of your query and mutation controllers, then there is little additional validation concerns with regards to user input. There are many reasons why you should consider defaulting to an InputType object, as opposed to individual arguments, for your queries and mutations. This is just one additional perk.

-To get started with validation on input types defined by an `@Input` annotation, you'll first need to register your validator with the `SchemaFactory`. +To get started with validation on input types defined by an `#[Input]` attribute, you'll first need to register your validator with the `SchemaFactory`. ```php $factory = new SchemaFactory($cache, $this->container); @@ -278,7 +181,7 @@ interface InputTypeValidatorInterface } ``` -The interface is quite simple. Handle all of your own validation logic in the `validate` method. For example, you might use Symfony's annotation based validation in addition to some other custom validation logic. It's really up to you on how you wish to handle your own validation. The `validate` method will receive the input type object populated with the user input. +The interface is quite simple. Handle all of your own validation logic in the `validate` method. For example, you might use Symfony's attribute based validation in addition to some other custom validation logic. It's really up to you on how you wish to handle your own validation. The `validate` method will receive the input type object populated with the user input. You'll notice that the `validate` method has a `void` return. The purpose here is to encourage you to throw an Exception or handle validation output however you best see fit. GraphQLite does it's best to stay out of your way and doesn't make attempts to handle validation output. You can, however, throw an instance of `TheCodingMachine\GraphQLite\Exceptions\GraphQLException` or `TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException` as usual (see [Error Handling](error-handling) for more details).