Install the Bundle:
composer require chamber-orchestra/view-bundle
Add to config/bundles.php:
return [
// ...
ChamberOrchestra\ViewBundle\ChamberOrchestraViewBundle::class => ['all' => true],
];
First Use Case: Typed JSON Response
Replace raw JsonResponse in controllers with ViewInterface:
use ChamberOrchestra\ViewBundle\View\ViewInterface;
use ChamberOrchestra\ViewBundle\View\DataView;
public function showUserAction(User $user): ViewInterface
{
return new DataView($user, 200, ['Content-Type' => 'application/json']);
}
The bundle auto-serializes the object to JSON, stripping null values by default.
Key Files to Explore:
src/View/DataView.php: Core view class.src/Attribute/: Attributes for customizing serialization (e.g., @View\Expose, @View\Hide).src/Serializer/: Custom serializer logic.ViewInterface instead of JsonResponse for consistent API responses.#[View\Expose]
#[View\As("full_name")]
public function getFullName(): string { ... }
#[View\Hide]
public ?string $sensitiveData;
return new DataView($user->posts, 200, [], PostView::class);
The bundle auto-maps collections to arrays of views.AbstractView for reusable view logic:
class UserView extends AbstractView
{
public function __construct(private User $user) {}
public function toArray(): array
{
return [
'id' => $this->user->id,
'name' => $this->user->name,
// Auto-stripped nulls handled by parent
];
}
}
bootstrap.php:
$kernel = new AppKernel();
$kernel->boot();
$cache = PropertyAccessor::createCache();
$cache->warmup();
kernel.response to transform responses:
# config/services.yaml
ChamberOrchestra\ViewBundle\EventSubscriber\ViewSubscriber:
tags: ['kernel.event_subscriber']
ViewInterface to JsonResponse.BindUtils for runtime property mapping:
use ChamberOrchestra\ViewBundle\Utils\BindUtils;
$data = BindUtils::bind($user, [
'name' => 'full_name',
'posts' => ['title', 'published_at'],
]);
Cache Invalidation:
cache:clear) after adding new attributes or view classes.APP_DEBUG=true to bypass cache and test live serialization.Circular References:
User->posts and Post->author) without explicit breaking logic:
#[View\Expose]
#[View\CircularReference]
public function getAuthor(): ?User { ... }
Null Handling:
null values are stripped. To preserve them:
new DataView($user, 200, [], [], false); // Disable null stripping
PHP 8.5+ Features:
readonly properties and typed class constants. Downgrading requires refactoring.Inspect Serialized Output:
$view = new DataView($user);
dump($view->toArray()); // Raw data before JSON encoding
Check Cache Warming:
php bin/console debug:container ChamberOrchestra\ViewBundle\Serializer\ViewSerializer
Verify the serializer is cached.
Attribute Conflicts:
#[View\Priority] to resolve attribute precedence:
#[View\Expose(priority: 10)]
#[View\Hide(priority: 20)]
Custom Serializers:
ViewSerializerInterface for non-standard types (e.g., DateTime formatting):
class CustomDateSerializer implements ViewSerializerInterface
{
public function serialize($data, ViewContext $context): mixed
{
return $data->format('Y-m-d');
}
}
services:
App\Serializer\CustomDateSerializer:
tags: [chamber_orchestra.view.serializer]
Post-Processing:
ViewEvent subscribers to modify responses:
public function onView(ViewEvent $event): void
{
$event->getView()->addHeader('X-Custom', 'value');
}
Testing Edge Cases:
null values, circular references, and custom attributes:
$this->assertEquals(
['id' => 1, 'name' => 'John'],
(new DataView(new User(null, 'John')))->toArray()
);
# config/packages/chamber_orchestra_view.yaml
chamber_orchestra_view:
cache_warming: '%kernel.debug% ? false : true'
View\Chunk for pagination:
#[View\Chunk(size: 20)]
public function getPosts(): array { ... }
```markdown
### **Config Quirks**
1. **Default Headers**:
Override globally in `config/packages/chamber_orchestra_view.yaml`:
```yaml
chamber_orchestra_view:
default_headers:
Cache-Control: 'public, max-age=3600'
Attribute Overrides: Disable auto-stripping for specific properties:
chamber_orchestra_view:
attributes:
App\Entity\User:
- { name: 'sensitiveData', strip_null: false }
Serializer Groups: Use Symfony’s serializer groups for conditional fields:
#[View\Groups(['default', 'admin'])]
public function getAdminData(): array { ... }
Pass groups to DataView:
new DataView($user, 200, [], [], [], ['admin']);
How can I help you explore Laravel packages today?