A Symfony bundle that provides a typed view layer for JSON API responses. Define response shapes as PHP classes, return them from controllers, and let the bundle handle serialization automatically — no manual JsonResponse construction needed.
Built for Symfony 8.0 and PHP 8.5+, the bundle eliminates boilerplate in REST API controllers by introducing view models with automatic property binding, collection mapping, and production-ready cache warming.
BindView maps domain object properties to view properties via reflectionIterableView transforms arrays and iterables with typed element viewscontainer.build_id for zero-downtime deploymentscomposer require chamber-orchestra/view-bundle:8.0.*
Enable the bundle in config/bundles.php:
return [
// ...
ChamberOrchestra\ViewBundle\ChamberOrchestraViewBundle::class => ['all' => true],
];
Define a view model that maps properties from a domain object:
use ChamberOrchestra\ViewBundle\Attribute\BindsFrom;
use ChamberOrchestra\ViewBundle\Attribute\Type;
use ChamberOrchestra\ViewBundle\View\BindView;
use ChamberOrchestra\ViewBundle\View\IterableView;
#[BindsFrom(User::class)]
final class UserView extends BindView
{
public string $id;
public string $name;
#[Type(ImageView::class)]
public IterableView $images;
public function __construct(User $user)
{
parent::__construct($user);
}
}
final class ImageView extends BindView
{
public string $path;
}
Return the view from a controller — the bundle converts it to a JsonResponse automatically:
#[Route('/user/me', methods: ['GET'])]
final class GetMeAction
{
public function __invoke(): UserView
{
return new UserView($this->getUser());
}
}
ViewSubscriber converts any ViewInterface result into a JsonResponse. Non-view results pass through unchanged.
| View | Purpose |
|---|---|
ResponseView |
Base response with HTTP status (200) and Content-Type: application/json headers |
DataView |
Wraps any view or array under a "data" key |
BindView |
Maps matching properties from a source object using reflection |
IterableView |
Maps collections via a callback or view class string |
KeyValueView |
Produces associative array output for metadata blocks |
BindView uses BindUtils to synchronize properties between source objects and view instances. It handles:
ViewInterface subclasses (auto-constructed)IterableView properties with #[Type(ViewClass::class)] attribute for typed collectionsBindUtils instance into BindView via BindView::setBindUtils()ViewInterface objectViewInterface results, wraps non-ResponseViewInterface in DataView, serializes to JSON via ViewNormalizerViews implementing ViewInterface are automatically tagged with chamber_orchestra.view via #[AutoconfigureTag]. The ViewPass compiler pass collects these classes and passes them to cache warmers for pre-computation.
The bundle includes a two-phase optimization strategy for production environments:
ViewMetadataFactory caches property metadata in memoryViewMetadataCacheWarmer pre-computes view property metadata at build timeBindUtilsCacheWarmer pre-computes property mappings (uses #[BindsFrom] for targeted source classes, falls back to N² pairs)kernel.share_dircontainer.build_id for safe deploymentsBindUtils is registered as a DI service with $buildId, $debug, and $shareDir constructor arguments. When APP_DEBUG=false, property accessor caching is enabled with a 24-hour lifetime. SetVersionSubscriber injects the configured instance into BindView on each request.
Warm the cache in production:
bin/console cache:warmup --env=prod
This generates build-versioned files in the shared cache directory:
BindUtilsPHPBench benchmarks are included to measure serialization performance and cache impact:
composer bench # Run all benchmarks
vendor/bin/phpbench run --report=default # Run with default report
Benchmark classes: BindUtilsBench, CacheWarmupBench, NormalizationBench.
composer install # Install dependencies
composer test # Run all tests (93 tests, 328 assertions)
./bin/phpunit # Run tests directly
./bin/phpunit --filter X # Run specific test class or method
MIT
How can I help you explore Laravel packages today?