pet0r/sphinxsearch-bundle
Symfony bundle that integrates the Sphinx full-text search engine, providing configuration for indexers and a service layer for running searches from your application.
Accept the framing first. This is a Symfony 2 bundle, not a Laravel package. For a Laravel developer the realistic "day-one" path is not composer require — it's reading the repository as a reference implementation, then wiring SphinxQL/Manticore by hand behind a repository interface. The steps below describe how you would actually use it as published, which only makes sense inside an existing Symfony 2.x app.
1. Install and register the bundle. Add it to composer.json and register it in app/AppKernel.php:
$bundles = array(
// ...
new Pet0r\SphinxsearchBundle\SphinxsearchBundle(),
);
There is no Flex recipe, no auto-discovery. If you vendored it manually, you likely also need a PSR-0/PSR-4 entry in app/autoload.php.
2. Make sure SphinxClient exists. The bundle is a thin wrapper around the SphinxClient class from sphinxapi.php. That class is not shipped with the bundle and is not reliably autoloaded from the sphinx extension. Either load the extension or require_once a vendored sphinxapi.php before the service is first resolved. The first symptom of failure is a Class 'SphinxClient' not found fatal at query time, not at container compile time.
3. Configure the connection and index aliases. Typical shape:
# app/config/config.yml
sphinxsearch:
searchd:
host: 127.0.0.1
port: 9312
indexes:
products:
index: products_main
articles:
index: articles_main
The logical-name → physical-index map is the most underrated part of the bundle: application code asks for products, ops can rename or split the physical index without touching PHP.
4. First use case — a search service call. Inject the configured search service into a controller or service and run a query against a logical index. This is the shape the bundle is designed to support (exact method names depend on the version — check Resources/config/services.xml for the service id and class):
$results = $this->get('sphinxsearch')->search('products', 'blue widget', ['limit' => 10]);
If you need SphinxQL (raw SQL-style queries, filtering, grouping), you are already past what this bundle gives you — drop to SphinxClient or PDO directly.
Where to look first in the source:
DependencyInjection/Configuration.php — the real config schema (this is your spec).DependencyInjection/*Extension.php — how YAML becomes services.Resources/config/services.xml — service ids you will inject.Provider/Search/Indexer classes — the actual query surface.5. On the Laravel side, if you are porting. Do not fork the bundle. Do this instead:
config/search.php as a plain array.SphinxClient-shaped service (or use the Manticore PHP client) as a container singleton.SearchRepository interface with a SphinxRepository implementation.That is the "getting started" that actually pays off.
Inject, don't reach into the container. In Symfony 2 code, autowiring is unreliable — use service ids from services.xml in services.yml or constructor injection in your own classes. In the Laravel port, bind the client as a singleton and inject SearchRepository everywhere.
Wrap the search service in your own interface. This is the single highest-value pattern. The bundle's surface is small and versioned against an EOL framework; a one-method SearchInterface (e.g. search(string $index, string $query, array $opts): SearchResult) lets you swap Sphinx for Manticore or Scout later without touching call sites.
interface ProductSearch
{
public function search(string $term, int $limit = 20): array;
}
final class SphinxProductSearch implements ProductSearch
{
public function __construct(private SphinxClient $client) {}
public function search(string $term, int $limit = 20): array
{
$this->client->SetMatchMode(SPH_MATCH_EXTENDED2);
$this->client->SetLimits(0, $limit);
return $this->client->Query($term, 'products_main')['matches'] ?? [];
}
}
Query construction pattern. SphinxClient is a fluent-ish builder: SetMatchMode, SetFilter, SetSortMode, SetFieldWeights, SetLimits, then Query. Treat each call as building one request — the client is stateful and will carry filters into the next Query if you reuse an instance. Prefer a fresh client per logical query, or a documented ResetFilters/ResetGroupBy discipline.
Indexing workflow — plan the cron. The bundle exposes the query side; indexing is your operational responsibility. Standard pattern:
indexer products_main --rotate.products_delta) built frequently from rows where updated_at > last_index_at.indexer --merge products_main products_delta --rotate.The config map (products → products_main) lets application code stay stable while the main/delta/merge choreography runs underneath.
Result hydration pattern. SphinxClient::Query returns matches with the document id and attributes; the bundle does not hydrate into Eloquent models. The day-to-day pattern is: search returns IDs → fetch models with a single whereIn → reorder in PHP to match the search ranking (this ordering step is the one people forget).
$ids = array_column($matches, 'id');
$models = Product::whereIn('id', $ids)->get()->keyBy('id');
return collect($ids)->map(fn ($id) => $models[$id] ?? null)->filter()->values();
Integration tip — one service per logical domain. Rather than one godlike Search service, expose ProductSearch, ArticleSearch, etc., each bound to its physical index name. This mirrors how the config aliases work and keeps query options (match mode, field weights, limits) colocated with the domain they belong to.
Extension points worth knowing. The bundle's service definitions are the only real extension surface: override a service id in your app's services.yml to swap in a subclass with, say, SetConnectTimeout / SetReadTimeout tuning, or a client wrapper that logs slow queries. There is no event system inside the bundle to lean on.
Testing pattern. Because the client is a single injected object, replace it with a fake returning canned matches — do not hit searchd in unit tests. Reserve integration tests for a running searchd instance, guarded by an env flag.
Laravel-side port pattern (recommended). Replace the Symfony DI layer with a service provider; keep the config map shape identical; implement the repository against SphinxQL over PDO or the Manticore client. Provide an Artisan command (search:index, search:health) as the equivalent of the bundle's implicit "run the indexer" workflow.
The hard blockers, in order of how often they bite:
SphinxClient is not autoloaded. The bundle assumes the class is available; it is not guaranteed by any Composer dependency. Symptom: Class 'SphinxClient' not found on first query, not first boot. Fix: load the extension or require_once the vendored sphinxapi.php in a bootstrap file.Design quirks and pitfalls:
SphinxClient accumulates filters, sort modes, and limits between Query calls. If you inject a shared instance and call it from multiple services, filters leak across requests within the same PHP process. Mitigate with per-query instances or explicit resets.SPH_MATCH_ALL is the historical default; if you want phrase/boolean semantics, set SPH_MATCH_EXTENDED2 explicitly. Silent scoring differences between environments usually trace back to this.DependencyInjection/Configuration.php is more accurate than the README. When the README and the code disagree (they will), trust the code.products) into your service layer and let config map to the physical index. Passing physical names directly couples application code to ops decisions and makes delta merges painful.searchd must be configured to expose port 9306.Operational gotchas to instrument for from day one:
updated_at lag or an explicit "last indexed at" heartbeat — this is the most common Sphinx incident.--rotate (hot swap) and keep a known-good index as rollback.searchd surfaces on first query. Add a health-check command that opens the connection and runs a trivial Query, and run it in deploy smoke tests.Where to extend, in order of least pain:
SphinxClient — set timeouts, log slow queries, add retry. Lowest-risk extension.services.yml to swap in that wrapper. Reaches into the bundle's DI wiring, which is stable enough for this.The debugging toolkit:
SphinxClient::SetArrayResult(true) to get predictable array-shaped results instead of the default nested-object shape — cuts a class of "why is matches shaped differently here" bugs.SetWarning/GetLastWarning and GetLastError after every Query; the client is quiet by default and warnings are the only signal for issues like "index not found, fell back to nothing."searchd.log and query.log (enable query_log in searchd.conf) before suspecting the PHP side — most "wrong results" turn out to be a stale index or an unexpected SetFilter.Configuration.php against your config.yml when a service fails to resolve — typos there fail opaquely.Bottom line for a day-to-day user: the bundle is a thin, unmaintained wrapper over a superseded search daemon. If you are already running it in a Symfony 2 app, the useful discipline is to isolate it behind one interface, keep the logical-index indirection, and instrument index lag. If you are starting fresh, read the repository for the shape of the integration (logical index map, stateful query builder, ID-out-then-hydrate pattern) and implement that shape against SphinxQL or Manticore — do not consume the bundle.
How can I help you explore Laravel packages today?