covex-nn/twig-callable-bridge-bundle
Installation Add the package via Composer:
composer require covex-nn/twig-callable-bridge-bundle
Register the bundle in config/bundles.php (Symfony 4+) or AppKernel.php (Symfony <4):
Covex\TwigCallableBridgeBundle\CovexTwigCallableBridgeBundle::class => ['all' => true],
Basic Configuration
Define your callables in config/packages/covex_twig_callable_bridge.yaml:
covex_twig_callable_bridge:
functions:
my_function: App\Service\MyService::customMethod
filters:
my_filter: App\Service\MyService::customFilter
tests:
my_test: App\Service\MyService::customTest
First Use Case
Use the newly exposed Twig functions in a template (templates/index.html.twig):
{{ my_function('input') }}
{{ 'INPUT'|my_filter }}
{% if '123' is my_test %}Valid{% endif %}
Reusing Business Logic Expose domain-specific PHP methods as Twig functions to avoid duplicating logic in templates:
# config/packages/covex_twig_callable_bridge.yaml
covex_twig_callable_bridge:
functions:
format_price: App\Service\PricingService::formatCurrency
{{ format_price(product.price) }}
Dynamic Callables via Services
Inject services into the bundle’s compiler pass (extend CallableBridgeCompilerPass) to register callables dynamically:
// src/DependencyInjection/CallableBridgeCompilerPass.php
public function process(\Symfony\Component\DependencyInjection\ContainerBuilder $container)
{
$definition = $container->findDefinition('covex_twig_callable_bridge.compiler');
$definition->addMethodCall('addCallable', [
'format_date',
[$container->getDefinition('app.date_formatter')->getClass(), 'format']
]);
}
Grouping Related Callables
Use namespacing in configuration to organize callables (e.g., user.*, math.*):
covex_twig_callable_bridge:
functions:
user:
full_name: App\Service\UserService::getFullName
is_active: App\Service\UserService::isActive
{{ user.full_name(user) }}
{% if user is user.is_active %}Active{% endif %}
Integration with Forms/Themes
Extend Twig templates in bundles by registering callables in Resources/config/services.yaml:
services:
Covex\TwigCallableBridgeBundle\CovexTwigCallableBridgeBundle:
arguments:
$callables:
form:
render_field: App\Form\Twig\Extension::renderField
Callable Signature Mismatches
Twig passes arguments as an array ($args), but PHP callables may expect positional arguments. Use ...func_get_args() or call_user_func_array:
// Bad: Assumes first arg is string
public function badExample($str) { ... }
// Good: Handles any args
public function goodExample(...$args) { ... }
Circular Dependencies
Avoid registering callables that depend on Twig itself (e.g., Twig_Environment) to prevent bootstrapping issues.
Configuration Overrides
Later bundle configurations silently override earlier ones. Use unique keys (e.g., app.) to avoid collisions:
covex_twig_callable_bridge:
functions:
app.my_function: App\Service\MyService::method
Security Risks
{{ user_input|call }}).Missing Callables
Check for typos in YAML keys or service names. Enable debug mode (APP_DEBUG=true) to see unregistered callables in Twig’s error output.
Argument Errors
Use {{ dump(_context) }} in Twig to inspect passed arguments and debug mismatches.
Compiler Pass Issues
Clear the cache (php bin/console cache:clear) after modifying CallableBridgeCompilerPass or service definitions.
Custom Compiler Pass
Extend CallableBridgeCompilerPass to add logic (e.g., auto-registering methods from a trait):
public function process(ContainerBuilder $container)
{
$reflection = new \ReflectionClass('App\Service\MyService');
foreach ($reflection->getMethods() as $method) {
$container->getDefinition('covex_twig_callable_bridge.compiler')
->addMethodCall('addCallable', [$method->name, [$method->class, $method->name]]);
}
}
Dynamic Registration via Event Subscribers
Register callables in response to kernel events (e.g., KernelEvents::BOOT):
public static function getSubscribedEvents()
{
return [
KernelEvents::BOOT => 'registerCallables',
];
}
public function registerCallables(KernelEvent $event)
{
$container = $event->getKernel()->getContainer();
$container->get('covex_twig_callable_bridge.compiler')
->addCallable('dynamic_func', [$container->get('app.service'), 'method']);
}
Namespaced Callables with Proxies Use Symfony’s proxy services to lazy-load callables (e.g., for heavy services):
services:
app.proxied_service:
class: App\Service\ProxiedService
factory: ['@service_container', get]
arguments: ['app.proxied_service.id']
covex_twig_callable_bridge:
functions:
proxied_func: '@app.proxied_service'
Testing Callables Mock the compiler in tests to verify registration:
$compiler = $this->createMock(CallableBridgeCompiler::class);
$compiler->expects($this->once())
->method('addCallable')
->with('test_func', [$mockService, 'testMethod']);
$container->set('covex_twig_callable_bridge.compiler', $compiler);
How can I help you explore Laravel packages today?