## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require chamber-orchestra/form-bundle
Enable the bundle in config/bundles.php:
return [
// ...
ChamberOrchestra\FormBundle\FormBundle::class => ['all' => true],
];
First Use Case:
Create a JSON-first API form type for a User entity:
// src/Form/Type/UserType.php
namespace App\Form\Type;
use ChamberOrchestra\FormBundle\Type\JsonFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', JsonFormType::class, [
'json_schema' => [
'type' => 'string',
'minLength' => 2,
],
])
->add('email', JsonFormType::class, [
'json_schema' => [
'type' => 'string',
'format' => 'email',
],
]);
}
}
Controller Integration:
Use the JsonFormControllerTrait to handle requests:
// src/Controller/UserController.php
use ChamberOrchestra\FormBundle\Controller\JsonFormControllerTrait;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class UserController
{
use JsonFormControllerTrait;
#[Route('/users', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
return $this->handleJsonFormRequest(
$request,
UserType::class,
'user',
'user_created'
);
}
}
Key Files to Reference:
src/Type/JsonFormType.php (core form type logic)src/Transformer/JsonTransformer.php (data transformation)src/Validator/JsonSchemaValidator.php (validation rules)JSON Schema Validation:
Leverage JsonFormType with embedded JSON Schema for validation:
$builder->add('address', JsonFormType::class, [
'json_schema' => [
'type' => 'object',
'properties' => [
'street' => ['type' => 'string'],
'city' => ['type' => 'string'],
],
'required' => ['street', 'city'],
],
]);
Nested Forms:
Use JsonFormType recursively for nested objects:
$builder->add('profile', JsonFormType::class, [
'json_schema' => ['$ref' => '#/components/schemas/Profile'],
'mapped' => false, // Opt-out of automatic binding
]);
Custom Transformers:
Extend JsonTransformer for domain-specific logic:
// src/Transformer/CustomUserTransformer.php
use ChamberOrchestra\FormBundle\Transformer\JsonTransformer;
class CustomUserTransformer extends JsonTransformer
{
public function transform($data): array
{
if (isset($data['raw_email'])) {
$data['email'] = strtolower($data['raw_email']);
}
return parent::transform($data);
}
}
Register in services.yaml:
services:
App\Transformer\CustomUserTransformer:
tags: [chamber_orchestra.form.transformer]
Request Handling:
Use JsonFormControllerTrait for standardized workflows:
// Handles POST/PUT/PATCH with automatic:
// - JSON parsing
// - Form creation
// - Validation
// - Error response (RFC 9457)
return $this->handleJsonFormRequest(
$request,
UserType::class,
'user', // form name
'user_updated', // success route
['csrf_protection' => false] // options
);
Partial Updates:
Enable PATCH support via partial option:
$this->handleJsonFormRequest(
$request,
UserType::class,
'user',
'user_updated',
['partial' => true]
);
Error Responses:
Customize problem details via error_mapper:
# config/services.yaml
services:
App\Error\CustomProblemDetailsMapper:
arguments: ['@chamber_orchestra.form.error_mapper.default']
tags: [chamber_orchestra.form.error_mapper]
Doctrine + JSON Schema: Combine constraints:
use Symfony\Component\Validator\Constraints as Assert;
$builder->add('age', JsonFormType::class, [
'json_schema' => ['type' => 'integer', 'minimum' => 0],
'constraints' => [
new Assert\NotBlank(),
new Assert\LessThan(150),
],
]);
Custom Validators:
Extend JsonSchemaValidator:
// src/Validator/CustomValidator.php
use ChamberOrchestra\FormBundle\Validator\JsonSchemaValidator;
class CustomValidator extends JsonSchemaValidator
{
public function validate($value, Constraint $constraint): void
{
if ($value === 'admin') {
$this->context->addViolation($constraint->message);
}
parent::validate($value, $constraint);
}
}
Register in services.yaml:
services:
App\Validator\CustomValidator:
tags: [validator.constraint_validator]
Symfony UX:
Pair with symfony/ux for live updates:
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
#[AsLiveComponent('user_form')]
class UserForm extends AbstractType { ... }
API Platform:
Use JsonFormType for input normalization:
# config/api_platform/resources.yaml
App\Entity\User:
input_form: App\Form\Type\UserType
Messenger: Dispatch events on form submission:
use ChamberOrchestra\FormBundle\Event\FormSubmittedEvent;
$dispatcher->addListener(FormSubmittedEvent::class, function (FormSubmittedEvent $event) {
$this->messageBus->dispatch(new UserCreatedEvent($event->getData()));
});
Null Handling:
JSON Schema defaults to null for missing fields. Explicitly mark required fields:
'json_schema' => [
'type' => 'object',
'required' => ['name'], // Forces presence
],
Circular References:
Avoid infinite loops in nested forms. Use mapped: false for non-entity fields:
$builder->add('metadata', JsonFormType::class, ['mapped' => false]);
CSRF Protection: Disable for API endpoints (but enable for web forms):
$this->handleJsonFormRequest(..., ['csrf_protection' => false]);
Doctrine Events:
Clear the FormInterface after submission to avoid stale data:
$form->submit($data);
if ($form->isSubmitted() && $form->isValid()) {
$entity = $form->getData();
$form->submit(null); // Reset form
}
Validation Errors:
Inspect the ProblemDetails object:
$this->handleJsonFormRequest(..., null, null, null, [
'error_mapper' => 'custom_mapper',
'debug' => true, // Logs full error details
]);
Transformer Issues:
Override JsonTransformer to log raw input:
class DebugTransformer extends JsonTransformer
{
public function transform($data): array
{
error_log('Raw data:', $data);
return parent::transform($data);
}
}
Schema Validation: Validate schemas independently:
use ChamberOrchestra\FormBundle\Validator\JsonSchemaValidator;
$validator = new JsonSchemaValidator();
$validator->validate($data, ['type' => 'object']);
Service Overrides: Prefer tagging over direct service replacement:
# Override the default transformer
services:
ChamberOrchestra\FormBundle\Transformer\JsonTransformer:
class: App\Transformer\CustomUserTransformer
JSON Schema Draft: Set the default draft in `config/packages/ch
How can I help you explore Laravel packages today?