PHPackages                             toppy/symfony-async-twig-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. toppy/symfony-async-twig-bundle

ActiveSymfony-bundle

toppy/symfony-async-twig-bundle
===============================

Symfony integration bundle for async Twig rendering stack

v0.6.1(3mo ago)010proprietaryPHPPHP &gt;=8.4

Since Jan 23Pushed 3mo agoCompare

[ Source](https://github.com/toppynl/symfony-async-twig-bundle)[ Packagist](https://packagist.org/packages/toppy/symfony-async-twig-bundle)[ RSS](/packages/toppy-symfony-async-twig-bundle/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (16)Versions (10)Used By (0)

Async Twig Bundle
=================

[](#async-twig-bundle)

> **Read-Only Repository**This is a read-only subtree split from the main repository. Please submit issues and pull requests to [toppynl/symfony-astro](https://github.com/toppynl/symfony-astro).

Symfony integration bundle for the async Twig rendering stack. This is Layer 3 of the Toppy architecture, providing the bridge between Symfony's service container, request handling, and the framework-agnostic async rendering packages. The bundle auto-configures view models, sets up profiler integration, and provides request-aware context factories for the islands architecture.

Installation
------------

[](#installation)

```
composer require toppy/symfony-async-twig-bundle
```

The bundle is auto-registered via Symfony Flex. If not using Flex, add it manually:

```
// config/bundles.php
return [
    // ...
    Toppy\SymfonyAsyncTwigBundle\ToppySymfonyAsyncTwigBundle::class => ['all' => true],
];
```

Requirements
------------

[](#requirements)

- PHP 8.4+
- Symfony 6.4, 7.0, or 8.0
- `toppy/async-view-model` (required)
- `toppy/twig-view-model` (required)
- `toppy/twig-streaming` (optional, for streaming features)
- `toppy/twig-prerender` (optional, for prerender modifiers)

Quick Start
-----------

[](#quick-start)

With zero configuration, the bundle enables core view model functionality:

```
# config/packages/toppy_symfony_async_twig.yaml
toppy_symfony_async_twig: ~
```

Create a view model:

```
namespace App\ViewModel;

use Toppy\AsyncViewModel\AsyncViewModel;
use Toppy\AsyncViewModel\Context\RequestContext;
use Toppy\AsyncViewModel\Context\ViewContext;

final class ProductStockViewModel implements AsyncViewModel
{
    public function __construct(
        private readonly StockService $stockService,
    ) {}

    public function resolve(ViewContext $viewContext, RequestContext $requestContext): mixed
    {
        $productId = $requestContext->get('product_id');
        return $this->stockService->getStock($productId);
    }
}
```

Use it in templates:

```
{# Pre-load for parallel resolution #}
{% do pre_load_view('App\\ViewModel\\ProductStockViewModel', {product_id: product.id}) %}

{# Access resolved data #}
{% set stock = view('App\\ViewModel\\ProductStockViewModel') %}
{{ stock.quantity }} in stock
```

Architecture
------------

[](#architecture)

### Key Classes

[](#key-classes)

ClassPurpose`ToppySymfonyAsyncTwigBundle`Bundle class, registers compiler passes`ToppySymfonyAsyncTwigExtension`Service registration for all packages`Configuration`Bundle configuration schema`ContextFactory`Creates ViewContext/RequestContext from Symfony Request`ContextResolver`Request-scoped context holder with reset support`ViewModelProfiler`Collects timing data for view model resolution`TemplateStreamProfiler`Collects template/block streaming events`ViewModelDataCollector`Web Profiler panel for view models`StreamingDataCollector`Unified timeline for streaming debugging### Compiler Passes

[](#compiler-passes)

Compiler PassPurpose`ViewModelDependencyValidationPass`Detects circular dependencies at compile time`OpenTelemetryCompilerPass`Auto-registers OpenTelemetry profiler when available`TwigYieldModeCompilerPass`Enables Twig's `use_yield` mode for streaming`DisableWebLinkListenerPass`Prevents duplicate Link headers with Early Hints`ReplaceTwigDataCollectorPass`Wraps Twig collector for streamed response support`ConditionalCompilerPass`Wrapper for conditional pass execution### Service Configuration

[](#service-configuration)

The bundle auto-configures services via tags:

```
// AsyncViewModel implementations are auto-tagged
$container->registerForAutoconfiguration(AsyncViewModel::class)
    ->addTag('toppy.async_view_model');

// EarlyHintsProvider implementations are auto-tagged
$container->registerForAutoconfiguration(EarlyHintsProviderInterface::class)
    ->addTag('toppy.early_hints_provider');
```

View models are collected into a `ServiceLocator` for lazy loading, and the `ViewModelManager` uses this locator to resolve view models on demand.

Configuration
-------------

[](#configuration)

### Bundle Configuration

[](#bundle-configuration)

```
# config/packages/toppy_symfony_async_twig.yaml
toppy_symfony_async_twig:
    # Core async view model services (ViewModelManager, profiler)
    # Enabled by default
    view_model:
        enabled: true

    # Twig view() and pre_load_view() functions
    # Enabled by default
    twig_view:
        enabled: true

    # Streaming features (deferred slots, early hints)
    # 'auto' detects if toppy/twig-streaming is installed
    streaming:
        enabled: auto  # true | false | 'auto'

    # Prerender modifiers ({% include ... prerender(false) %})
    # 'auto' detects if toppy/twig-prerender is installed
    prerender:
        enabled: auto  # true | false | 'auto'

    # Symfony Web Profiler integration
    # Enabled by default
    profiler:
        enabled: true

    # Stale-while-revalidate cache layer
    cache:
        enabled: false
        pool: cache.app  # Must implement TagAwareCacheInterface
        lock:
            enabled: false  # Prevents thundering herd
            factory: lock.factory
            ttl: 30.0  # Lock TTL in seconds

    # Cache invalidation endpoint
    invalidation:
        enabled: false
        secret: '%env(CACHE_INVALIDATION_SECRET)%'  # Required when enabled
        route_prefix: /_cache
```

### View Model Registration

[](#view-model-registration)

View models are automatically discovered and registered when they implement `AsyncViewModel`:

```
namespace App\ViewModel;

use Toppy\AsyncViewModel\AsyncViewModel;

// This class is auto-tagged with 'toppy.async_view_model'
final class MyViewModel implements AsyncViewModel
{
    // ...
}
```

For view models with dependencies on other view models, implement `WithDependencies`:

```
use Toppy\AsyncViewModel\WithDependencies;

final class OrderTotalsViewModel implements AsyncViewModel, WithDependencies
{
    public static function getDependencies(): array
    {
        return [
            CartItemsViewModel::class,
            ShippingCostViewModel::class,
        ];
    }

    // Dependencies are resolved first, then this view model
}
```

The `ViewModelDependencyValidationPass` detects circular dependencies at container compile time, failing the build rather than causing runtime errors.

Usage
-----

[](#usage)

### In Controllers

[](#in-controllers)

Return a `StreamedResponse` with the streaming template renderer:

```
use Symfony\Component\HttpFoundation\StreamedResponse;
use Toppy\TwigStreaming\Twig\StreamingTemplateRendererInterface;

final class ProductController
{
    public function __construct(
        private readonly StreamingTemplateRendererInterface $renderer,
    ) {}

    public function show(int $id): StreamedResponse
    {
        return new StreamedResponse(
            $this->renderer->stream('product/show.html.twig', [
                'product_id' => $id,
            ])
        );
    }
}
```

For non-streaming responses, use standard Twig rendering - view models still resolve in parallel:

```
use Twig\Environment;

final class ProductController
{
    public function show(int $id, Environment $twig): Response
    {
        return new Response(
            $twig->render('product/show.html.twig', ['product_id' => $id])
        );
    }
}
```

### In Templates

[](#in-templates)

The `view()` function retrieves resolved view model data:

```
{# Pre-load multiple view models for parallel resolution #}
{% do pre_load_view('App\\ViewModel\\ProductDetailsViewModel', {id: product_id}) %}
{% do pre_load_view('App\\ViewModel\\ProductReviewsViewModel', {id: product_id}) %}
{% do pre_load_view('App\\ViewModel\\RelatedProductsViewModel', {id: product_id}) %}

{# Shell renders immediately with skeletons #}

    {% set details = view('App\\ViewModel\\ProductDetailsViewModel') %}
    {% if details.isReady %}
        {{ details.name }}
        {{ details.description }}
    {% else %}
        {% include 'skeletons/product-details.html.twig' %}
    {% endif %}

    {% set reviews = view('App\\ViewModel\\ProductReviewsViewModel') %}
    {# ... #}

```

### Context from Symfony Request

[](#context-from-symfony-request)

The `ContextFactory` creates contexts from the current Symfony request:

```
// ViewContext contains user/session state
$viewContext = $contextFactory->createViewContext();
// - currency: from session or 'EUR'
// - locale: from request locale
// - isB2B: from session
// - isVatExempt: from session
// - customerGroup: from session

// RequestContext contains route parameters
$requestContext = $contextFactory->createRequestContext(['extra' => 'param']);
// - params: merged from _route_params + additional
// - requestId: unique identifier for this request
```

The `ContextResolver` is request-scoped and implements `ResetInterface` for FrankenPHP worker mode:

```
// Automatically reset between requests in worker mode
$contextResolver->reset();
```

### Profiler Integration

[](#profiler-integration)

When `profiler.enabled` is true, the bundle registers data collectors for the Symfony Web Profiler:

#### Web Debug Toolbar

[](#web-debug-toolbar)

The toolbar shows:

- Number of resolved view models
- Total resolution time
- Parallel efficiency percentage

#### Profiler Panels

[](#profiler-panels)

**ViewModels Panel** (`toppy.view_model`):

- List of all resolved view models
- Resolution status (success, error, cached, stale)
- Timing information (start, duration)
- Dependencies between view models

**Streaming Panel** (`toppy.streaming`):

- Unified timeline of all events
- Template enter/leave events
- Block enter/leave events
- View model resolution events
- HTTP request events (if HttpClientProfiler is available)
- Key markers (First Template, All Data Ready, Response Complete)

**HTTP Client Panel** (`toppy.http_client`):

- Outgoing HTTP requests made during view model resolution
- Status codes, timing, URLs

The profilers implement `LateDataCollectorInterface` because `StreamedResponse` callbacks execute after `kernel.response`. Data is collected at `kernel.terminate`.

### Streaming Response with Debug Toolbar

[](#streaming-response-with-debug-toolbar)

The `StreamedResponseWebDebugToolbarListener` injects the web debug toolbar into streamed responses by appending the toolbar JS after the stream completes. This preserves streaming benefits while maintaining debugging capabilities.

### Cache Layer

[](#cache-layer)

Enable stale-while-revalidate caching for view models:

```
toppy_symfony_async_twig:
    cache:
        enabled: true
        pool: cache.app
        lock:
            enabled: true
            factory: lock.factory
            ttl: 30.0
```

View models can define cache behavior:

```
use Toppy\AsyncViewModel\Cache\CacheableViewModel;

final class ExpensiveViewModel implements AsyncViewModel, CacheableViewModel
{
    public function getCacheTtl(): int
    {
        return 300; // Fresh for 5 minutes
    }

    public function getStaleWhileRevalidate(): int
    {
        return 600; // Serve stale for 10 more minutes while revalidating
    }

    public function getCacheTags(RequestContext $context): array
    {
        return ['product:' . $context->get('product_id')];
    }
}
```

### Cache Invalidation

[](#cache-invalidation)

When `invalidation.enabled` is true, an endpoint is available at `/_cache/invalidate`:

```
# Invalidate by tags
curl -X POST "https://example.com/_cache/invalidate?secret=your-secret" \
    -H "Content-Type: application/json" \
    -d '{"tags": ["product:123", "category:electronics"]}'

# Or via query params
curl "https://example.com/_cache/invalidate?secret=your-secret&tags[]=product:123"
```

### OpenTelemetry Integration

[](#opentelemetry-integration)

When `open-telemetry/api` is installed and a `TracerInterface` is available, the `OpenTelemetryCompilerPass` automatically decorates the profiler to emit spans for view model resolution.

### Early Hints Providers

[](#early-hints-providers)

Implement `EarlyHintsProviderInterface` to add resources to HTTP 103 Early Hints:

```
use Toppy\TwigStreaming\EarlyHints\EarlyHintsProviderInterface;

final class CustomEarlyHintsProvider implements EarlyHintsProviderInterface
{
    public function getHints(): array
    {
        return [
            [
                'rel' => 'preload',
                'href' => '/assets/critical.css',
                'attributes' => ['as' => 'style'],
            ],
        ];
    }
}
```

The bundle includes `ImportMapEarlyHintsProvider` for Symfony AssetMapper integration.

Integration
-----------

[](#integration)

This bundle sits at the top of the dependency graph:

```
symfony-async-twig-bundle (this bundle)
        |
   +----+----+
   v         v
twig-prerender --> twig-streaming
                       |
                       |    twig-view-model
                       |         |
                       +----+----+
                            v
                    async-view-model (core)

```

The bundle consolidates service definitions from all packages:

- **async-view-model**: ViewModelManager, profiler interfaces, context abstractions
- **twig-view-model**: ViewExtension, ViewModelRuntime
- **twig-streaming**: SlotRegistry, StreamingTemplateRenderer, EarlyHints
- **twig-prerender**: PrerenderExtension, ContextEncryptor

Testing
-------

[](#testing)

```
cd src/Toppy/Bundle/AsyncTwigBundle
composer install
./vendor/bin/phpunit
```

Key test areas:

- Configuration validation (`Tests/DependencyInjection/ConfigurationTest.php`)
- Profiler data collection (`Tests/Unit/Profiler/`)
- Cache implementation (`Tests/Unit/Cache/`)
- Controller responses (`Tests/Unit/Controller/`)

License
-------

[](#license)

Proprietary - see the main repository for license details.

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance82

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 95.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

9

Last Release

90d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/239d9adcbadfaac1e3ce531c1b81d87e378c3395b9c10bef5bfddb1637c07c9d?d=identicon)[Swahjak](/maintainers/Swahjak)

---

Top Contributors

[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (23 commits)")[![Swahjak](https://avatars.githubusercontent.com/u/4386577?v=4)](https://github.com/Swahjak "Swahjak (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/toppy-symfony-async-twig-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/toppy-symfony-async-twig-bundle/health.svg)](https://phpackages.com/packages/toppy-symfony-async-twig-bundle)
```

###  Alternatives

[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.3M152](/packages/sulu-sulu)[prestashop/prestashop

PrestaShop is an Open Source e-commerce platform, committed to providing the best shopping cart experience for both merchants and customers.

9.0k15.4k](/packages/prestashop-prestashop)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.2M386](/packages/shopware-core)[ec-cube/ec-cube

EC-CUBE EC open platform.

78527.0k1](/packages/ec-cube-ec-cube)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
