verdet/sphinxsearch-bundle
Integrates the Sphinx full-text search engine into PHP applications, with configuration for indexes and sources, data indexing, and query building so you can add fast, scalable keyword search to your existing stack. A lightweight bundle for teams already running Sphinx or SphinxQL.
Reality check first. This bundle targets Symfony 2 and has had no release since October 2016, with zero dependents. Before wiring it into anything, confirm it will actually install against your stack — it predates Symfony 3.3/4 flexible bundles, PSR-4 autoloading conventions, and modern PHP. In practice you will either:
src/ (or a VCS repository entry in composer.json) and modernize the bundle class + autoload map.Install and register:
composer require verdet/sphinxsearch-bundle
// app/AppKernel.php (Symfony 2)
$bundles = [
// ...
new Verdet\SphinxSearchBundle\SphinxSearchBundle(),
];
Know your Sphinx setup before configuring. Sphinx is a separate daemon — indexer builds indexes from a sphinx.conf, searchd serves queries on two ports:
9312 — native binary protocol (Sphinx API)9306 — SphinxQL, MySQL-compatible protocolCheck which one this bundle talks to (look at its service definitions and whether it bundles sphinxapi.php or uses PDO). That single detail dictates your config and how you'll debug queries.
First config pass (app/config/config.yml) — the shape is host/port/index names, approximately:
sphinx_search:
host: 127.0.0.1
port: 9306
indexes:
products: { index: products }
articles: { index: articles }
Where to look first: open the bundle's DependencyInjection/Configuration.php and Resources/config/services.yml in vendor/verdet/sphinxsearch-bundle/. The config tree is the source of truth — docs (if any) will be stale. Then:
bin/console debug:container --show-private | grep -i sphinx
to find the actual service IDs to inject.
First use case: search a Doctrine-backed table. Stand up a Sphinx index in sphinx.conf over the same MySQL table, run indexer --all, boot searchd, then inject the bundle's search service into a controller and query products. This is the path of least resistance and matches how the bundle was designed to be used.
Configuration-defined indexes. Treat the bundle config as a registry mapping logical names → Sphinx index names. Centralize it so application code asks for products, not products_rt_v3. This matters when you re-index: keeping the logical name stable lets you swap physical indexes behind it.
Service injection, not static calls. Resolve the search service through the container and depend on it via constructor injection. Because the bundle is unmaintained, wrap it in your own thin interface:
interface ProductSearchInterface { public function search(string $q, int $page = 1): SearchResult; }
final class SphinxProductSearch implements ProductSearchInterface
{
public function __construct(private $sphinxSearch) {}
public function search(string $q, int $page = 1): SearchResult
{
// delegate to the bundle service, normalize its response
}
}
This is the single most valuable pattern here — it isolates the abandoned dependency behind a seam you control, so replacing it with Manticore/Elasticsearch later is a one-class change.
Indexing as a deployment step, not a request-time action. The bundle gives you queries; it does not manage the Sphinx lifecycle. Wire indexing into your release pipeline:
indexer --all --rotate # rebuild without downtime
--rotate is what lets a live searchd pick up new indexes. Never build a "reindex" button that runs indexer synchronously from a web request — it will time out and can corrupt the live index.
Real-time vs. batch indexes. Two common shapes:
indexer rebuilds periodically, a delta index catches recent rows via a sql_query_range on an autoincrement ID. Query both and merge — most bundles expose multi-index queries, so pass ['products_main', 'products_delta'].INSERT INTO products_rt .... Faster freshness, but you own consistency and the bundle's support for write operations is likely thin. Verify before committing to this.Pagination. Sphinx returns limited result sets; use the offset/limit (start/max_matches in native API) options rather than fetching everything and slicing in PHP. Keep max_matches bounded — the default is often 1000 and silently truncating results is a classic bug.
Query building. Use the bundle's escaping/quoting path rather than string-concatenating user input. For richer features (phrase search, field weights, WITHIN, attribute filters) check whether the bundle forwards raw Sphinx query syntax — if it does, you get @title "exact phrase" and @weight for free; if it exposes a structured query object, prefer that.
Integration with Doctrine. Don't try to make Sphinx a Doctrine driver. Treat it as a read-only index: query Sphinx for IDs, then hydrate entities with a single WHERE id IN (...) — preserving Sphinx's relevance order in PHP. This avoids N+1 and keeps Sphinx out of your write path.
Testing. Because the daemon is external, stub your ProductSearchInterface in unit tests. Keep one integration test that runs against a real searchd in CI (docker image), tagged/skipped by default.
Assume it's abandoned. No maintainer, no releases since 2016, zero dependents. Plan for owning the code. Forking to a private package (or src/) is usually cheaper than fighting composer constraints — and lets you fix the inevitable PHP 7.2+/8.x incompatibilities.
Bundle name vs. daemon dependency. The bundle is only a client. Nothing works if searchd isn't running or the index doesn't exist. Errors here surface as connection refused or "unknown index," not as PHP exceptions with useful messages. Always test mysql -h127.0.0.1 -P9306 (SphinxQL) directly when debugging.
Port/protocol mismatch is the #1 config bug. Pointing the client at 9306 when it speaks the native 9312 protocol (or vice versa) produces confusing handshake failures. Confirm in the bundle's client class which API is bundled.
Sphinx 2.2 vs Sphinx 3 vs Manticore. This package predates Sphinx 3.x and Manticore. SphinxQL syntax, max_matches defaults, and RT index features differ. If you're on a modern stack, consider Manticore (Sphinx fork) — and evaluate whether a maintained client (manticoresoftware/manticoresearch-php) isn't a better choice than resurrecting this bundle. Be honest with stakeholders: this is a stopgap, not a long-term bet.
--rotate and the .sph lock state. Interrupted indexer runs leave .old/.new/.tmp files behind; searchd may fail to start with cryptic "index is locked" or "persistent" errors. Clean up and restart the daemon. Watch disk usage — indexes are full copies.
Escape user input every time. Sphinx query syntax treats @, (, ), ", -, | specially. Unescaped input produces either wrong results or a parse error from the daemon. Route everything through the bundle's escaper; if it has none, build a conservative sanitizer.
max_matches silently truncates. If hit counts look suspiciously round (1000), you've hit the default cap. Raise it in sphinx.conf searchd { max_matches = ... } deliberately — large values cost memory per query.
Config cache quirks (Symfony 2). After editing the bundle config, bin/console cache:clear --env=prod. Stale prod cache causing "config key not found" errors is common. Container compilation also means you can't change host/port at runtime without rebuilding — use parameters and environment-specific config files instead.
Extension points are limited — extend via your wrapper. Expect to touch: the bundle's search service (wrap it), the config tree (add indexes), and command classes (if it ships any). Beyond that, the cleanest extension point is the service definition override — redefine the search service in your app to add logging, metrics, and a fallback. That's also where you'd swap in a Manticore client without touching callers.
Log the generated query. Whatever service you build, log the final Sphinx string with a correlation ID. When results are wrong, the query is the first thing to inspect, and the bundle won't do this for you.
How can I help you explore Laravel packages today?