saifuleroz / select2table-bundle
A Symfony bundle integrating Select2 as a high-performance remote AJAX drop-in replacement for standard entity/table select fields.
Package info
github.com/saifulferoz/select2table-bundle
Type:symfony-bundle
pkg:composer/saifuleroz/select2table-bundle
Requires
- php: >=8.2 <9.0
- doctrine/dbal: ^3.6 || ^4.0
- symfony/config: ^6.4 || ^7.4 || ^8.0
- symfony/dependency-injection: ^6.4 || ^7.4 || ^8.0
- symfony/form: ^6.4 || ^7.4 || ^8.0
- symfony/http-foundation: ^6.4 || ^7.4 || ^8.0
- symfony/http-kernel: ^6.4 || ^7.4 || ^8.0
- symfony/options-resolver: ^6.4 || ^7.4 || ^8.0
- symfony/property-access: ^6.4 || ^7.4 || ^8.0
- symfony/routing: ^6.4 || ^7.4 || ^8.0
- twig/twig: ^3.0
Requires (Dev)
- phpunit/phpunit: ^10.5 || ^11.0
- symfony/browser-kit: ^6.4 || ^7.4 || ^8.0
- symfony/flex: ^2.4
- symfony/phpunit-bridge: ^6.4 || ^7.4 || ^8.0
- symfony/translation: ^6.4 || ^7.4 || ^8.0
- symfony/twig-bridge: ^6.4 || ^7.4 || ^8.0
- symfony/yaml: ^6.4 || ^7.4 || ^8.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-10-15 01:40:19 UTC
README
A high-performance Symfony bundle that integrates Select2 as an asynchronous, remote-AJAX autocomplete replacement for standard table/entity select fields in Symfony forms.
Inspiration: Inspired by
tetranz/select2entity-bundle, optimized with direct Doctrine DBAL performance, typed properties, strict typing, modern assets, and full support for Symfony 7.4+ and Symfony 8.x (PHP 8.2+).
Key Features
- ⚡ High Performance: Queries large database tables effortlessly with lightweight Doctrine DBAL pagination and count queries.
- 🔄 Single & Multi-Select: Full support for
multiple: false(single selection) andmultiple: true(multi-selection). - 📜 Infinite Scrolling: Server-side pagination with seamless incremental scrolling.
- 🔗 Cascading / Dependent Dropdowns: Reacts dynamically to changes in parent form fields (
req_params). - 🏷️ Dynamic Tag Creation: Allows users to create new tags on-the-fly (
allow_add). - 🎨 HTML & Custom Templating: Render rich option templates (images, avatars, badges, icons).
- 🛠️ Modern Asset Integration: Compatible with Symfony UX / AssetMapper, Webpack Encore, Vite, and traditional CDN/Script setups.
- 🛡️ SQL Injection Protection: Validates identifiers and safely binds parameters across DBAL 3 & DBAL 4.
Requirements
- PHP:
^8.2 || ^8.3 || ^8.4 || ^8.5 - Symfony:
^6.4 || ^7.4 || ^8.0 - Doctrine DBAL:
^3.6 || ^4.0 - Twig:
^3.0 - jQuery & Select2 4.x
Installation
1. Install via Composer
composer require saifuleroz/select2table-bundle
2. Enable the Bundle (if not using Symfony Flex)
If you are not using Symfony Flex, add the bundle to config/bundles.php:
return [ // ... SaifulFeroz\Select2TableBundle\Select2TableBundle::class => ['all' => true], ];
3. Register the Twig Form Theme
Add the bundle's form theme in config/packages/twig.yaml:
twig: form_themes: - '@Select2Table/form/fields.html.twig'
Asset Setup
Option A: Traditional / CDN Setup
Ensure jQuery and Select2 (CSS & JS) are loaded on your page, then include the bundle's JavaScript:
<!-- Select2 CSS --> <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" /> <!-- jQuery & Select2 JS --> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script> <!-- Select2Table Script --> <script src="{{ asset('bundles/saifulferozselect2table/js/select2table.js') }}"></script>
Install the public bundle assets with:
php bin/console assets:install --symlink
Option B: Symfony UX / AssetMapper (Stimulus)
Import the script in your assets/app.js:
import 'select2/dist/css/select2.min.css'; import 'select2'; import './vendor/saifuleroz/select2table-bundle/select2table.js';
Usage
1. Form Type Configuration
In your Symfony form class, use Select2TableType::class:
namespace App\Form; use SaifulFeroz\Select2TableBundle\Form\Type\Select2TableType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class OrderType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('customer', Select2TableType::class, [ 'table_name' => 'tbl_customers', 'primary_key' => 'id', 'text_property' => 'name', 'property' => ['name', 'email', 'phone'], // Searchable columns 'remote_route' => 'app_customer_autocomplete', 'minimum_input_length' => 2, 'page_limit' => 10, 'scroll' => true, 'allow_clear' => true, 'placeholder' => 'Select a customer...', ]); } }
2. Autocomplete Controller Endpoint
Create an autocomplete controller action and use the provided AutocompleteService:
namespace App\Controller; use App\Form\OrderType; use SaifulFeroz\Select2TableBundle\Service\AutocompleteService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Attribute\Route; class AutocompleteController extends AbstractController { #[Route('/autocomplete/customers', name: 'app_customer_autocomplete', methods: ['GET'])] public function customerAutocomplete(Request $request, AutocompleteService $autocompleteService): JsonResponse { $results = $autocompleteService->getAutocompleteResults($request, OrderType::class); return new JsonResponse($results); } }
Configuration Reference
You can customize global defaults in config/packages/select2_table.yaml:
select2_table: minimum_input_length: 1 # Characters before triggering search page_limit: 10 # Number of records per page scroll: false # Enable infinite scrolling allow_clear: false # Show clear ('x') button delay: 250 # Debounce delay in milliseconds language: 'en' # Select2 language code theme: 'default' # Select2 theme (e.g. 'default', 'bootstrap-5') cache: true # Client-side AJAX cache cache_timeout: 60000 # Cache lifetime in ms (0 = indefinite) table_name: null # Default table name text_property: null # Default column for text label primary_key: 'id' # Default primary key column order_by: null # Column used to order results (defaults to text_property) width: null # CSS width (e.g. '100%', 'resolve') render_html: false # Allow HTML rendering in results (see security note) allow_add: enabled: false # Enable new tag creation new_tag_text: ' (NEW)' # Text appended to new tags new_tag_prefix: '__' # Prefix added to submitted value tag_separators: '[",", " "]'
Security —
render_htmlWith
render_html: truethe bundle disables Select2's markup escaping and injects thehtmlcolumn of each row into the page as raw HTML. Any user-controllable content in that column is then executed as script (stored XSS).Only enable this when the
htmlcolumn is written exclusively by trusted code, and sanitize the value server-side before storing it. Leave itfalseotherwise.As a safeguard the
htmlcolumn is omitted from the AJAX response entirely unless the field setsrender_html: true, so fields that do not opt in never receive the raw value in the first place.
Result ordering
Paginated results are always ordered by a column so that pages partition the result
set deterministically. Without an explicit order_by the text_property column is
used. Set order_by when you need a different order (for example created_at):
$builder->add('country', Select2TableType::class, [ 'table_name' => 'tbl_countries', 'text_property'=> 'name', 'order_by' => 'created_at', ]);
Advanced Recipes
1. Cascading / Dependent Dropdowns (req_params)
When selecting a City that depends on the chosen Country, specify req_params:
$builder ->add('country', Select2TableType::class, [ 'table_name' => 'tbl_countries', 'remote_route' => 'app_autocomplete', 'property' => 'name', ]) ->add('city', Select2TableType::class, [ 'table_name' => 'tbl_cities', 'remote_route' => 'app_autocomplete', 'property' => 'name', 'req_params' => ['country_id' => 'parent.children[country]'], 'callback' => function (\Doctrine\DBAL\Query\QueryBuilder $qb, $request): void { if ($countryId = $request->get('country_id')) { $qb->andWhere('country_id = :country_id') ->setParameter('country_id', $countryId); } }, ]);
2. Multi-Column Search & Custom Filtering Callbacks
Search across multiple columns (e.g. first_name, last_name, email) and filter active rows:
$builder->add('agent', Select2TableType::class, [ 'table_name' => 'tbl_agents', 'primary_key' => 'id', 'text_property' => 'full_name', 'property' => ['first_name', 'last_name', 'email'], 'remote_route' => 'app_agent_autocomplete', 'callback' => function (\Doctrine\DBAL\Query\QueryBuilder $qb, $request): void { $qb->andWhere('is_active = :active') ->setParameter('active', 1); }, ]);
3. Rich HTML Results & Avatar Icons
Set render_html: true on your form field:
$builder->add('member', Select2TableType::class, [ 'table_name' => 'tbl_members', 'remote_route' => 'app_member_autocomplete', 'property' => 'username', 'render_html' => true, ]);
In your controller, return an html property in the row array:
// In custom repository or callback query: $results = [ 'results' => [ [ 'id' => 1, 'text' => 'Jane Doe', 'html' => '<img src="/avatars/jane.png" class="rounded-circle me-2" width="24" /> <strong>Jane Doe</strong> <span class="badge bg-primary">Admin</span>' ] ], 'more' => false, ];
4. Embedded Collection Form Support
If using Symfony Form Collections with data-prototype or dynamic additions, select2table.js automatically detects newly added elements and initializes them without extra code.
Upgrading
From 2.0.0
2.0.0 could not be installed: its extension alias did not match the name Symfony
derives from the bundle class, so cache:clear aborted with a LogicException.
Fixing it required renaming the bundle class and its configuration key.
| 2.0.0 (broken) | Current | |
|---|---|---|
| Bundle class | SaifulFerozSelect2TableBundle |
Select2TableBundle |
| Config key | saifulferoz_select2_table |
select2_table |
| Config file | config/packages/saifulferoz_select2_table.yaml |
config/packages/select2_table.yaml |
| Twig namespace | @SaifulFerozSelect2Table |
@Select2Table |
Update config/bundles.php, rename the configuration file and its root key, and
update the form_themes entry as shown in the installation section.
Backward Compatibility
- Legacy namespace
Feroz\Select2TableBundle\classes and service aliases remain intact.FerozSelect2TableBundlekeeps theferoz_select2_tableconfiguration key. - Legacy form theme block
{% block feroz_select2table_widget %}delegates automatically tosaifulferoz_select2table_widget.
Testing
Run the PHPUnit test suite:
composer test
License
This bundle is open-sourced software licensed under the MIT License.