How It Works
The Core Philosophy: Single Source of Truth
This bundle is built on one fundamental principle: your JSON Schema is the single source of truth for your API contracts.
The Problem with Traditional Approaches
In most frameworks, you write validation rules separately from documentation:
// Validation in code
class UserCreateDto {
#[Assert\NotBlank]
#[Assert\Email]
private string $email;
}
// Documentation (separate, can drift)
/**
* @OA\Property(
* property="email",
* type="string",
* format="email",
* description="User email"
* )
*/The problem: Two sources of truth can diverge. You change validation, forget to update docs. Your API documentation lies.
Our Solution: One Schema, Everything Else Follows
With this bundle, you write one JSON Schema:
{
"properties": {
"body": {
"properties": {
"email": {
"type": "string",
"format": "email",
"description": "User email"
}
},
"required": ["email"]
}
}
}This single schema:
- ✅ Validates incoming requests
- ✅ Generates OpenAPI documentation
- ✅ Types your DTOs
- ✅ Always in sync (physically impossible to diverge)
JSON Schema = OpenAPI 3+ (Not Conversion!)
Critical understanding: We don't "convert" JSON Schema to OpenAPI.
OpenAPI 3.0+ natively uses JSON Schema for request/response bodies:
# This IS the OpenAPI 3.1 specification format
requestBody:
content:
application/json:
schema:
# This is JSON Schema, part of OpenAPI spec
type: object
properties:
email:
type: string
format: emailWhen you use #[MapRequest('user.json')], the bundle:
- Reads your JSON Schema
- Embeds it directly into OpenAPI spec (no transformation)
- Uses the same schema to validate requests
Result: Documentation and validation use the exact same schema object. They cannot desync.
Request-Level Validation
Unlike other frameworks that validate only request body, this bundle validates the entire HTTP request as one unified contract:
{
"type": "object",
"properties": {
"body": {
"type": "object",
"properties": {
"name": {"type": "string"}
}
},
"query": {
"type": "object",
"properties": {
"page": {"type": "integer", "minimum": 1}
}
},
"path": {
"type": "object",
"properties": {
"id": {"type": "string", "format": "uuid"}
}
},
"headers": {
"type": "object",
"properties": {
"authorization": {"type": "string", "pattern": "^Bearer .+"}
}
}
}
}One schema defines the complete request contract: body + query + path + headers.
Contract-First Development
This bundle enables true Contract-First Development:
Traditional Flow (Code-First):
- Write code with validation annotations
- Generate documentation from code
- Hope docs match reality
- Frontend sees outdated docs, breaks
Our Flow (Contract-First):
- Design API contract (JSON Schema) - discuss with frontend team
- Validate all requests automatically against contract
- Document automatically in OpenAPI (same schema)
- Implement business logic
The schema is law. Code must conform to it. Documentation reflects it exactly.
How MapRequest Works
When you write:
#[Route('/api/users', methods: ['POST'])]
public function create(#[MapRequest('user-create.json')] UserCreateDto $user): JsonResponse
{
// $user is validated and typed
}Here's what happens:
- Request arrives at your controller
- Before your method executes, bundle intercepts it
- Normalizes request into unified structure:php
[ 'body' => /* parsed JSON */, 'query' => /* query params */, 'path' => /* route params */, 'headers' => /* headers */ ] - Validates against
user-create.jsonschema - If valid: Creates
UserCreateDtowith validated data - If invalid: Returns 400 JSON error response (your method never runs)
- Your method receives guaranteed-valid typed DTO
Guaranteed Documentation Accuracy
The MapRequestArgumentDescriber (when nelmio/api-doc-bundle is installed):
- Detects
#[MapRequest]attribute - Loads the referenced JSON Schema
- Embeds exact same schema into OpenAPI specification
- Swagger UI displays it
Physical guarantee: The schema shown in docs is byte-for-byte identical to the schema used for validation.
It's not a copy. It's not a conversion. It's the same file.
Why This Matters
Before (Traditional):
- ❌ Validation rules in PHP annotations
- ❌ Documentation in PHPDoc/OpenAPI annotations
- ❌ Can drift apart
- ❌ No single source of truth
- ❌ Frontend gets wrong expectations
After (This Bundle):
- ✅ One JSON Schema file
- ✅ Validates requests
- ✅ Generates docs
- ✅ Types DTOs
- ✅ Cannot drift (same file)
- ✅ Frontend and backend work from identical contract
Type Safety Bonus
Because validation happens before your code runs:
public function create(#[MapRequest('user.json')] UserCreateDto $user): JsonResponse
{
// $user->email is GUARANTEED to be:
// - present (if required in schema)
// - a string (if type: string in schema)
// - valid email format (if format: email in schema)
// No null checks needed!
// No type checks needed!
// Schema already validated everything!
}Your code works with pre-validated, typed data. No defensive programming needed.
Next Steps
Now that you understand the philosophy:
- Installation → - Set up the bundle
- Quick Start → - Your first schema in 5 minutes
- Schema Basics → - Learn JSON Schema syntax
- OpenAPI Integration → - See automatic documentation in action