Configuration
This page covers all configuration options available in the Symfony JSON Schema Validation bundle.
Basic Configuration
Create or update your bundle configuration file:
# config/packages/outcomer_validation.yaml
outcomer_validation:
schemas:
- path: '%kernel.project_dir%/config/validation/schemas'
domain: 'https://your-domain.com/schemas'
filters:
unique_email: App\Filter\UniqueEmailFilter
valid_promo_code: App\Filter\PromoCodeFilterConfiguration Reference
schemas
Type: array of {path, domain}
Default: []
Path/domain pairs to register. Each pair works the same way schemas_path/schema_domain used to: path is a directory of JSON Schema files, domain is the base URL used to build their auto-generated schema IDs (and to resolve $ref inside them). Register more than one pair if you have schemas in more than one directory - e.g. one for your app's own schemas, another for a shared/vendored set.
outcomer_validation:
schemas:
- path: '%kernel.project_dir%/config/validation/schemas'
domain: 'https://api.example.com/schemas'
- path: '%kernel.project_dir%/config/validation/shared-schemas'
domain: 'https://api.example.com/shared-schemas'Deprecated: schemas_path / schema_domain
The single-pair schemas_path and schema_domain options are deprecated as of 4.0 in favor of schemas. They still work today but will be removed in a future major version - migrate by wrapping your existing values into a single schemas entry, as shown above.
auto_cast_query
Type: boolDefault: true
Automatically casts numeric/boolean-looking strings in query parameters (e.g. "20" → 20) before validation, so schemas can use "type": "integer" for query params without every client having to send real JSON types.
outcomer_validation:
auto_cast_query: falseauto_cast_path
Type: boolDefault: true
Same casting behavior as auto_cast_query, applied to route path parameters. Headers are never auto-cast.
outcomer_validation:
auto_cast_path: falsefilters
Type: array
Default: {}
Custom validation filters for dynamic validation logic. Important: Filters do not modify data - they return true (valid) or false (invalid).
outcomer_validation:
filters:
unique_email: App\Filter\UniqueEmailFilter
valid_promo_code: App\Filter\PromoCodeFilter
user_exists: App\Filter\UserExistsFilterCustom Filters
Filters enable dynamic validation that goes beyond JSON Schema's static rules. Use them for:
- Database uniqueness checks
- External API validations
- Business logic constraints
Key concept: Filters validate, not transform. They return boolean results.
Important: Filters must implement Opis\JsonSchema\Filter interface from Opis JSON Schema library.
Example: Email Uniqueness Check
<?php
namespace App\Filter;
use App\Repository\UserRepository;
use Opis\JsonSchema\Filter;
use Opis\JsonSchema\ValidationContext;
use Opis\JsonSchema\Schema;
class UniqueEmailFilter implements Filter
{
public function __construct(
private readonly UserRepository $userRepository
) {}
public function validate(ValidationContext $context, Schema $schema, array $args = []): bool
{
$value = $context->currentData();
// Return true if valid (email is unique)
// Return false if invalid (email already exists)
if (!is_string($value)) {
return true; // Not our concern, let JSON Schema handle type validation
}
return !$this->userRepository->emailExists($value);
}
}Example: Promo Code Validation with Parameters
<?php
namespace App\Filter;
use App\Service\PromoCodeService;
use Opis\JsonSchema\Filter;
use Opis\JsonSchema\ValidationContext;
use Opis\JsonSchema\Schema;
class PromoCodeFilter implements Filter
{
public function __construct(
private readonly PromoCodeService $promoService
) {}
public function validate(ValidationContext $context, Schema $schema, array $args = []): bool
{
$value = $context->currentData();
if (!is_string($value)) {
return true;
}
// Access parameters from $vars
$requiredLength = $args['minLength'] ?? 6;
$prefix = $args['prefix'] ?? null;
return $this->promoService->isValid($value, $requiredLength, $prefix);
}
}Schema with filter parameters:
{
"properties": {
"body": {
"properties": {
"promoCode": {
"type": "string",
"$filters": {
"$func": "valid_promo_code",
"$vars": {
"minLength": 8,
"prefix": "SUMMER"
}
}
}
}
}
}
}Registering Filters
Step 1: Register filter in configuration with a unique name:
# config/packages/outcomer_validation.yaml
outcomer_validation:
filters:
unique_email: App\Filter\UniqueEmailFilter # Key = filter name
valid_promo_code: App\Filter\PromoCodeFilterStep 2: Use the same name in $func in your schema:
{
"properties": {
"body": {
"properties": {
"email": {
"type": "string",
"format": "email",
"$filters": {
"$func": "unique_email" // Must match key in configuration
}
},
"promoCode": {
"type": "string",
"$filters": {
"$func": "valid_promo_code", // Must match key in configuration
"$vars": {
"minLength": 8,
"prefix": "SUMMER"
}
}
}
}
}
}
}Filter Name Matching
The value in "$func": "unique_email" must exactly match the key in your outcomer_validation.filters configuration.
Configuration key: unique_email: → Schema: "$func": "unique_email"
Validation flow:
- JSON Schema validates type and format
- If valid, bundle looks up filter by name from
$func - Filter executes with parameters from
$vars(if any) - If filter returns
false, validation fails
Environment-Specific Configuration
You can have different configurations for different environments:
# config/packages/dev/outcomer_validation.yaml
outcomer_validation:
schemas_path: '%kernel.project_dir%/dev-schemas'# config/packages/prod/outcomer_validation.yaml
outcomer_validation:
schema_domain: 'https://production-api.com/schemas'Error Handling
The bundle throws a domain ValidationException internally; when #[MapRequest]'s triggerResponse is true (the default), MapRequestResolver wraps it into HttpValidationException before throwing. To format it as a JSON response, you need to set up an exception listener for HttpValidationException:
// src/EventListener/ExceptionListener.php
namespace App\EventListener;
use Outcomer\ValidationBundle\Exception\HttpValidationException;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
#[AsEventListener(event: KernelEvents::EXCEPTION, method: 'handleException', priority: 0)]
class ExceptionListener
{
public function handleException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
if ($exception instanceof HttpValidationException) {
$response = new JsonResponse(
data: [
'message' => $exception->getMessage(),
'errors' => $exception->getValidationErrors(),
],
status: $exception->getStatusCode()
);
$event->setResponse($response);
}
}
}Response format:
{
"message": "Request data is invalid",
"errors": {
"/body/email": [
{
"expected": "The data must match the 'email' format",
"recieved": "invalid@"
}
],
"/body/name": [
{
"expected": "String should have a minimum length of 2",
"recieved": "A"
}
]
}
}Next Steps
- Schema Basics → - Learn JSON Schema fundamentals
- DTO Injection → - Use custom DTOs