abdielcs/expanded-collection-bundle
Symfony 2/3 bundle that renders entity collections as expanded selectable lists (checkboxes). Provides form types extending EntityType, supports OneToMany and ManyToMany relations, configurable displayed fields, and includes a Bootstrap 3 theme example.
Installation:
composer require abdielcs/expanded-collection-bundle
Add to AppKernel.php:
new Abdielcs\ExpandedCollectionBundle\AbdielcsExpandedCollectionBundle(),
First Use Case:
Replace a standard EntityType field in your form with ExpandedEntityType for a OneToMany or ManyToMany relationship.
Example:
use Abdielcs\ExpandedCollectionBundle\Form\Type\ExpandedEntityType;
$builder->add('tags', ExpandedEntityType::class, [
'class' => Tag::class,
'property' => 'name', // Display property
'expanded' => true,
'multiple' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('t')->where('t.active = :active')
->setParameter('active', true);
},
]);
Template Integration:
Override the default template (optional) by copying:
vendor/abdielcs/expanded-collection-bundle/Resources/views/Form/expanded_entity_widget.html.twig
to templates/Form/expanded_entity_widget.html.twig.
Basic Expanded Checkbox List:
Use ExpandedEntityType for OneToMany or ManyToMany fields where you want a checkbox-based UI instead of a dropdown.
$builder->add('authors', ExpandedEntityType::class, [
'class' => Author::class,
'property' => 'fullName', // Custom getter/method
'expanded' => true,
'multiple' => true,
]);
Dynamic Query Building:
Filter entities dynamically using query_builder:
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('e')
->where('e.status = :status')
->setParameter('status', 'published');
},
Custom Field Rendering: Configure which properties/methods to display and their order:
'properties' => [
'name', // Simple property
'getFormattedDate', // Custom method
'price' => ['label' => 'Cost', 'format' => 'currency'], // Custom label + formatting
],
Bootstrap 3 Integration: Extend the default template to use Bootstrap classes:
{% extends '::expanded_entity_widget.html.twig' %}
{% block expanded_entity_widget %}
<div class="form-group">
<div class="checkbox">
{% for child in form.children %}
<label>
<input type="checkbox" {{ child.vars.value ? 'checked' : '' }}
name="{{ form.name }}[]"
value="{{ child.vars.value }}">
{{ child.vars.data.name }}
</label>
{% endfor %}
</div>
</div>
{% endblock %}
Validation and Data Binding:
The bundle handles validation and data binding automatically. Ensure your entity methods (e.g., getFormattedDate()) are public and return the expected type.
Deprecated Symfony Version:
symfony/flex or manually patch dependencies.Template Overrides:
expanded_entity_widget). Misspelling blocks breaks rendering.var/dump(form.vars) in Twig to inspect the rendered data structure.Query Builder Conflicts:
query_builder is not set, the bundle falls back to the default repository findAll(). For large datasets, always define a query builder to avoid performance issues.DQL in query_builder must return the same entity class as specified in class.Property vs. Method Ambiguity:
property option expects a public getter method (e.g., getName()) or a direct property (e.g., name). Using a non-existent method throws a PropertyAccessException.$reflection = new ReflectionClass(YourEntity::class);
var_dump($reflection->getProperties(), $reflection->getMethods());
Bootstrap CSS/JS Dependencies:
bootstrap.css loaded. Without it, checkboxes may render incorrectly.{{ encore_entry_link_tags('app') }} {# if using Webpack Encore #}
or manually:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
Form Variable Inspection: Dump form variables in Twig to debug rendering:
{{ dump(form.vars) }}
Look for data, value, and full_name keys.
Symfony Profiler: Use the Profiler to inspect form submissions and entity hydration:
config/packages/dev/debug.yaml:
framework:
profiler: { only_exceptions: false }
Entity Hydration Issues: If selected values aren’t saved, ensure:
addAuthor(Author $author) for OneToMany).multiple: true for collections.Custom Twig Extensions: Extend the bundle’s Twig logic by creating a custom extension:
// src/Twig/ExpandedCollectionExtension.php
class ExpandedCollectionExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction('custom_expanded_render', [$this, 'renderCustom']),
];
}
public function renderCustom(FormView $form, array $options)
{
// Custom logic here
return new \Twig_Markup($html);
}
}
Register in services.yaml:
services:
App\Twig\ExpandedCollectionExtension:
tags: ['twig.extension']
Event Listeners: Hook into form events to modify behavior:
// src/EventListener/ExpandedCollectionListener.php
class ExpandedCollectionListener
{
public function onPreSetData(FormEvent $event)
{
$data = $event->getData();
if ($data instanceof YourEntity) {
// Modify data before binding
}
}
}
Register in services.yaml:
services:
App\EventListener\ExpandedCollectionListener:
tags:
- { name: kernel.event_listener, event: form.pre_set_data, method: onPreSetData }
Dynamic Property Mapping: For dynamic property rendering, use a compiler pass to generate getter methods at runtime (advanced):
// src/DependencyInjection/Compiler/ExpandedPropertyPass.php
class ExpandedPropertyPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->findDefinition('abdielcs_expanded_collection.templating.helper');
$definition->addMethodCall('setDynamicProperties', [
[$this->getDynamicProperties($container)]
]);
}
}
How can I help you explore Laravel packages today?