vertigolabs/doctrine-full-text-postgres
Adds PostgreSQL full-text search support to Doctrine, letting you index and query text columns with tsvector/tsquery for fast, relevance-ranked searching in Laravel and PHP applications without external search services.
Architecture fit. The package is a Doctrine DBAL/ORM extension, not an Eloquent package. It supplies custom DBAL types (tsvector, tsquery) and DQL functions (to_tsvector, to_tsquery, plainto_tsquery, ts_rank, etc.) so that PostgreSQL full-text search can be expressed inside Doctrine DQL rather than raw SQL. In a standard Laravel application — which is Eloquent-first — this is a foreign-body fit. It only becomes architecturally natural if the application already runs Doctrine through laravel-doctrine/orm (the Laravel Doctrine bridge) or is a Symfony application. The package itself is small: it is essentially a registration layer over Postgres primitives, which is an asset for risk (easy to fork) and a liability for value (thin wrapper around what the DB already does).
Integration feasibility. Feasible but conditional. Requirements: PostgreSQL as the primary datastore, Doctrine DBAL/ORM in the stack, and a willingness to register custom DQL functions and types in Doctrine configuration. For an Eloquent codebase, the cost is disproportionate: you would be introducing a second ORM (or at least a second persistence abstraction) to gain a query ergonomic that Laravel already provides. Laravel 9+ ships whereFullText() with PostgreSQL support, which covers the common to_tsvector @@ to_tsquery case natively. The package's incremental value over that is typed hydration and DQL composability, not raw capability.
Technical risk. High on maintenance, moderate on compatibility.
tsvector, tsquery, GIN indexes, text search dictionaries) is stable and well-documented, so the concept is durable even if the package is not.Key questions for the team.
whereFullText + GIN index.)doctrine/dbal and doctrine/orm majors are in the lockfile, and does the package's constraint allow them?ts_rank, ts_rank_cd, or custom weighting — and does the package expose it or do we drop to raw SQL anyway?Stack fit. Two viable postures:
whereFullText(), a generated tsvector column, and a GIN index. Reaching for this package here adds an ORM and a dormant dependency to solve a problem Laravel already solves.Migration path (assuming the Doctrine posture).
tsvector, tsquery) and DQL functions in the Doctrine configuration (e.g. config/doctrine.php under custom_string_functions / custom_numeric_functions / types).search_vector column via migration (tsvector), populated by trigger or generated column, with a GIN index.to_tsvector('english', body) against a sample corpus.Compatibility. Verify three gates before committing: package constraint vs. locked DBAL/ORM majors, PHP version support (8.1/8.2/8.3), and Laravel/Symfony framework version. Given the 2021 release date, expect to pin versions or patch the package's composer.json. Test hydration of tsvector fields explicitly — typed conversion is the most fragile seam.
Sequencing. Run as a time-boxed spike on a branch before committing: (a) prove type/function registration works on the current lockfile, (b) benchmark query plans with EXPLAIN ANALYZE on realistic data volume, (c) compare effort against native whereFullText and against a pure raw-SQL repository method. Decide build-vs-buy-vs-fork at the end of the spike. If the spike shows friction, the fallback — a handful of raw SQL calls wrapped in a repository — is genuinely cheap.
Maintenance. The dominant operational fact: the package is unmaintained. Treat it as vendored code from day one — fork it into the org, tag a version, and own it. The surface is small enough that this is hours, not weeks, but it must be an explicit decision, not a drift into "we'll deal with it later." Budget a review each time DBAL or PHP majors bump.
Support. Community support is effectively nil: 17 stars, no dependents, last release 2021. Internal expertise is the only support channel. Document the DQL functions and index strategy in the repo so the knowledge is not held by one engineer.
Scaling. PostgreSQL FTS scales well within a single primary: GIN indexes handle millions of rows competently, read replicas serve search reads fine. Watch write amplification — tsvector recomputation and GIN index maintenance add cost to every insert/update of the indexed columns. If search volume or corpus size outgrows single-node Postgres, the exit is to a dedicated search engine (OpenSearch/Meilisearch/Typesense); this package does not abstract that path, so plan the repository boundary to make the swap survivable.
Failure modes.
unaccent, switching language) → existing search_vector values become stale until reindexed; requires a coordinated reindex window.ts_rank output is not intuitive and must be tuned against real queries; users will notice relevance regressions.to_tsquery composition → injection or syntax errors on user input; prefer plainto_tsquery/websearch_to_tsquery at the boundary.Ramp-up. Engineers must learn both Doctrine DQL and Postgres FTS semantics (dictionaries, weights, ranking). For an Eloquent-only team this is a real onboarding cost with little transferable payoff. For a team already on Doctrine, it is a day of reading docs plus a spike. Factor ramp-up into the build-vs-buy call: native whereFullText plus a GIN index gets an Eloquent team to working search in an afternoon, which is very likely the better TPM recommendation despite the package's conceptual neatness.
How can I help you explore Laravel packages today?