markup/twig-extension-proxy-bundle
Symfony bundle that adds a Twig extension proxy, routing function, filter and test calls through one service so extensions can be decorated, lazy-loaded or overridden without touching your templates.
This is a low-adoption (0 stars, 0 dependents) MIT-licensed bundle with no Flex recipe, so treat the README and composer.json as the source of truth before you build anything on it.
1. Install and register manually. Expect no recipe, so add the bundle yourself:
composer require markup/twig-extension-proxy-bundle
// config/bundles.php
return [
// ...
Markup\TwigExtensionProxyBundle\MarkupTwigExtensionProxyBundle::class => ['all' => true],
];
Class name is inferred from convention — confirm the exact FQCN in the package's *Bundle.php file after install.
2. Check version constraints first. A 0-star bundle is usually pinned to the era it was written in. Compare its composer.json requirements (twig/twig, symfony/framework-bundle) against your app before you write code against it. If it targets Twig 1.x/2.x and you're on Twig 3.x, stop there — see the redundancy note in Gotchas.
3. Where to look first, in order:
README.md — the intended config shape (per-extension opt-in vs. global).DependencyInjection/Configuration.php — the real config keys, since docs are often stale.Twig\Extension\AbstractExtension methods they forward.4. First use case: a heavy extension you don't own. The value proposition is that a Twig extension with expensive constructor dependencies (a repository, an HTTP client, a paid SDK) gets instantiated on container build even when no template calls its functions. Baseline it before touching config:
bin/console debug:container --tag=twig.extension
bin/console debug:twig
Find the extension whose constructor wires a DB connection or API client, enable the proxy for that one, and re-run. If you can't demonstrate a reduction in instantiated services on a page that doesn't use the extension, the bundle is earning nothing for you.
Measure first, adopt second. The legitimate win is narrow: extensions that are (a) registered on twig.extension, (b) never used on most requests, and (c) expensive to construct. Anything else is noise. Profile with the Symfony profiler disabled and a warm container cache — debug:twig instantiates extensions and will lie to you about laziness.
Proxy the third-party extensions you can't edit. The clean pattern is to leave the real extension registered as-is and let the bundle intercept resolution, so vendor code and its own bundle config stay untouched. Your own extensions are better fixed directly: drop the dependency into a Twig runtime instead.
Compare against Twig's native answer. On Twig 2.7+/3.x, a lazy extension is Twig\Extension\RuntimeExtensionInterface plus a runtime service, loaded by Twig\RuntimeLoader\ContainerRuntimeLoader (already wired by TwigBundle via the twig.runtime tag):
final class PriceRuntime implements RuntimeExtensionInterface
{
public function __construct(private PriceRepository $repo) {}
public function price(int $productId): string { /* ... */ }
}
# config/services.yaml
App\Twig\PriceRuntime:
tags: ['twig.runtime']
If you're on Twig 3 and can edit the extension, this is the supported, zero-dependency path and the bundle is redundant. The bundle's niche is legacy stacks, or third-party bundles whose extension you can't rewrite.
Guard parity with a golden-output test. A proxy that mis-forwards metadata silently changes rendering. Assert byte-identical output with the proxy on and off:
public function testProxyParity(): void
{
$twig = static::getContainer()->get('twig');
$out = $twig->createTemplate('{{ my_filter("x") }}')->render([]);
$this->assertSame('expected', $out);
}
Cover autoescaped output, functions that need the Environment, and functions that need context — those are the three places proxies break.
Roll out per-extension, one at a time. Enable on a single extension, deploy, watch error rates. A batch rollout makes a mis-forwarded is_safe or missing needs_environment impossible to bisect.
getFunctions()/getFilters() return TwigFunction/TwigFilter objects carrying is_safe, needs_environment, needs_context, and is_variadic. If the proxy constructs these by hand and drops a flag, you get either a double-escaped output bug or an outright XSS hole. Verify flag-for-flag.twig.extension priority than the original can flip which implementation wins. If two bundles define price(), you will find out in production.{{ cart_count() }} gains nothing.Symfony\Component\DependencyInjection\Definition patterns, old TreeBuilder signatures), it will fatal at container compile with an opaque error. bin/console cache:clear is the fastest reproduction.config/bundles.php), clear cache, render the same template, and diff output. If output changes, the proxy is the culprit — then debug:container --tag=twig.extension with and without the bundle to see what got swapped.twig.runtime pattern covers the same ground with no third-party surface area, and a 0-star dependency is a liability you should be able to justify to the next person who reads composer.json.How can I help you explore Laravel packages today?