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

Autocomplete Bundle Laravel Package

lifo/autocomplete-bundle

Lifo Autocomplete Bundle adds autocomplete support to Symfony applications, helping connect form fields to suggestion endpoints and display matching results for simple typeahead behavior.

View on GitHub
Deep Wiki
Context7

Getting Started

First, resolve the framework question. lifo/autocomplete-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/autocomplete.php to publish. On Laravel 10/11 the symfony/form, symfony/framework-bundle, and symfony/http-kernel constraints almost certainly will not resolve, and if they did you would be installing inert code. So "getting started" means one of two things:

  1. On a Symfony host — actually install and use the bundle.
  2. On Laravel — lift the contract (endpoint, response shape, provider abstraction) and implement natively. This is the path you should default to.

Where to look first, in order:

  • composer.json — the Symfony and PHP constraints. If it pins Symfony <4.0 or PHP <7.4, stop: the source will not run unmodified on a current runtime, and anything you build is greenfield with the repo as a spec.
  • Resources/config/routing.xml|yaml (or config/routing.*) — the route name, path, and default controller. This tells you the endpoint contract faster than the PHP will.
  • The controller behind that route — this is the whole package in ~50 lines: read q, call a provider, json_encode the result.
  • The form type under src/Form/Type/ — the Symfony-facing widget that renders the text field and attaches the JS.
  • The data provider interface/implementations — the Doctrine repository wrapper. This is the piece you will reimplement regardless of host framework.

Minimal Symfony steps (only if you have a Symfony app to run it in):

composer require lifo/autocomplete-bundle

Register it in config/bundles.php, import the bundle's routing, define a provider service that returns [{id, label}] for a given term, and use the bundle's form type in your form. That is the whole loop: form widget → JS → JSON route → provider → JSON.

First use case for a Laravel developer — don't install; port. The one-to-two-day version is an invokable controller:

// routes/api.php
Route::get('/autocomplete/users', AutocompleteUsers::class)
    ->middleware('throttle:60,1');

// app/Http/Controllers/AutocompleteUsers.php
public function __invoke(Request $request)
{
    $q = trim((string) $request->query('q', ''));
    if (strlen($q) < 2) return response()->json([]);

    return response()->json(
        User::query()
            ->where('name', 'like', $q.'%')   // prefix, not %term%
            ->orderBy('name')
            ->limit(10)
            ->get(['id', 'name as label'])
    );
}

That is the bundle's value proposition in fifteen lines, minus the Symfony coupling. Read the repo to confirm the response shape and any options (min length, limit, which column) match what you build, then credit under MIT and move on.

Implementation Patterns

The endpoint contract is the reusable artifact. The bundle normalizes to a JSON array of objects with an id and a human label (label, text, or value depending on the vintage — check the controller, this is the most common source of frontend breakage). Everything else — Symfony DI, annotations, Doctrine repository access — is host-specific and does not survive a port. Copy the shape, not the wiring.

Provider abstraction is worth preserving conceptually. The bundle splits "which endpoint" from "which query." Mirror that in Laravel with a small interface or just a scoped query-builder method per entity:

interface AutocompleteSource {
    public function search(string $term, array $scope = []): array;
}

Then each endpoint (/autocomplete/users, /autocomplete/tags) is a thin bind of source + controller. This keeps the ranking logic in one testable place instead of scattered across closures.

Bind and cap on the server, debounce on the client. The server-side pattern that holds up: reject terms under 2–3 characters, cap results at 8–15, order deterministically, and never run an unbounded LIKE '%term%'. On the client:

let ctrl;
input.addEventListener('input', debounce(async (e) => {
  ctrl?.abort();
  ctrl = new AbortController();
  const res = await fetch(`/api/autocomplete/users?q=${encodeURIComponent(e.target.value)}`,
                          { signal: ctrl.signal });
  renderSuggestions(await res.json());
}, 250));

The AbortController matters more than the debounce — without it, out-of-order responses overwrite newer results and you get the classic "suggestions flicker back to a stale query" bug.

Cache the hot path. Autocomplete fires per keystroke per user; it is disproportionately expensive relative to its business value. Cache (source, scope, normalized term) for a short TTL (30–120s) and you cut query volume by an order of magnitude. Key on the tenant/permission scope too, or you leak results across users.

Frontend integration depends on the library, not the bundle. The repo advertises jQuery UI and Select2. Those expect different payloads — jQuery UI wants [{value, label}], Select2 wants {results: [{id, text}]}. If you port, pick one and adapt the response once, in the controller, rather than per-component.

Testing. Two tests carry most of the weight: (1) a feature test asserting the endpoint rejects short terms, caps results, and returns the documented shape; (2) a test asserting scope isolation — user A's search never returns user B's rows. Everything else is cosmetic.

Gotchas and Tips

Composer will lie to you. Even if resolution "succeeds" with --ignore-platform-reqs, you get a package whose bundle class is never registered and whose routes are never imported. There is no silent-failure mode here that works in your favor. Verify framework fit before touching code.

The PHP constraint is a load-bearing detail. Bundles of this vintage commonly target PHP 5.6–7.x. json_encode behavior, typed properties, and null-coalescing assumptions in the source all shift across that boundary. If you fork, run the source review against your actual PHP version, not the version the README implies.

Unvalidated input is the real risk. A zero-dependent, single-maintainer package with no tests, sitting on an endpoint that accepts user-controlled strings and forwards them to a query builder, is an injection and enumeration surface. Treat any raw interpolation in the bundled controller as a defect to fix, not a pattern to copy. Bind parameters, always.

Result limits are not optional. If the bundled provider does not enforce a LIMIT, under load you will table-scan on every keystroke. Add the cap on day one; retrofitting it after a latency incident is embarrassing.

LIKE '%term%' does not scale. It is fine for admin tables under a few hundred thousand rows and wrong for anything customer-facing. If you need fuzzy matching, typo tolerance, or multi-language, the answer is a search backend (Meilisearch, Typesense, Postgres pg_trgm/tsvector) — the bundle has no opinion on this and neither should you pretend it does.

CSRF and method. The endpoint is a GET, so CSRF does not apply, but confirm the route is not behind session middleware that would serialize requests in a way that hurts latency. A throttle middleware is essential; unauthenticated typeahead is a free DoS vector.

Debugging checklist when suggestions are empty:

  • Route resolves? (route:list on Laravel, debug:router on Symfony.)
  • Is q arriving under the name the controller expects? (term vs q vs query is a classic mismatch.)
  • Did the min-length guard reject the request silently? Try a 3-character term.
  • Is the JSON shape what the JS library expects? Open the endpoint in a browser directly.
  • Does the provider apply a scope that excludes all rows for the current user?

Response-shape drift is the top porting bug. If you lift the controller but not the exact field names, the typeahead renders blank list items. Nail the shape in one test before building the UI.

Extension points worth keeping when you fork: the provider interface (new sources without touching the controller), a limit and min_length option (tune per entity), and a scope hook (tenant/permission filtering). Everything else — bundle registration, annotation loading, form-type inheritance — discard.

Licensing works in your favor. MIT means vendoring, forking, and modifying carry no legal review. That is the one genuine asset here. Use it: copy the pattern, attribute it, and don't add the package to composer.json.

Ownership is the hidden cost. If you fork, name a maintainer and add a note to the codebase explaining why the code exists and where the upstream is. A zero-star package with no dependents becomes orphaned the moment the engineer who ported it rotates off, and the next person has no context to evaluate whether it is safe to delete.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings