A Symfony 8 bundle that simplifies JSON-first form handling for REST APIs. It provides controller traits for the submit/validate/response flow, specialized API form types with CSRF disabled, reusable data transformers, Doctrine-backed uniqueness validation, and structured error responses following RFC 9457 (Problem Details for HTTP APIs).
FormTrait, ApiFormTrait) for submit/validate/response flow with null-safe request handlingQueryForm, MutationForm) with CSRF disabled and empty block prefixes for clean JSON payloadsBooleanType, TimestampType, HiddenEntityType with secure query builder parameterizationValidationFailedView with structured violations for consistent API error responsesProblemNormalizer) for localized exception messages in problem detail responsesUniqueField validation constraint for Doctrine repositories with multi-field checks, closure-based exclusions, and custom normalizersTelExtension to strip non-digit characters from phone number inputCollectionUtils for syncing Doctrine collections (add new / remove stale items)composer require chamber-orchestra/form-bundle:8.0.*
Enable the bundle in config/bundles.php:
return [
// ...
ChamberOrchestra\FormBundle\ChamberOrchestraFormBundle::class => ['all' => true],
];
Use FormTrait for standard HTML form submissions and ApiFormTrait for JSON API endpoints.
handleFormCall() accepts a form class or instance, handles the request, and returns a view or response. handleApiCall() does the same for API endpoints -- it automatically parses JSON payloads for MutationForm types and merges uploaded files.
onFormSubmitted() checks validity, returns a ValidationFailedView on failure, or invokes the callback on success:
use ChamberOrchestra\FormBundle\ApiFormTrait;
use ChamberOrchestra\ViewBundle\View\ViewInterface;
use Symfony\Component\HttpFoundation\Request;
final class SearchCourseAction
{
use ApiFormTrait;
public function __invoke(Request $request): ViewInterface
{
$form = $this->createForm(SearchCourseForm::class);
$form->submit($request->query->all());
return $this->onFormSubmitted($form, function (SearchCourseData $dto) use ($request) {
$entities = $this->er->searchCourses(
$pagination = $this->getPagination(['per_page_limit' => $this->getPerPageLimit($request)]),
$dto->query,
$dto->brands,
$dto->topics,
$dto->products,
$dto->durations
);
return new PaginatedView($entities, $pagination, CourseView::class);
});
}
}
Extend QueryForm for GET requests or MutationForm for POST/PUT/PATCH requests. Both disable CSRF protection and use empty block prefixes for clean JSON input/output.
| Transformer | Description |
|---|---|
TextToBoolTransformer |
Converts "true", "1", "yes" to boolean |
DateTimeToNumberTransformer |
Converts Unix timestamps to DateTimeInterface objects |
ArrayToStringTransformer |
Converts arrays to/from comma-separated strings |
JsonStringToArrayTransformer |
Parses JSON strings to arrays (handles empty strings) |
Loads Doctrine entities by ID from a hidden form field. Supports a custom query_builder option with secure parameterized queries.
Validates field uniqueness against Doctrine repositories. Supports multiple fields, closure-based exclusions, custom normalizers, and targeted error paths.
Extends Symfony's ProblemNormalizer to translate exception messages when the exception implements TranslatableExceptionInterface. This ensures localized error messages in RFC 9457 problem detail responses.
| View | HTTP Status | Description |
|---|---|---|
SuccessView |
200 | Empty success response |
ValidationFailedView |
422 | Form validation errors with structured ViolationView items |
FailureView |
Configurable | Generic error response |
RedirectView |
301/302 | Redirect response for AJAX requests |
SuccessHtmlView |
200 | HTML fragment response for AJAX requests |
Install dependencies and run the full test suite:
composer install
./vendor/bin/phpunit
The integration test kernel (tests/Integrational/TestKernel.php) boots a minimal Symfony application with in-memory SQLite for Doctrine tests.
MIT
How can I help you explore Laravel packages today?