This bundle provides a tight integration with the Symfony Validator Component to validate user input data.
- Overview
- How does it work?
- Applying of validation constraints
- Groups
- Group Sequences
- Validating inside resolvers
- Injecting errors
- Error messages
- Translations
- Using built-in expression functions
- ValidationNode API
- Limitations
In order to validate input data the only thing you need to do is to apply constraints
in your yaml
type definitions (args
by object
types and fields
by input-object
types). The bundle will then
automatically validate the data and throw an exception, which will be caught and returned in the response back to the
client.
Follow the example below to get a quick overview of the most basic validation capabilities of this bundle.
# config\graphql\types\Mutation.yaml
Mutation:
type: object
config:
fields:
register:
type: User
resolve: "@=mutation('register', [args])"
args:
username:
type: String!
validation: # applying constraints to `username`
- Length:
min: 6
max: 32
password:
type: String!
validation: # applying constraints to `password`
- Length:
min: 8
max: 32
- IdenticalTo:
propertyPath: passwordRepeat
passwordRepeat:
type: String!
emails:
type: "[String]"
validation: # applying constraints to `emails`
- Unique: ~
- Count:
min: 1
max: 3
- All:
- Email: ~
birthdate:
type: Birthdate
validation: cascade # delegating validation to the embedded type
Birthday:
type: input-object
config:
fields:
day:
type: Int!
validation:
- Range: { min: 1, max: 31 }
month:
type: Int!
validation:
- Range: { min: 1, max: 12 }
year:
type: Int!
validation:
- Range: { min: 1900, max: 2019 }
The configuration above checks, that:
- username
- has length between 6 and 32
- password
- has length between 8 and 32
- is equal to the passwordRepeat value
- email
- every item in the collection is unique
- the number of items in the collection is between 1 and 3
- every item in the collection is a valid email address
The birthday
field is of type input-object
and is marked as cascade
so it's validation will happen according to the constraints declared in the Birthday
type:
- day is between 1 and 31
- month is between 1 and 12
- year is between 1900 and 2019
The validation system ensures, that all arguments in your resolver are always valid. If validation fails, your resolver will never be called.
The Symfony Validator Component is designed to validate objects. For this reason this bundle creates temporary objects
for each of your GraphQL types during the validation process and populates them with the input data. Resulting objects
will repeat the nesting structure of your GraphQL schema. The object properties are created dynamically in runtime with
the same names as the corresponding args
or fields
, depending on GraphQL type (object
and input-object
respectively). All newly created objects will be instances of the class ValidationNode
(see ValidationNode API). The resulting object composition will be then recursively validated,
starting from the root object down to it's children.
Please note, that the original arguments won't be altered in any way.
Let's take the example from the chapter Overview. When a user requests the register
field, two following
objects will be created (for both GraphQL types):
If the
birthday
argument weren't marked ascascade
it would remain an array and we would have only 1 object.
Here is a more complex example to better demonstrate how the InputValidator
creates objects from your GraphQL schema and embeds them in each other:
Mutation:
type: object
config:
fields:
registerUser:
type: User
resolve: "@=mutation('registerUser', [args])"
args:
username:
type: String!
validation:
- App\Constraint\Latin: ~
- Length: { min: 5, max: 16 }
password:
type: String!
validation:
- App\Constraint\Latin: ~
- IdenticalTo:
propertyPath: passwordRepeat
passwordRepeat:
type: String!
emails:
type: "[String]"
validation:
- Unique: ~
- Count:
min: 1
max: 3
- All:
- Email: ~
birthday:
type: Birthday
validation: cascade
job:
type: Job
validation: cascade
address:
type: Address
validation:
- Collection:
fields:
street:
- App\Constraint\Latin: ~
- Length: { min: 2, max: 64 }
city:
- App\Constraint\Latin: ~
- Length: { min: 2, max: 64 }
zip:
- Positive: ~
registerAdmin:
type: User
resolve: "@=mutation('registerAdmin', [args])"
args:
username:
type: String!
validation:
- Length: { min: 8 }
password:
type: String!
validation:
- Length: { min: 10 }
- IdenticalTo:
propertyPath: passwordRepeat
passwordRepeat:
type: String!
Job:
type: input-object
config:
fields:
position:
type: String!
validation:
- Choice: [developer, manager, designer]
workPeriod:
type: Period
validation: cascade
address:
type: Address
validation: cascade
Address:
type: input-object
config:
fields:
street:
type: String!
validation:
- App\Constraint\Latin: ~
- Length: { min: 2, max: 64 }
city:
type: String!
validation:
- App\Constraint\Latin: ~
- Length: { min: 2, max: 64 }
zip:
type: Int!
validation:
- Positive: ~
Period:
type: input-object
config:
fields:
startDate:
type: String!
validation:
- Date: ~
endDate:
type: String!
validation:
- Date: ~
- GreaterThan:
propertyPath: 'startDate'
Birthday:
type: input-object
config:
fields:
day:
type: Int!
validation:
- Range: { min: 1, max: 31 }
month:
type: Int!
validation:
- Range: { min: 1, max: 12 }
year:
type: Int!
validation:
- Range: { min: 1900, max: today }
The configuration above would produce object compositions as shown in the UML diagrams below:
for the registerUser
resolver:
Note that the argument
address
in the objectMutation
wasn't converted into an object, as it doesn't have the keycascade
, but it will still be validated against theCollection
constraint as an array.
for the registerAdmin
resolver:
If you are familiar with Symfony Validator Сomponent, then you might know that constraints can have different targets (class members or entire classes). Since each of your GraphQL types is represented by an object during the validation, you can also declare member constraints as well as class constraints.
There are 3 different methods to apply validation constraints:
- List them directly in the type definitions with the
constraints
key. - Link to an existing class with the
link
key. - Delegate validation to a child type (input-object) with the
cascade
key.
All 3 methods can be mixed. If you use only 1 method you can omit the corresponding key (short form). Only definitions of type object
and input-object
can have validation rules.
The most straightforward way to apply validation constraints to input data is to list them under the constraints
key.
In the chapter Overview this method has already been demonstrated. Follow the examples below to see how to use
only this method, as well as in combinations with linking:
Property constraints are applied to arguments:
Mutation:
type: object
config:
fields:
updateUser:
type: User
resolve: "@=mutation('updateUser', [args])"
args:
username:
type: String
validation: # using an explicit list of constraints (short form)
- NotBlank: ~
- Length:
min: 6
max: 32
minMessage: "Username must have {{ limit }} characters or more"
maxMessage: "Username must have {{ limit }} characters or less"
email:
type: String
validation: App\Entity\User::$email # using a link (short form)
info:
type: String
validation: # mixing both
link: App\Entity\User::$info
constraints:
- NotBlank: ~
- App\Constraint\MyConstraint: ~ # custom constraint
Class constraints are applied to fields:
Mutation:
type: object
config:
fields:
updateUser:
type: User
resolve: "@=mutation('updateUser', [args])"
validation:
- Callback: [App\Validation\UserValidator, updateUser]
args:
username: String
email: String
info: String
It's also possible to declare validation constraints to the entire type. This is useful if you don't want to repeat the configuration for each field or if you want to move the entire validation logic into a function:
Mutation:
type: object
config:
validation:
- Callback: [App\Validation\UserValidator, validate]
fields:
createUser:
type: User
resolve: "@=mutation('createUser', [args])"
args:
username: String
email: String
info: String
updateUser:
type: User
resolve: "@=mutation('updateUser', [args])"
args:
username: String
email: String
info: String
input-object
types are designed to be used as arguments in other types. Basically, they are composite arguments, so
the property constraints are declared for each field unlike object
types, where the property constraints are
declared for each argument:
User:
type: input-object
config:
fields:
username:
type: String!
validation: # using an explicit list of constraints
- NotBlank: ~
- Length: { min: 6, max: 32 }
password:
type: String!
validation: App\Entity\User::$password # using a link
email:
type: String!
validation: # mixing both
link: App\Entity\User::$email
constraints:
- Email: ~
class constraints are declared 2 levels higher, under the config
key:
User:
type: input-object
config:
validation:
- Callback: [App\Validation\UserValidator, validate]
fields:
username:
type: String!
password:
type: String!
email:
type: String!
If you already have classes (e.g. Doctrine entities) with validation constraints applied to them, you can reuse these constraints in your configuration files by linking corresponding properties, getters or entire classes. What the link
key does is simply copy all constraints of the given target without any change and apply them to an argument/field.
A link
can have 4 different forms, each of which targets different parts of a class:
- property:
<ClassName>::$<propertyName>
- the$
symbol indicates a single class property. - getters:
<ClassName>::<propertyName>()
- the parentheses indicate all getters of the given property name. - property and getters:
<ClassName>::<propertyName>
- the absence of the$
and parentheses indicates a single property and all it's getters. - class:
<ClassName>
- the absence of a class member indicates an entire class.
for example:
- property:
App\Entity\User::$username
- copies constraints of the property$username
of the classUser
. - getters:
App\Entity\User::username()
- copies constraints of the gettersgetUsername()
,isUsername()
andhasUsername()
. - property and getters:
App\Entity\User::username
- copies constraints of the property$username
and it's gettersgetUsername()
,isUsername()
andhasUsername()
. - class:
App\Entity\User
- copies constraints applied to the entire classUser
.
Note: If you target only getters, then prefixes must be omitted. For example, if you want to target getters of the class
User
with the namesisChild()
andhasChildren()
, then the link would beApp\Entity\User::child()
.Only getters with the prefix
get
,has
, andis
will be searched.
Note: Linked constraints which work in a context (e.g. Expression or Callback) will NOT copy the context of the linked class, but instead will work in it's own. That means that the
this
variable won't point to the linked class instance, but will point to an object of the classValidationNode
representing your GraphQL type. See the How does it work? section for more details about internal work of the validation process.
Suppose you have the following class:
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @Assert\Callback({"App\Validation\PostValidator", "validate"})
*/
class Post
{
/**
* @Assert\NotBlank()
*/
private $title;
/**
* @Assert\Length(max=512)
*/
private $text;
/**
* @Assert\Length(min=5, max=10)
*/
public function getTitle(): string
{
return $this->title;
}
/**
* @Assert\EqualTo("Lorem Ipsum")
*/
public function hasTitle(): bool
{
return strlen($this->title) !== 0;
}
/**
* @Assert\Json()
*/
public function getText(): string
{
return $this->text;
}
}
Then you could link class members this way:
Mutation:
type: object
config:
fields:
editPost:
type: Post
resolve: "@=mutation('edit_post', [args])"
validation:
link: App\Entity\Post # targeting the class
args:
title:
type: String!
validation:
link: App\Entity\Post::title # property and getters
text:
type: String!
validation:
link: App\Entity\Post::$text # only property
or use the short form (omitting the link
key), which is equal to the config above:
# ...
validation: App\Entity\Post # targeting the class
args:
title:
type: String!
validation: App\Entity\Post::title # property and getters
text:
type: String!
validation: App\Entity\Post::$text # only property
# ...
The argument title
will get 3 assertions: NotBlank()
, Length(min=5, max=10)
and EqualTo("Lorem Ipsum")
, whereas the argument text
will only get Length(max=512)
. The method validate
of the class PostValidator
will also be called once, given an object representing the current GraphQL type.
When linking constraints, keep in mind that the validation context won't be inherited (copied). For example, suppose you have the following Doctrine entity:
namespace App\Entity;
/**
* @Assert\Callback("validate")
*/
class User
{
public static function validate()
{
// ...
}
}
and this config:
Mutation:
type: object
config:
fields:
createUser:
validation: App\Entity\User # linking
resolve: "@=res('createUser', [args])"
# ...
Now, when you try to validate the arguments in your resolver, it will throw an exception, because it will try to call a method with the name validate
on the object of class ValidationNode
, which doesn't have such. As explained in the section How does it work? each GraphQL type is represented by an object of class ValidationNode
during the validation process.
Linked constraints will be used as it is. This means that it's not possible to change any of their params including groups.
For example, if you link a property on class User
, then all copied constraints will be in the groups Default
and User
(unless other groups declared explicitly in the linked class).
The validation of arguments of the type input-object
, which are marked as cascade
, will be delegated to the embedded type. The nesting can be any depth.
Mutation:
type: object
config:
fields:
updateUser:
type: Post
resolve: "@=mutation('update_user', [args])"
args:
id:
type: ID!
address:
type: Address
validation: cascade
workPeriod:
type: Period
validation: cascade
Address:
type: input-object
config:
fields:
street:
type: String!
validation:
- Length: { min: 5, max: 15 }
city:
type: String!
validation:
- Choice: ['Berlin', 'New York', 'Moscow']
house:
type: Int!
validation:
- Positive: ~
Period:
type: input-object
config:
fields:
startDate:
type: String!
validation:
- Date: ~
endDate:
type: String!
validation:
- Date: ~
- GreaterThan:
propertyPath: 'startDate'
It is possible to organize constraints into validation groups.
By default, if you don't declare groups explicitly, every constraint of your type will be in 2 groups: Default and
the name of the type. For example, if the type's name is Mutation and the declaration of constraint is NotBlank: ~
(no explicit groups declared), then it automatically falls into 2 default groups: Default and Mutation. These
default groups will be removed, if you declare groups explicitly. Follow the
link for more details about validation groups in the Symfony
Validator Component.
Validation groups could be useful if you use a same input-object
type in different contexts and want it to be
validated differently (with different groups). Take a look at the following example:
Mutation:
type: object
config:
fields:
registerUser:
type: User
resolve: "@=('register_user')"
validationGroups: ['User']
args:
input:
type: UserInput!
validation: cascade
registerAdmin:
type: User
resolve: "@=('register_admin')"
validationGroups: ['Admin']
args:
input:
type: UserInput!
validation: cascade
UserInput:
type: input-object
config:
fields:
username:
type: String!
validation:
- Length: {min: 3, max: 15}
password:
type: String
validation:
- Length: {min: 4, max: 32, groups: 'User'}
- Length: {min: 10, max: 32, groups: 'Admin'}
As you can see the password
field of the UserInput
type has a same constraint applied to it twice, but with
different groups. The validationGroups
option ensures that validation will only use the onstraints that are listed
in it.
In case you inject the validator into the resolver (as described here), the validationGroups
option will be ignored. Instead you should pass groups directly to the injected validator. This approach could be
necessary in some few cases.
Let's take the example from the chapter Overview and edit the configuration to inject the validator
and
to use validation groups:
# config\graphql\types\Mutation.yaml
Mutation:
type: object
config:
fields:
register:
type: User
resolve: "@=mut('register', [args, validator])" # injecting validator
args:
username:
type: String!
validation:
- Length:
min: 6
max: 32
groups: ['registration']
password:
type: String!
validation:
- Length:
min: 8
max: 32
- IdenticalTo:
propertyPath: passwordRepeat
groups: ['registration']
passwordRepeat:
type: String!
emails:
type: "[String]"
validation:
- Unique: ~
- Count:
min: 1
max: 3
- All:
- Email: ~
birthday:
type: Birthday
validation: cascade
Birthday:
type: input-object
config:
fields:
day:
type: Int!
validation:
- Range: { min: 1, max: 31 }
month:
type: Int!
validation:
- Range: { min: 1, max: 12 }
year:
type: Int!
validation:
- Range: { min: 1900, max: today }
Here we injected the validator
variable into the register
resolver. By doing so we are turning the automatic
validation off to perform it inside the resolver (see Validating inside resolvers). The
injected instance of the InputValidator
class could be used in a resolver as follows:
namespace App\GraphQL\Mutation\Mutation
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface;
use Overblog\GraphQLBundle\Definition\Resolver\MutationInterface;
use Overblog\GraphQLBundle\Validator\InputValidator;
class UserResolver implements MutationInterface, AliasedInterface
{
public function register(Argument $args, InputValidator $validator)
{
/*
* Validates:
* - username against 'Length'
* - password againt 'IdenticalTo'
*/
$validator->validate('registration');
/*
* Validates:
* - password against 'Length'
* - emails against 'Unique', 'Count' and 'All'
* - birthday against 'Valid' (cascade).
* - day against 'Range'
* - month against 'Range'
* - year against 'Range'
*/
$validator->validate('Default');
// ... which is in this case equal to:
$validator->validate();
/**
* Validates only arguments in the 'Birthday' type
* against constraints with no explicit groups.
*/
$validator->validate('Birthdate');
// Validates all arguments in each type against all constraints.
$validator->validate(['registration', 'Default']);
// ... which is in this case equal to:
$validator->validate(['registration', 'Mutation', 'Birthdate']);
}
public static function getAliases(): array
{
return ['register' => 'register'];
}
}
Note: All arguments marked for cascade validation will be automatically validated against the Valid constraint.
You can use GroupSequence
constraint to sequentially apply validation groups. See the official documentation for more details.
Applying group sequences is similar to normal constraints:
Mutation:
type: object
config:
validation:
- GroupSequence: ['group1', 'group2']
fields:
create:
# ...
update:
# ...
or for each field:
Mutation:
type: object
config:
fields:
create:
validation:
- GroupSequence: ['group1', 'group2']
# ...
update:
validation:
- GroupSequence: ['group3', 'group4']
# ...
You can turn the auto-validation off by injecting the validator into your resolver. This can be useful if you want to do something before the actual validation happens or customize other aspects, for example validate data multiple times with different groups or make the validation conditional.
Here is how you can inject the validator:
Mutation:
type: object
config:
fields:
register:
resolve: "@=mutation('register', [args, validator])"
# ...
resolver:
namespace App\GraphQL\Mutation\Mutation
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface;
use Overblog\GraphQLBundle\Definition\Resolver\MutationInterface;
use Overblog\GraphQLBundle\Validator\InputValidator;
class UserResolver implements MutationInterface, AliasedInterface
{
public function register(Argument $args, InputValidator $validator): User
{
// This line executes a validation process and throws ArgumentsValidationException
// on fail. The client will then get a well formatted error message.
$validator->validate();
// To validate with groups just pass a string or an array
$validator->validate(['my_group', 'group2']);
// Or use a short syntax, which is equal to $validator->validate().
// This is possible thanks to the __invoke magic method.
$validator();
// The code below won't be reached if one of the validations above fails
$user = $this->userManager->createUser($args);
$this->userManager->save($user);
return $user;
}
public static function getAliases(): array
{
return ['register' => 'register'];
}
}
If you want to prevent the validator to automatically throw an exception just pass false
as the second argument. It
will return an instance of the ConstraintViolationList
class instead:
$errors = $validator->validate('my_group', false);
// Do something with errors
if ($errors->count() > 0) {
// ...
}
It's possible to inject the errors
variable with all validation violations instead of automatic exception throw:
Mutation:
type: object
config:
fields:
register:
resolve: "@=mutation('register', [args, errors])"
# ...
namespace App\GraphQL\Mutation\Mutation
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface;
use Overblog\GraphQLBundle\Definition\Resolver\MutationInterface;
use Overblog\GraphQLBundle\Error\ResolveErrors;
class UserResolver implements MutationInterface, AliasedInterface
{
public function register(Argument $args, ResolveErrors $errors): User
{
$violations = $errors->getValidationErrors();
// ...
}
public static function getAliases(): array
{
return ['register' => 'register'];
}
}
By default the InputValidator
throws an ArgumentsValidationException
, which will be caught and serialized into
a readable response. The GraphQL specification defines a
certain shape of all errors returned in the response. According to it all validation violations are to be found under
the path errors[index].extensions.validation
of the response object.
Example of a response with validation errors:
{
"data": null,
"errors": [{
"message": "validation",
"extensions": {
"category": "arguments_validation_error",
"validation": {
"username": [
{
"message": "This value should be equal to 'Lorem Ipsum'.",
"code": "478618a7-95ba-473d-9101-cabd45e49115"
}
],
"email": [
{
"message": "This value is not a valid email address.",
"code": "bd79c0ab-ddba-46cc-a703-a7a4b08de310"
},
{
"message": "This value is too short. It should have 5 character or more.",
"code": "9ff3fdc4-b214-49db-8718-39c315e33d45"
}
]
}
},
"locations": [
{"line": 3, "column": 17}
],
"path": ["linkedConstraintsValidation"]
}]
}
The codes in the response could be used to perform a client-side translation of the validation violations.
You can customize the output by passing false
as a second argument to the validate
method.
This will prevent an exception to be thrown and a ConstraintViolationList
object will be returned instead:
public function resolver(InputValidator $validator)
{
$errors = $validator->validate(null, false);
// Use $errors to build your own exception
...
}
See more about Error handling.
All validation violations are automatically translated from the validators
domain.
Example:
Mutation:
type: object
config:
fields:
register:
type: User
resolve: "@=mutation('register', [args])"
args:
username:
type: String!
validation:
- Length:
min: 6
max: 32
minMessage: "register.username.length.min"
maxMessage: "register.username.length.max"
password:
type: String!
validation:
- Length:
min: 8
max: 32
minMessage: "register.password.length.min"
maxMessage: "register.password.length.max"
- IdenticalTo:
propertyPath: passwordRepeat
message: "register.password.identical"
passwordRepeat:
type: String!
Create a translation resource for the validators
domain:
# translations\validators.en.yaml
register.username.length.min: "The username should have {{ length }} characters or more"
register.username.length.max: "The username should have {{ length }} characters or less"
register.password.length.min: "The password should have {{ length }} characters or more"
register.password:length.max: "The password should have {{ length }} characters or less"
register.password.identical: "The passwords are not equal."
or use another format, which is more readable:
# translations\validators.en.yaml
register:
username:
length:
min: "The username should have {{ length }} characters or more"
max: "The username should have {{ length }} characters or less"
password:
identical: "The passwords are not equal."
length:
min: "The password should have {{ length }} characters or more"
max: "The password should have {{ length }} characters or less"
To translate into other languages just create additional translation resource with a required suffix, for example validators.de.yaml
for German and validators.ru.yaml
for Russian.
This bundle comes with pre-registered expression functions and variables. By default the Expression
constraint has no access to them, because it uses the default instance of the ExpressionLanguage
class. In order to tell the Expression
constraint to use the instance of this bundle, add the following config to the services.yaml
to rewrite the default service declaration:
validator.expression:
class: Overblog\GraphQLBundle\Validator\Constraints\ExpressionValidator
arguments: ['@Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage']
tags:
- name: validator.constraint_validator
alias: validator.expression
This will make possible to use all functions, registered in this bundle:
# ...
args:
username:
type: String!
validation:
- Expression: "service('my_service').entityExists(value)"
Note Expressions in the
Expression
constraint shouldn't be prefixed with@=
.
and it's also possible to use variables from the resolver context (value
, args
, context
and info
):
# ...
args:
username:
type: String!
validation:
- Expression: "service('my_service').isValid(value, args, info, context, parentValue)"
Note
As you might, know the
Expression
constraint has one built-in variable calledvalue
. In order to avoid name conflicts, the resolver variablevalue
is renamed toparentValue
, when using in theExpression
constraint.In short: the
value
represents currently validated input data, andparentValue
represents the data returned by the parent resolver.
The ValidationNode class is used internally during the validation process. See the How does it work? section for more details.
This class has methods that may be useful when using such constraints as Callback
or Expression
, which work in a context.
getType(): GraphQL\Type\Definition\Type
Returns the Type
object associated with current validation node.
getName(): string
Returns the name of the associated Type object. Shorthand for getType()->name
.
getFieldName(): string|null
Returns the field name if the object is associated with an object
type, otherwise returns null
getParent(): ValidationNode|null
Returns the parent node.
findParent(string $name): ValidationNode|null
Traverses up through parent nodes and returns first object with matching name.
In this example we are checking if the value of the field shownEmail
is contained in the emails
array. We are using the method getParent()
to access a field of the type Mutation
from within the type Profile
:
Mutation:
type: object
config:
fields:
registerUser:
type: User
resolve: "@=resolver('register_user', [args])"
args:
username: String!
password: String!
passwordRepeat: String!
emails:
type: "[String]"
validation:
- Unique: ~
- Count:
min: 1
max: 5
- All:
- Email: ~
profile:
type: Profile
validation: cascade
Profile:
type: input-object
config:
fields:
shownEmail:
type: String!
validation:
- Expression: "value in this.getParent().emails"
# ...
In this example we are applying a same validation constraint to both createUser
and createAdmin
resolvers.
Mutation:
type: object
config:
validation:
- Callback: [App\Validation\Validator, validate]
fields:
createUser:
type: User
resolve: "@=resolver('createUser', [args])"
args:
username: String!
password: String!
passwordRepeat: String!
email: String!
createAdmin:
type: User
resolve: "@=resolver('createAdmin', [args])"
args:
username: String!
password: String!
passwordRepeat: String!
email: String!
To find out which of 2 fields is being validated inside the method, we can use method getFieldName
:
namespace App\Validation;
use Overblog\GraphQLBundle\Validator\ValidationNode;
// ...
public static function validate(ValidationNode $object, ExecutionContextInterface $context, $payload): void
{
switch ($object->getFieldName()) {
case 'createUser':
// Validation logic for users
break;
case 'createAdmin':
// Validation logic for admins
break;
default:
// Validation logic for all other fields
}
}
// ...
The current implementation of InputValidator
works only for schema types declared in yaml files. Types declared with annotations or with GraphQL schema language are not supported. This can be changed in the future versions.
The annotations system of this bundle has its own limited validation implementation, see the Arguments Transformer section for more details.
These are the validation constraints, which are not currently supported:
- File
- Image
- UniqueEntity
- Traverse - although you can use this constraint in your type definitions, it would make no sense, as nested objects will be automatically validated with the
Valid
constraint. See How does it work? section to get familiar with the internals.