besmartand-pro/graphqlite-symfony-validator-bridge
Bridge package connecting Symfony Validator with GraphQLite, enabling automatic validation of GraphQL input/arguments using Symfony constraints and returning structured validation errors in GraphQL responses. Suitable for Symfony apps using GraphQLite.
Leverage Symfony’s annotations for type-hinted validation in GraphQLite schemas. Example:
use GraphQL\Type\Definition\Type;
use Besmartand\GraphQLite\SymfonyValidatorBridge\Annotation\ValidatedInputType;
use Doctrine\Common\Annotations\Annotation\Target;
/**
* @ValidatedInputType(
* constraints={
* @Assert\Email(),
* @Assert\NotBlank()
* }
* )
*/
class UserInputType extends InputType {
public function __construct() {
parent::__construct([
'name' => 'UserInput',
'fields' => [
'email' => Type::string(),
'password' => Type::string(),
],
]);
}
}
Workflow:
@ValidatedInputType.ValidatorBridge in resolvers to auto-validate:
$validator = app(ValidatorBridge::class);
$validator->validate($args); // Automatically checks annotations
For non-annotated projects, define constraints externally:
config/validation/user.yaml:
UserInput:
email: { required: true, type: email }
password: { required: true, min: 8 }
Resolver Integration:
$validator = app(ValidatorBridge::class);
$errors = $validator->validate($args, 'UserInput'); // Loads from YAML
Validate complex types recursively: Schema:
$addressType = new InputType([
'name' => 'AddressInput',
'fields' => [
'street' => Type::string(),
'city' => Type::string(),
],
'validator' => function (ValidatorBridge $validator, array $input) {
return $validator->validate($input, [
'street' => 'required|string',
'city' => 'required|string',
]);
},
]);
$userType = new InputType([
'name' => 'UserInput',
'fields' => [
'name' => Type::string(),
'address' => $addressType,
],
]);
Resolver:
$validator = app(ValidatorBridge::class);
$validator->validate($args['user'], [
'name' => 'required',
'address' => new CallbackConstraint(function ($address) {
return $validator->validate($address, [
'street' => 'required',
'city' => 'required',
]);
}),
]);
Extend Symfony’s constraints for domain-specific rules: Custom Constraint:
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class ValidLaravelUser extends Constraint {
public $message = 'This user is not active.';
public function validatedBy() { return 'valid_laravel_user'; }
}
Validator Service:
use Symfony\Component\Validator\Constraints\Callback;
$validator->validate($user, [
'id' => new ValidLaravelUser(),
'email' => new Callback(function ($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}),
]);
Convert Symfony validation errors to GraphQL-compatible format:
use Besmartand\GraphQLite\SymfonyValidatorBridge\Exception\ValidationException;
try {
$validator->validate($args);
} catch (ValidationException $e) {
throw new GraphQLValidationError([
'errors' => array_map(function ($error) {
return [
'field' => $error->getPropertyPath(),
'message' => $error->getMessage(),
];
}, $e->getErrors()),
]);
}
$validator->setConstraintCache(new FileCache('/path/to/cache'));
$validator->validate($args, [
'name' => 'required',
'optionalField' => new LazyConstraint(function () {
return new Assert\NotBlank();
}),
]);
Reuse existing Laravel validation logic:
use App\Http\Requests\StoreUserRequest;
$validator = app(ValidatorBridge::class);
$errors = $validator->validate($args, StoreUserRequest::rules());
Unit-test constraints independently:
public function testEmailValidation()
{
$validator = $this->app->make(ValidatorBridge::class);
$errors = $validator->validate(['email' => 'invalid'], [
'email' => 'email',
]);
$this->assertCount(1, $errors);
}
Annotation Parsing Failures
composer dump-autoload is run and doctrine/annotations is installed:
composer require doctrine/annotations
composer dump-autoload
Circular Dependencies in Validation
CallbackConstraint with depth limits or break cycles manually.Symfony Validator Initialization
$this->app->bind('validator', function () {
return Validation::makeContainerValidator();
});
Error Message Localization
$validator = app(ValidatorBridge::class);
$validator->setTranslator($this->app->make('translator'));
GraphQLite Schema Caching
php artisan graphqlite:clear-cache
Constraint Overrides
ValidatorBridge:
$validator->setConstraintSourcePriority('annotation'); // or 'yaml'
Enable Symfony Validator Debug Mode
$validator->setDebug(true);
storage/logs/validator.log.Inspect Validation Groups Use named groups to isolate validation:
$validator->validate($args, 'UserInput', ['group' => 'create']);
Validate Constraints Independently Test constraints in isolation:
$constraint = new Assert\Email();
$validator->validate('test@example.com', $constraint);
GraphQL Error Tracing Enable GraphQLite’s error tracing:
$schema->setErrorFormatter(function ($error) {
return [
'message' => $error->getMessage(),
'path' => $error->getPath(),
'extensions' => [
'validator' => $error->getValidatorErrors(),
],
];
});
Custom Error Formats
Extend GraphQLValidationError to format errors for your API:
class CustomValidationError extends GraphQLValidationError {
public function __construct(array $errors) {
parent::__construct([
'errors' => array_map(function ($error) {
return [
'field' => $error->getPropertyPath(),
'message' => __($error->getMessage()),
'code' => $error->getCode(),
];
}, $errors),
]);
}
}
Dynamic Constraint Loading Load constraints from a database or API:
$validator->setConstraintLoader(function ($type) {
return DB::table('validation_rules')->where('type', $type)->get();
});
Integration with Laravel Policies Validate against Laravel’s authorization:
$validator->validate($user, [
'id' => new Callback(function ($id) {
return auth()->user()->can('update', User::find($id));
}),
]);
Validation Middleware Create a GraphQL middleware for global validation:
$schema->addMiddleware(function ($root, $args, $context, $info) {
$validator = app(ValidatorBridge::class);
$validator->validate($args, 'GlobalRules');
});
$this->app->afterResolving('validator',
How can I help you explore Laravel packages today?