chamber-orchestra/view-bundle

Symfony bundle providing a typed, reusable view layer for building JSON API responses with automatic property binding and cache-warmed serialization

Maintainers

šŸ‘ wtorsi

Package info

github.com/chamber-orchestra/view-bundle

Type:symfony-bundle

pkg:composer/chamber-orchestra/view-bundle

Statistics

Installs: 2 953

Dependents: 5

Suggesters: 1

Stars: 226

Open Issues: 4

v8.0.23 2026-02-25 22:11 UTC

Suggests

None

Provides

None

Conflicts

Replaces

None

MIT 189c965bcc17097f7b095a6e6246581570e13c06

symfonyjsonserializationviewview-modelsymfony-bundlerest-apiJSON-APIapi-responseproperty-binding


README

šŸ‘ PHP Composer
šŸ‘ PHPStan
šŸ‘ PHP-CS-Fixer
šŸ‘ Latest Stable Version
šŸ‘ Total Downloads
šŸ‘ License: MIT
šŸ‘ PHP 8.5+
šŸ‘ Symfony 8.0

ChamberOrchestra View Bundle

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.

Key Features

  • Typed view models — define JSON response structures as PHP classes with typed properties
  • Automatic property binding — BindView maps domain object properties to view properties via reflection
  • Collection mapping — IterableView transforms arrays and iterables with typed element views
  • Null stripping — null values are automatically excluded from serialized JSON output
  • Build-time cache warming — pre-computed metadata and property mappings eliminate reflection overhead in production
  • Build-versioned caching — cache files are tied to container.build_id for zero-downtime deployments
  • Doctrine proxy support — transparent lazy-load initialization before property access

Requirements

  • PHP 8.5+
  • Symfony 8.0 components (http-kernel, serializer, property-access, dependency-injection, config, framework-bundle)
  • doctrine/common ^3.5

Installation

composer require chamber-orchestra/view-bundle:8.0.*

Enable the bundle in config/bundles.php:

return [
 // ...
 ChamberOrchestra\ViewBundle\ChamberOrchestraViewBundle::class => ['all' => true],
];

Quick Start

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 Types

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 Property Binding

BindView uses BindUtils to synchronize properties between source objects and view instances. It handles:

  • Built-in PHP types and custom objects
  • ViewInterface subclasses (auto-constructed)
  • IterableView properties with #[Type(ViewClass::class)] attribute for typed collections
  • Skips union types and incompatible type pairs

Architecture

Request/Response Flow

  1. SetVersionSubscriber (priority 256) — injects the DI-managed BindUtils instance into BindView via BindView::setBindUtils()
  2. Controller returns a ViewInterface object
  3. ViewSubscriber — detects ViewInterface results, wraps non-ResponseViewInterface in DataView, serializes to JSON via ViewNormalizer

View Auto-Discovery

Views 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.

Performance Optimizations

The bundle includes a two-phase optimization strategy for production environments:

Phase 1: Runtime Metadata Caching

  • ViewMetadataFactory caches property metadata in memory
  • Direct property access eliminates repeated reflection calls
  • 30-50% faster normalization on repeated calls

Phase 2: Build-Time Cache Warming

  • ViewMetadataCacheWarmer pre-computes view property metadata at build time
  • BindUtilsCacheWarmer pre-computes property mappings (uses #[BindsFrom] for targeted source classes, falls back to N² pairs)
  • Generated opcache-optimized PHP files stored in kernel.share_dir
  • Cache files are versioned with container.build_id for safe deployments
  • 60-80% reduction in reflection overhead on production requests
  • Automatic fallback to reflection when warmed cache is unavailable

Cache Configuration

BindUtils 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:

  • View property metadata (nullability, defaults, types)
  • View-to-view property mappings for BindUtils

Benchmarks

PHPBench 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.

Development

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

License

MIT