smolevich/reindexer-client
PHP client for Reindexer, the high-performance in-memory document database. Offers a fluent query builder, CRUD operations, namespace and index management, filtering, sorting, pagination, and straightforward integration with Laravel and other PHP applications.
This is a thin SDK over Reindexer's HTTP/gRPC API, published by a single maintainer and sitting at 10 stars. Treat the README and the tests/ directory as the specification — they are more reliable than any docs site, and the API surface is small enough to read in one sitting (src/ is the whole client).
1. Stand up a server first. Reindexer is a separate process; the PHP package only speaks to it.
docker run -d --name reindexer -p 9088:9088 -p 9089:9089 reindexer/reindexer
2. Install and configure.
composer require smolevich/reindexer-client
You'll need a PSR-18/PSR-7 HTTP stack (or Guzzle) for the HTTP transport. The gRPC transport additionally needs ext-grpc — if that isn't installed in your runtime, stay on HTTP until you have a measured reason not to switch.
3. Where to look first, in order:
tests/ — the de-facto usage guide; every method has an example call there.src/ — client class (transport wiring), namespace/schema DTOs, query builder or raw-SQL helpers./api/v1/ HTTP endpoints — when the SDK doesn't expose something, you can always curl it and know whether the gap is in PHP or in Reindexer.4. First use case — a searchable catalog. Create a namespace, insert documents, run a query. This is the workload Reindexer is built for and the one that will tell you within an hour whether the package is viable for you:
// Names from the package's src/ — confirm exact signatures against tests/.
$client = new ReindexerClient('http://127.0.0.1:9088', 'catalog');
$client->createNamespace('products', [
'id' => ['type' => 'int64', 'index' => ['is_pk' => true, 'type' => 'hash']],
'name' => ['type' => 'string', 'index' => ['type' => 'text', 'config' => ['stemming' => true]]],
'brand' => ['type' => 'string', 'index' => ['type' => 'hash']],
'price' => ['type' => 'double', 'index' => ['type' => 'tree']],
]);
$client->insert('products', ['id' => 1, 'name' => 'Trail Runner GTX', 'brand' => 'Acme', 'price' => 149.0]);
$results = $client->query(
"SELECT id, name, price FROM products WHERE MATCH(name, 'trail runner') AND price < 200 "
. "ORDER BY price DESC LIMIT 20"
);
If that round-trips, you have everything you need for the rest of the integration.
Wrap the client in a repository, never call it from controllers. The client is a transport object; put domain-specific query methods one layer above it so you can swap transports, add caching, or migrate off the package without touching application code.
final class ProductSearchRepository
{
public function __construct(private ReindexerClient $client) {}
public function search(string $term, int $limit = 20): array
{
return $this->client->query($this->buildSql($term, $limit))['items'] ?? [];
}
}
Bind it as a singleton in a service provider — the client should be constructed once per request/worker, not per call, so the underlying HTTP connection is reused.
Bootstrap namespaces as a deploy-time step, not at runtime. Schema creation belongs in an artisan command or migration run once per environment. Creating or altering namespaces under live traffic is the fastest way to stall reads. Keep the schema definitions in version-controlled PHP arrays so staging and production can't drift.
Bulk-index in batches. Reindexer is fast at ingestion, but per-document round-trips over HTTP will dominate. Use the batch/bulk insert the SDK exposes (check tests/ for the multi-item form) and size batches empirically — 500–2000 documents is a reasonable starting range before you measure.
Choose SQL or the query DSL deliberately. If the package offers a builder, prefer it for anything user-influenced, because it handles escaping; drop to raw SQL for complex MATCH/facet/join queries where the builder fights you. Never interpolate request input into an SQL string — Reindexer's SQL dialect has its own quoting rules and the SDK's escaping may be thinner than you expect from Doctrine.
Full-text + facets is the killer pattern. Define the text index at namespace creation (you generally cannot add full-text config cheaply later), then request facets alongside the hits in a single query to avoid double round-trips. Facet fields need their own indexes.
Use transactions for read-modify-write. If you update counters or stock levels, do the read and write inside the SDK's transaction/UPDATE path rather than select-then-insert — otherwise concurrent writers will clobber each other silently, since Reindexer upserts by primary key.
Integration-test against a real container. There is no meaningful in-memory fake here. Spin up Reindexer in CI (docker-compose service), create namespaces from your bootstrap definitions, seed, assert. Tests that mock the client test nothing but your own mocks.
Transport selection: start on HTTP. Switch to gRPC only when profiling shows transport overhead matters (very high query rates or large result sets) — it adds ext-grpc, proto compilation, and a second code path to reason about in prod.
Pin the version exactly. With one maintainer, ten stars, and no evidence of a stable semver contract, "smolevich/reindexer-client": "1.2.*" behaviour is safer than ^1.2. Read the diff of every upgrade; the changelog is likely to be the commit log.
Reindexer SQL is not MySQL. No LIKE semantics you'd recognise, different JOIN support across versions, MATCH() instead of full-text WHERE col LIKE. Write queries against the target server version and keep them in one place.
Result shape is nested and inconsistent between endpoints. Hits usually live under items, with counts elsewhere (total_items, explain). Always ?? [] and always log the raw response on the first integration of any new query — the SDK returns decoded arrays, and a missing key is a silent empty result rather than an exception.
int64 overflow is real. Reindexer stores int64; PHP ints are 64-bit on modern platforms but JSON decoding of large IDs can bite if anything touches them as strings, or on 32-bit builds. Keep primary keys comfortably inside range, and be explicit about id type in the schema.
Auto-generated vs. supplied IDs. If you omit id, Reindexer assigns one — decide up front whether you own identity or the database does, and never mix the two in the same namespace.
Index creation is not instantaneous on large namespaces. After CREATE INDEX / schema change, queries may not use it immediately and the server may be busy building. Do schema changes in a maintenance window, and verify with EXPLAIN-style output that the index is actually being used — a "fast" query that tables-scans will look fine at 10k docs and fall over at 1M.
In-memory means in-memory. Monitor server RSS. A namespace that fits comfortably on your laptop may OOM a small container; Reindexer does have on-disk storage options, but capacity planning is your job, and the PHP SDK gives you no visibility into it.
Timeouts are your problem. Set explicit connect/read timeouts on the HTTP client. Retry only idempotent reads — blind retries on insert/update will duplicate or double-apply.
Debugging playbook: reproduce the failing call as a raw curl against /api/v1/db/{db}/query (or /namespaces). If the curl succeeds, the bug is in your PHP payload (types, nesting, encoding); if it fails, it's server-side schema or syntax. This bisects the problem in one step and is far faster than adding var_dump around the SDK.
Extension points are thin but usable. The transport is the seam: if you need auth headers, tracing, or metrics, wrap the underlying PSR-18 client rather than patching the package. If a server feature is missing from the SDK, issue a raw request through that same transport — you are not locked in.
Budget for abandonment. The realistic risk with a 10-star, single-maintainer SDK is that it stops receiving updates. Mitigate by keeping all Reindexer access behind your repository layer and, if the integration becomes load-bearing, be prepared to fork src/ — it's small enough that forking is a day's work, not a quarter's.
How can I help you explore Laravel packages today?