cvek/apip-graphql-validator-formatter
Installation Add the package via Composer:
composer require cvek/apip-graphql-validator-formatter
Publish the config (if needed):
php artisan vendor:publish --provider="Cvek\GraphQLValidatorFormatter\GraphQLValidatorFormatterServiceProvider"
Basic Usage
Register the formatter in your API Platform GraphQL configuration (config/graphql.php):
'validation' => [
'error_formatter' => \Cvek\GraphQLValidatorFormatter\ErrorFormatter::class,
],
First Use Case Trigger a GraphQL mutation with invalid input (e.g., missing required field). The package will automatically format validation errors into a structured JSON response, improving client-side error handling.
Validation Error Handling
mutation CreateUser($name: String!, $email: String!) {
createUser(input: { name: $name, email: $email }) {
errors {
field
message
code
}
}
}
{
"errors": [
{
"field": "email",
"message": "This value should not be blank.",
"code": "NOT_BLANK"
}
]
}
Custom Error Mapping Extend the formatter to map Symfony validation errors to custom GraphQL error types:
// app/GraphQL/ErrorFormatter.php
use Cvek\GraphQLValidatorFormatter\ErrorFormatter;
class CustomErrorFormatter extends ErrorFormatter {
protected function formatError(ConstraintViolationInterface $error) {
return [
'field' => $error->getPropertyPath(),
'message' => $this->translateMessage($error),
'code' => $error->getConstraint()->getName(),
'custom_field' => 'your_value' // Add custom data
];
}
}
Register it in config/graphql.php:
'validation' => [
'error_formatter' => \App\GraphQL\CustomErrorFormatter::class,
],
Combining with API Platform Filters
Use the formatter with API Platform’s validation_groups or custom constraints:
# config/validator/validation.yaml
App\Entity\User:
constraints:
- Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: { fields: email, message: "Email already taken." }
$error = $this->createMock(ConstraintViolationInterface::class);
$error->method('getPropertyPath')->willReturn('email');
$error->method('getMessage')->willReturn('Invalid email.');
$formatter = new ErrorFormatter();
$result = $formatter->format($error); // Test structured output
// Example: Display errors in a form
errors.forEach(error => {
console.log(`Field: ${error.field}, Message: ${error.message}`);
});
Configuration Overrides
config/graphql.php. API Platform may override settings in config/packages/api_platform.yaml.config/graphql.php after other configurations.Nested Validation Errors
input: { user: { name: "" } }) by default.ErrorFormatter to recursively process nested violations:
public function format(ConstraintViolationListInterface $errors) {
$formatted = [];
foreach ($errors as $error) {
$path = explode('.', $error->getPropertyPath());
$formatted = $this->buildNestedErrors($formatted, $path, $error);
}
return $formatted;
}
protected function buildNestedErrors(array $errors, array $path, ConstraintViolationInterface $error) {
$current = &$errors;
foreach ($path as $key) {
if (!isset($current[$key])) {
$current[$key] = [];
}
$current = &$current[$key];
}
$current[] = $this->formatSingleError($error);
return $errors;
}
Translation Issues
translator service is configured in Laravel.app/Resources/translations/validation.en.yaml).Performance
formatError(). For large payloads, lazy-load error details or use caching.// In ErrorFormatter
public function format(ConstraintViolationListInterface $errors) {
\Log::debug('Raw errors:', $errors->getIterator()->getArrayCopy());
return parent::format($errors);
}
api_platform.validation.error events to inspect validation failures:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use ApiPlatform\Core\EventListener\ValidationErrorListener;
class ValidationSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
ValidationErrorListener::EVENT_NAME => 'onValidationError',
];
}
public function onValidationError(ValidationErrorEvent $event) {
\Log::debug('Validation errors:', $event->getErrors());
}
}
Custom Error Codes Add custom error codes to constraints and map them in the formatter:
# config/validator/validation.yaml
App\Entity\User:
constraints:
- App\Validator\Constraints\CustomConstraint: { code: "CUSTOM_ERROR", message: "Custom error message." }
// In ErrorFormatter
protected function formatError(ConstraintViolationInterface $error) {
if ($error->getConstraint()->getName() === 'custom_constraint') {
return [
'field' => $error->getPropertyPath(),
'message' => $error->getMessage(),
'code' => 'CUSTOM_ERROR',
'metadata' => ['custom_field' => 'value'] // Extend with custom data
];
}
return parent::formatError($error);
}
Localization Override error messages per locale by extending the formatter:
protected function translateMessage(ConstraintViolationInterface $error) {
$translator = app('translator');
return $translator->trans(
$error->getMessage(),
[],
'validation'
);
}
GraphQL Directives Use the formatter with GraphQL directives to conditionally apply validation:
directive @validate(input: String!) on FIELD_DEFINITION
type Mutation {
createUser(input: UserInput!): User @validate(input: "required")
}
How can I help you explore Laravel packages today?