gkovrey/postgres-search-bundle
gkovrey/postgres-search-bundle integrates PostgreSQL full-text search into Symfony/PHP applications, offering database-native indexing and query support for fast, relevant search results without an external search engine. Configurable, lightweight, and easy to wire into existing projects.
Architecture fit — poor, and not in the way the brief assumes. The package is a Symfony2 bundle, not a Laravel package. It ships a *Bundle class, a DependencyInjection/ extension, and Symfony Resources/config/services.yml wiring. Laravel's service container has no concept of bundles, so nothing loads: no service provider, no auto-discovery, no config merge. Beyond packaging, the bundle's real functionality (Doctrine 2 DQL custom functions and schema/listener hooks) is bound to the Doctrine ORM persistence layer, while Laravel defaults to Eloquent. Two independent mismatches — framework and ORM — must both be resolved before a single line of value is delivered.
Integration feasibility — effectively zero without a rewrite. Composer resolution will fail on any current Laravel (10/11) project. Expect hard conflicts on symfony/* (bundle requires 2.x, Laravel 11 requires 6/7), doctrine/orm (~2.2–2.4 vs current 2.19+/3.x), and PHP platform constraints (package era implies PHP 5.x/7.0; Laravel 11 requires 8.2+). The useful surface area is narrow: DQL functions wrapping to_tsvector / plainto_tsquery / ts_rank, plus index DDL helpers. That is reimplementable in a Laravel app in roughly a day of engineering — the bundle is cheaper to understand than to install.
Technical risk — high, in every dimension.
| Risk | Assessment |
|---|---|
| Maintenance | Last release 2016-09-06. ~9 years unmaintained. Abandoned in practice. |
| Adoption signal | 0 stars, 0 dependents. No external validation, no production usage evidence, no issue history to mine for known bugs. |
| Security | No CVE response path, no maintainer to patch. Any transitive dependency it pins is frozen at 2016 vulnerability state. |
| Compatibility | PHP 8.x incompatibilities highly likely (removed each(), create_function, old reflection APIs, Doctrine\Common\Persistence). |
| Correctness | PostgreSQL FTS semantics (dictionaries, locale, weighting, ts_rank_cd normalization) are subtle; with no users, there is no bug-finding pressure on the implementation. |
| Licensing | Verify LICENSE file before any procurement motion; unstated license on an abandoned repo is a legal blocker regardless of code quality. |
Key questions for the team:
tsvector columns + GIN index + ranking, or just substring/prefix matching that pg_trgm or LIKE handles?ts_headline)? These drive build effort, not package choice.Recommendation: Do not adopt. Treat the repository as reference material, not a dependency.
Stack fit — none. The fit matrix is a hard fail on framework (Symfony2 ≠ Laravel) and a soft fail on ORM (Doctrine ≠ Eloquent). Even in a hypothetical Symfony shop, Symfony 2.x has been EOL since 2018 and the bundle targets an ORM generation now three majors behind. There is no version of a current PHP stack where this installs cleanly.
Migration path — reimplement the pattern natively, in three steps. The bundle's value is a set of idioms, and those idioms transfer to Laravel cleanly:
tsvector column and a GIN index via a Laravel migration using DB::statement:
DB::statement("ALTER TABLE documents ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,''))) STORED");
DB::statement("CREATE INDEX documents_search_idx ON documents USING GIN (search_vector)");
Generated columns (Postgres 12+) eliminate the trigger-maintenance problem the 2016 bundle solved with listeners — a material simplification.whereRaw("search_vector @@ plainto_tsquery(?, ?)", [...]) with ts_rank_cd ordering. Keep it in one class so the SQL dialect is not leaked across the app.Search::documents($term) behind an interface so a later move to Meilisearch/OpenSearch/Scout is a swap, not a rewrite. This is the decision that actually protects the roadmap.Compatibility. Validate on the target stack before committing: PostgreSQL ≥ 12 (generated columns), PHP 8.2+, Laravel 10/11. Confirm unaccent and the relevant text_search_config dictionaries are installed on the target database — a frequent staging-to-prod surprise.
Sequencing.
VACUUM/autovacuum tuning on the target table, relevance tuning with real queries from product.Alternative evaluation (short list). laravel/scout with a Postgres driver, or a maintained FTS package, if the team wants a dependency rather than in-house SQL. Either is a strictly better bet than a 2016 Symfony2 bundle, because both are installable, testable against your stack, and have a maintainer to escalate to.
Maintenance — the dominant cost, and it is total. An unmaintained 2016 dependency means the team inherits the entire maintenance burden the moment it lands, plus the burden of the framework/ORM port. That is strictly worse than building the ~200 lines of SQL and query-object code yourself, which the team would own and understand either way. Cost of ownership: the package adds work relative to a native implementation.
Support — none exists. No maintainer SLA, no issue triage, no release cadence, no security disclosure channel. Escalation path is "read the source." With 0 dependents there is also no peer community to consult. In a procurement or architecture-review context this is an automatic finding: no viable support model.
Scaling — the Postgres side is fine; everything around it is not. PostgreSQL FTS with a GIN index scales well to tens of millions of rows for read-heavy search. Real constraints to plan for:
tsvector; on high-write tables this shows up as index maintenance cost. Partitioning or a narrower indexed field set mitigates.tsvector adds bytes per row; acceptable, but size it against the largest text columns.ts_rank sorts over large result sets — bound with LIMIT and, if needed, ts_rank thresholds.Failure modes to instrument before launch:
| Failure | Symptom | Mitigation |
|---|---|---|
Stale search_vector |
New/edited records invisible in search | Generated column removes the class of bug entirely; if trigger-based, add a reconciliation job + drift check |
| Locale/dictionary mismatch | Wrong stemming, missed matches for non-English content | Pin text_search_config explicitly per column; test with production-language corpora |
Missing unaccent extension |
Accented queries return nothing | Provision extension in migrations, not manually |
| Unbounded ranking sort | Slow queries under load | LIMIT, pagination, EXPLAIN ANALYZE in CI against representative data |
| PHP 8 incompatibility (if adopted) | Fatal errors on autoload or first call | Eliminated by not adopting |
| Silent relevance regression | Users report "search is bad" with no error signal | Capture zero-result queries; add relevance test fixtures to CI |
Ramp-up. Low for the native path — a mid-level Laravel engineer with basic Postgres familiarity can own it in under a week, and the SQL is legible to any DBA. High for the package path — the engineer must first learn Symfony2 bundle conventions and Doctrine 2 internals to port code that will then be thrown away. The ramp-up asymmetry alone justifies the build decision.
Bottom line for the product owner: the package scores 0.03 for good reasons. It is a 2016 Symfony2/Doctrine artifact with no maintainer, no users, and no installable path into a Laravel codebase. The correct move is to harvest its ideas — tsvector column, GIN index, ranked tsquery matching — into ~200 lines of first-party Laravel code behind a search interface, and reserve the dependency decision for the point where requirements genuinely exceed what PostgreSQL FTS can do. Budget one engineering week, not a migration project.
How can I help you explore Laravel packages today?