alsatian/postgres-search-bundle
A Symfony bundle that brings PostgreSQL full-text search to Doctrine applications, enabling native indexing and querying so you can add fast, relevant search without an external engine. It provides convenient integration for building search features directly on Postgres.
Framing correction (decision-relevant): the request describes this as a "Laravel/PHP package." It is not. alsatian/postgres-search-bundle is a Symfony bundle built on Doctrine ORM/DBAL. The dominant finding of this assessment is that the delivery vehicle — not the search pattern — determines whether this is usable. Any ticket scoped as "evaluate for the Laravel app" should be rewritten before estimation.
Architecture fit
tsvector/tsquery, GIN indexes, setweight field boosting, ts_rank_cd, websearch_to_tsquery) is a credible tier-1/tier-2 replacement for Elasticsearch/Algolia/Typesense at low-to-mid corpus sizes and moderate QPS.Integration feasibility
laravel-doctrine, which is a much larger architectural commitment than the search feature justifies.Technical risk
| Risk | Severity | TPM response |
|---|---|---|
| Adoption/supply-chain: 0 stars, 0 dependents, single maintainer | High | Treat as reference design; fork and own in-repo |
Doctrine major-version drift breaks custom DQL FunctionNode signatures |
Medium | Pin versions; contract tests in CI |
| PostgreSQL-only — breaks SQLite-based test suites | Medium | Testcontainers/real PG in CI, or driver guards |
Injection surface if tsquery built by string concatenation |
High if present | Code review of generator before any pilot |
Silent failures: language mismatch, NULL columns dropped via COALESCE omission |
Medium | Fixture-based tests asserting non-empty results |
Bulk DQL updates skip lifecycle callbacks → stale tsvector |
Medium | Prefer DB triggers over entity listeners |
to_tsquery throws on malformed user input → 500s |
Medium | Standardize on websearch_to_tsquery/plainto_tsquery |
Key questions (gate items before pilot approval)
tsquery predicates, or concatenate user input?Stack fit matrix
| Stack | Verdict | Path |
|---|---|---|
| Symfony + Doctrine ORM + PostgreSQL | Green | Install, or vendor the DQL functions in-repo |
| Laravel + Eloquent + PostgreSQL | Red (as dependency) / Amber (as design) | Port patterns into an in-house SearchService; or evaluate a Laravel-native FTS package or Scout + Meilisearch |
Laravel + laravel-doctrine |
Amber | Only if Doctrine is already strategic; otherwise the integration cost dominates |
| Any multi-database requirement (MySQL/SQLite prod) | Red | tsvector is unavailable; stop here |
Migration path
composer require, register the bundle, configure searchable fields, then immediately move to a stored tsvector column with a GIN index and DB-trigger maintenance. Do not ship on-the-fly to_tsvector in WHERE clauses — it forces sequential scans.search_vector column, a trigger or model observer, a GIN index, and a query scope using whereRaw("search_vector @@ websearch_to_tsquery(?, ?)") with bound parameters. This is the recommended path and is smaller than the integration work to make Doctrine fit.app/Doctrine/Functions and own them. This converts an unmaintained dependency into ~200 lines of first-party code with no supply-chain exposure.Compatibility checks to schedule
websearch_to_tsquery; ≥ 12 if generated columns are preferred over triggers.ext-pdo_pgsql.Sequencing (recommended)
EXPLAIN ANALYZE in psql, and complete the injection review. Output: an ADR — adopt, fork, port, or reject.title = A, body = B), GIN index, trigger maintenance, pagination.pgvector or Meilisearch) against that data, not against intuition.Maintenance. The bundle is unmaintained in practice; adoption means the team owns it. Budget a named owner, an in-repo fork, and a quarterly review against Doctrine/PHP upgrades. Realistic ongoing cost is low (small surface, few hundred lines) but non-zero — this is the trade for avoiding a vendor contract.
Support. No vendor, no SLA, no security-review trail, no community triage. Escalation is entirely internal, which means on-call needs PostgreSQL full-text competence, not just Laravel/Symfony competence. Document this in the runbook before launch, not after.
Scaling. Comfortable territory is low tens of millions of rows with well-chosen GIN indexes and read-heavy, modest-QPS traffic. Watch for: GIN write amplification on hot tables, index bloat requiring REINDEX/VACUUM policy, and planner regressions where the index silently stops being chosen (sequential scan latency cliff). Benchmark write paths before committing a high-write entity to a maintained tsvector.
Failure modes to design against
NULL source columns omitted from COALESCE → rows silently dropped from @@ comparisons.createQuery()->execute() bypasses Doctrine lifecycle callbacks → stale vectors. Use DB triggers.to_tsquery → uncaught exception → 500s. Use websearch_to_tsquery for anything user-supplied.Ramp-up. An engineer fluent in PostgreSQL FTS needs ~1–2 days; a Laravel-only engineer needs ~1 week plus a reference implementation. The bundle's real value to the org is as that reference — a worked example of Doctrine-side search modeling that shortens a port.
Cost position. Near-zero marginal infrastructure and no search-SaaS line item, offset by a small, permanent engineering ownership cost and a hard capability ceiling (no typo tolerance, no synonyms, no faceting, no semantic retrieval). State that ceiling in the feature's acceptance criteria before launch so expectations are set with stakeholders rather than discovered at demo.
How can I help you explore Laravel packages today?