tudortibu/select2entity-bundle
A Symfony bundle that provides a Select2Entity form type, enabling AJAX-powered Select2 entity selection in forms. It supports autocomplete-style Doctrine entity lookup and configurable remote endpoints for smoother form integration.
This is a fork of the well-known tetranz/select2entity-bundle, pinned to the Symfony 2.x era. Treat it as a jQuery Select2 adapter for Doctrine entity fields: the form type renders a hidden input, Select2 drives a text box, and an AJAX endpoint returns matching entities by name/label. Nothing more, nothing less.
Install via Composer. Because it has zero stars/dependents and likely no Packagist release for your version, add a VCS repository entry rather than relying on composer require:
{
"repositories": [
{ "type": "vcs", "url": "https://github.com/TibuTudor/select2entity-bundle" }
],
"require": {
"tudortibu/select2entity-bundle": "dev-master"
}
}
Register the bundle in app/AppKernel.php (Symfony 2/early 3 only):
new TibuTudor\Select2EntityBundle\Select2EntityBundle(),
If your AppKernel is newer or if this namespace isn't registered, check Resources/config/services.xml in the package to confirm the bundle namespace before wiring it in.
Expose the autocomplete endpoint. Register a controller action and route. The controller proxies the request to the bundle's autocomplete service:
// src/AppBundle/Controller/AjaxController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class AjaxController extends Controller
{
public function autocompleteAction(Request $request)
{
return $this->get('tudortibu_select2_entity.autocomplete')->autocomplete($request);
}
}
# app/config/routing.yml
app_autocomplete:
path: /autocomplete
defaults: { _controller: AppBundle:Ajax:autocomplete }
Verify the actual service id in the bundle's services.xml — forks frequently rename it (tetranz_select2_entity.autocomplete vs tudortibu_select2_entity.autocomplete).
First use case — replace an entity field. Take an entity-backed form field and swap the type:
use TibuTudor\Select2EntityBundle\Form\Type\Select2EntityType;
$builder->add('author', Select2EntityType::class, [
'class' => 'AppBundle:Author',
'remote_route' => 'app_autocomplete',
'text_property' => 'name',
'minimum_input_length' => 2,
'page_limit' => 10,
'allow_clear' => true,
'placeholder' => 'Search authors…',
]);
Check that Select2 and jQuery are loaded in your base template. This bundle is a thin adapter; it does not ship jQuery/Select2 assets. Ensure Select2 (v3 or v4 — see gotchas) and jQuery are available before the form type's JS runs.
Where to look first if something doesn't work: the bundle's Form/Type/Select2EntityType.php (for option names), its Resources/views for the hidden-input markup, and DependencyInjection/Configuration.php (if present) for the autocomplete service's tunables.
Single entity field (many-to-one). The default pattern above. The form type's data_class is the entity, text_property is used as the display label in the AJAX response, and primary_key defaults to id. The submitted value is the entity's ID; Symfony hydrates it back into the entity because Select2EntityType extends the Doctrine EntityType and just overrides rendering/JS.
Multiple selection (many-to-many / one-to-many). Set multiple => true. Select2 submits a comma-separated list of IDs in the hidden input; the parent form treats it as EntityType with multiple => true, so Doctrine resolves the collection naturally.
$builder->add('tags', Select2EntityType::class, [
'class' => 'AppBundle:Tag',
'multiple' => true,
'remote_route' => 'app_autocomplete',
'text_property' => 'label',
]);
Controlling the search query. Most of these bundles pass a repository method name or a custom callback. If the fork supports repository_method, point it at a method that returns an array (or an array of [id, text]), not a Query:
$builder->add('author', Select2EntityType::class, [
'class' => 'AppBundle:Author',
'remote_route' => 'app_autocomplete',
'repository_method' => 'findLike',
// ...
]);
public function findLike(string $q, int $limit, int $page): array
{
return $this->createQueryBuilder('a')
->where('a.name LIKE :q')
->setParameter('q', '%'.$q.'%')
->setMaxResults($limit)
->setFirstResult(($page - 1) * $limit)
->getQuery()
->getResult();
}
If the fork does not support repository_method, override the autocomplete controller in your subclass and run the query there, returning the same JSON shape (results: [{id, text}], total_count).
Rendering in a styled form. The bundle's template emits a hidden input plus a text input and a small <script> block calling $el.select2({...}). If you use a JS build pipeline (Gulp/Webpack/Encore), copy the option array into your own select2() init and set the form type to suppress rendering ('attr' => ['class'=>'select2entity'] plus a custom form_theme), so Select2 loads consistently with your stack.
Integration with the CRUD flow. The natural place is the create/edit form of the owning entity. Keep the field in the form, but strip the autocomplete route from the show view (it's a form-only concern). For a "view" page, render the related entity's text_property directly rather than a disabled Select2.
Testing. The autocomplete controller is testable with WebTestCase by hitting the route with ?q=foo&page=1. Assert the JSON structure (results, total_count) rather than HTML.
Symfony version lock-in. This is a Symfony 2-era bundle. app/AppKernel.php, $this->get('...'), and AppBundle:Entity notation are all strong hints. On Symfony 4+ you'll need to (a) use FlexRecipe-style service config or manual services.yaml aliasing, (b) replace the bundle-level Resources/config/services.xml autoloading with Resource\config\…, and (c) swap get('…') for constructor injection. If you're on Symfony 5/6, this package is effectively unmaintained for you — fork it.
Select2 v3 vs v4 incompatibility. The bundle's inline JS is written for one API shape. v3 uses id/text and ajax.results; v4 uses processResults. If you upgrade Select2 in your app, the dropdown may stop populating with no console error — the AJAX response is parsed but the callback is never invoked. Pin Select2 in package.json to match the bundle, or override the template and write v4-compatible processResults.
jQuery load order. The hidden-input JS references jQuery at parse time. If your layout defers jQuery (defer) or bundles it after the form partial, you get $ is not defined. Either inline the form partial above your scripts or wrap in DOMContentLoaded.
The AJAX endpoint leaks data by default. The autocomplete route is a plain GET that returns entity labels. Under a security.firewall that permits anonymous access you will expose names/slugs to unauthenticated users. Put the route behind the same firewall as the form, or check $this->denyAccessUnlessGranted('…') in your controller before delegating.
Pagination is page + page_limit, and the response must include more. Select2's infinite scroll breaks silently if the JSON includes total_count but no more boolean, or if total_count is the full table count while results are capped. Match the two: more = ($page * $page_limit) < $total_count.
text_property vs __toString. If text_property is unset the bundle may fall back to __toString(). A missing __toString on the entity produces blank options (no exception, just empty labels). Always set text_property explicitly and confirm the property has a getter.
Hydration failures on edit forms. On edit, the hidden input must contain the current entity's ID(s), and Select2 needs the initial label(s) in the text input. If the bundle relies on EntityType's data option for hydration but a fork omitted the pre-population of the text input, users see an empty search box on edit even though the saved relation is intact. Pre-fill via 'data' => $entity and inspect the rendered markup in app_dev.php first.
Multiple DISTINCT queries. When the autocomplete query joins a collection (e.g. tags on a product), the generated DQL returns duplicate rows and your result count is wrong. Add ->distinct(true) in your repository method or in the controller's query builder.
Caching. If cache => true is set (Select2's ajax.cache), stale results persist after you add entities. During development set cache => false; in production a short cache_timeout (30–60s) is safer than long TTLs.
Extension points to prefer over editing vendor code. Rather than forking for each change, extend Select2EntityType, override getParent() and configureOptions(), or replace the template via form_themes. If the bundle's service ids clash with tetranz's (likely if both are installed), alias in services.yaml:
services:
tudortibu_select2_entity.autocomplete:
alias: tetranz_select2_entity.autocomplete
Debugging a blank dropdown. Check, in order: (1) the route resolves (app/console router:debug app_autocomplete), (2) the JSON response is valid with curl '…/autocomplete?q=a&page=1' while authenticated, (3) the response keys are id/text (not value/label), (4) minimum_input_length isn't above what you typed. XHR failures in the browser Network tab are the fastest signal.
Abandonment risk. With no stars, no dependents, and a Symfony 2 API surface, do not build new features that assume upstream fixes. Vendor the package (copy the source into src/) if you must ship on modern Symfony, and budget time to migrate to a maintained alternative (e.g. tetranz/select2entity-bundle itself, or a hand-rolled EntityType + small AJAX controller) rather than patching it repeatedly.
How can I help you explore Laravel packages today?