Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Select2Entity Bundle Laravel Package

kissifrot/select2entity-bundle

A Symfony bundle that turns entity form fields into AJAX-powered Select2 inputs, letting users search and pick related entities from large datasets without loading them all. It provides a form type, autocomplete controller, and configurable endpoints for paginated, server-side entity lookup.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package — no service provider, no facade, no config/select2entity.php. Treat it as a reference implementation you either install into a Symfony app or port in ~100 lines to Laravel.

If you're in a Symfony codebase:

  1. composer require kissifrot/select2entity-bundle
  2. Register the bundle (AppKernel::registerBundles() on Symfony 2/3, config/bundles.php on 4+ — Flex will not auto-register it).
  3. Import the AJAX route the widget calls:
    # config/routes.yaml
    select2entity:
        resource: "@Select2EntityBundle/Resources/config/routing.xml"
    
  4. Make sure jQuery and Select2 (v3 or v4 — check which the bundled template expects) are loaded in your base layout. The bundle does not manage assets for you.
  5. First use case — replace a 50k-row <select> with a searchable remote picker:
    $builder->add('customer', Select2EntityType::class, [
        'class'        => Customer::class,
        'remote_route' => 'select2entity_remote',
        'primary_key'  => 'id',
        'text_property'=> 'name',
        'allow_clear'  => true,
        'placeholder'  => 'Search customers…',
    ]);
    

Where to look first: Form/Type/Select2EntityType.php (the full option list), Controller/Select2EntityController.php (the exact JSON/query contract), and Resources/views/Form/*.twig (how the JS is wired). Those three files are the bundle.

Implementation Patterns

Single vs. multiple selection. Default is many-to-one. Set 'multiple' => true for many-to-many; the data transformer hydrates an ArrayCollection of ids from the posted array. Edit forms work if you pass the existing entity/collection as data — the widget renders the selected labels server-side so they survive the first paint.

The three-piece pattern (port this to Laravel). The bundle is really:

  1. A form widget that renders <select multiple> plus a JS init block.
  2. An endpoint returning {"results":[{"id":1,"text":"Acme"}],"pagination":{"more":true}}.
  3. A transformer that turns posted ids back into entities.

Laravel equivalent:

// routes/web.php
Route::get('/select2/customers', function (Request $request) {
    $q = trim((string) $request->input('q'));
    $rows = Customer::query()
        ->when($q !== '', fn ($qb) => $qb->where('name', 'like', "%{$q}%"))
        ->orderBy('name')->limit(11)->get();

    return response()->json([
        'results'    => $rows->take(10)->map(fn ($c) => ['id' => $c->id, 'text' => $c->name]),
        'pagination' => ['more' => $rows->count() > 10],
    ]);
})->middleware('auth');
<select name="customer_id" class="select2-remote" data-url="{{ route('select2.customers') }}">
    @if($customer)<option value="{{ $customer->id }}" selected>{{ $customer->name }}</option>@endif
</select>

Custom endpoint instead of the bundled one. Always preferred: point remote_route at your own controller so you can apply tenant scoping, soft-delete filters, role checks, and a hard LIMIT. The default controller answers whatever class you hand it in the query string.

Server-side pre-filtering. For "customer must belong to this account," a custom action with a route parameter (/accounts/{account}/select2/customers) is cleaner than trying to pass context through the widget's options.

Validation. The transformer only resolves ids that exist; still validate with an entity/exists constraint afterward — a client can post any integer.

Gotchas and Tips

  • The default endpoint is class-name driven. Select2EntityController reads the entity FQCN and text_property from query parameters. That is an information-disclosure and enumeration surface if the route is public. Either firewall it hard or expose only an explicit allow-list of class → property pairs, or roll your own action.
  • Symfony version drift. The bundle targets Symfony 2/3. On 5/6, check composer.json constraints before installing; you may need a fork or to register the controller as an explicit service (no autowiring of old-style controllers). Do not plan a sprint around it working unmodified.
  • Select2 v3 vs v4 param mismatch. v3 sends q + page and expects a bare array; v4 sends term/q and expects {results, pagination}. If the dropdown spins forever, log the actual XHR payload — this is almost always the cause.
  • text_property must be a real column or __toString(). Nested paths (customer.name) are typically not supported; add a getter or a Doctrine SELECT alias instead.
  • Infinite scroll needs pagination.more. Setting scroll: true without returning that key shows only the first page with no way to page further. Pair it with page_limit.
  • LIKE %term% doesn't use an index. On big tables this is your bottleneck. Cap page_limit (10–20), add a trigram/full-text index, and debounce via delay: 250 so you aren't firing a query per keystroke.
  • Asset collisions. The bundle expects a Select2 build to be present; if your layout already loads Select2 4.x, don't also load the bundle's copy — double-init produces duplicated dropdowns.
  • Stale labels after a validation error. If the endpoint is unreachable on re-render, Select2 drops the selected text even though the id is correct. Render the current selection into the markup server-side (as in the Blade example) and it survives.
  • Debugging checklist: network tab for the XHR URL and params → confirm the route name resolves (bin/console debug:router) → log the generated DQL/SQL in the controller → confirm JSON shape. Ninety percent of "it doesn't work" is step 1 or 3.
  • Caching is client-side only. cache/cache_timeout memoize XHRs in the browser; nothing is cached server-side, so a rename shows up stale until timeout.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource