Installation:
composer require bml/form-theme-bundle
For non-Flex projects, add the bundle to AppKernel.php:
new Bml\FormThemeBundle\BmlFormThemeBundle(),
First Use Case:
Define a custom Twig theme for a form type in configureOptions():
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'theme' => '@App/Form/my_form_theme.html.twig', // Path to your Twig template
]);
}
}
Create the Theme Template:
Place my_form_theme.html.twig in templates/App/Form/ (or your preferred path) and extend it:
{% extends 'form_theme.html.twig' %}
{% block form_row %}
<div class="custom-form-row">
{{ form_widget(form) }}
{% if form.errors|length > 0 %}
<div class="errors">{{ form_errors(form) }}</div>
{% endif %}
</div>
{% endblock %}
Use the Form Type:
$form = $this->createForm(MyFormType::class, $entity);
Theme Inheritance:
Use Twig’s extends to inherit from Symfony’s default form themes (form_theme.html.twig) or other custom themes. Override blocks like form_row, form_widget, or form_end for granular control.
Dynamic Theme Paths:
Pass dynamic theme paths via configureOptions:
$resolver->setDefaults([
'theme' => function ($options) {
return $options['dynamic_theme'] ? '@App/Form/dynamic_theme.html.twig' : null;
},
]);
Theme Composition:
Combine multiple themes by chaining form_theme calls in Twig:
{% form_theme form '@App/Form/base_theme.html.twig' %}
{% form_theme form '@App/Form/override_theme.html.twig' %}
Reusable Themes:
Create modular themes (e.g., _field.html.twig, _errors.html.twig) and include them in your main theme:
{% include '@App/Form/_field.html.twig' with {'form': form} %}
Symfony UX/Twig:
Works seamlessly with Symfony UX components (e.g., Turbo, Stimulus) for dynamic form updates.
Example: Use data-turbo-frame in your theme for SPA-like behavior.
Form Events:
Modify themes dynamically via PRE_SET_DATA or POST_SUBMIT events:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$event->getForm()->getConfig()->setTheme('@App/Form/dynamic_theme.html.twig');
});
Asset Management:
Co-locate CSS/JS with themes (e.g., templates/App/Form/my_theme.css) and reference them in Twig:
{{ asset('bundles/app/form/my_theme.css') }}
Testing:
Test themes with PHPUnit by mocking the FormView and asserting rendered HTML:
$view = $form->createView();
$this->assertStringContainsString('<div class="custom-form-row">', $view->children['name']->vars['full_name']);
Theme Path Resolution:
@Bundle/ syntax but the bundle isn’t registered.@App/Form/theme.html.twig) or ensure bundles are loaded in AppKernel.php.Caching:
php bin/console cache:clear) or use debug:config-parameters to force reload.Form Theme Overrides:
form_theme in base templates (e.g., base.html.twig) can conflict with per-form themes.{% block form_theme %}{% endblock %} in base templates to allow form-level overrides.CSRF Token Placement:
{{ form_widget(form.vars.csrf_token) }} can break form submission.form_rest block or place tokens in a hidden field:
{{ form_widget(form.vars.csrf_token) }}
Nested Forms:
form_theme in Twig for nested blocks:
{% form_theme form.child 'app/form/nested_theme.html.twig' %}
{{ dump(form.vars.theme) }} in Twig to inspect active themes.TWIG_DEBUG=1 in .env to see template inheritance chains.Dynamic Theme Selection:
Override configureOptions to conditionally set themes:
$resolver->setDefaults([
'theme' => function ($options, $form) {
return $form->getParent() ? '@App/Form/nested_theme.html.twig' : null;
},
]);
Theme Registry: Create a service to manage themes centrally:
# config/services.yaml
services:
App\Service\FormThemeRegistry:
arguments:
$themes: ['@App/Form/default.html.twig', '@App/Form/admin.html.twig']
Then inject into form types to switch themes programmatically.
Custom Theme Directives: Extend Twig with custom filters for theme logic:
{% filter custom_theme_filter(form) %}
{{ form_widget(form) }}
{% endfilter %}
Theme Validation:
Validate theme paths in configureOptions:
$resolver->setDefined('theme');
$resolver->setAllowedTypes('theme', 'string');
$resolver->setNormalizer('theme', function ($theme, OptionsResolver $resolver) {
if (!file_exists($theme)) {
throw new \InvalidArgumentException("Theme file not found: $theme");
}
return $theme;
});
Legacy Support:
For Symfony <5.3 (where form_theme was natively added), use this bundle as a drop-in replacement for the PR feature. Test thoroughly for edge cases.
How can I help you explore Laravel packages today?