lexis/lexis-php

Official PHP SDK for the Lexis search API (sync + search endpoints).

Maintainers

Package info

github.com/florentiu/lexis-php

Homepage

Documentation

pkg:composer/lexis/lexis-php

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-04-21 08:11 UTC

This package is auto-updated.

Last update: 2026-04-21 08:14:14 UTC


README

Official PHP client for the Lexis search API — sync your catalog, query the index, done. Zero runtime dependencies beyond ext-curl + ext-json.

Requirements

  • PHP 7.4 or 8.x (all minors supported)
  • ext-curl, ext-json
  • A Lexis API key (Settings → API keys in the dashboard)

Examples below are written with positional arguments so they paste-run on every supported PHP version. On PHP 8.0+ you're free to use named arguments ($client->search(index: 'products', query: 'x')) — the method signatures match.

Install

composer require lexis/lexis-php

Quickstart

<?php
require 'vendor/autoload.php';

use Lexis\Client;

// Managed cloud (default)
$lexis = new Client(getenv('LEXIS_API_KEY'));

// OR — self-hosted / enterprise install, point at your own dashboard URL:
// $lexis = new Client(getenv('LEXIS_API_KEY'), 'https://search.my-company.internal');

// 1. Push your catalog
//    sync->start(indexSlug, indexName, primaryKey, source)
$run = $lexis->sync->start('products', 'Products');

$run->push([
    ['id' => 'sku-1', 'title' => 'Adidași Nike Air', 'price' => 349, 'brand' => 'Nike'],
    ['id' => 'sku-2', 'title' => 'Adidași Puma RS',  'price' => 299, 'brand' => 'Puma'],
    // ... up to millions of docs; SDK chunks into 1000-doc batches
]);

$stats = $run->commit();
echo "Committed {$stats['documents']} docs (deleted {$stats['deleted']})\n";

// 2. Search — (index, query, limit?, offset?, filters?)
$result = $lexis->search('products', 'adidasi');

foreach ($result->hits as $hit) {
    printf("- %s (%.2f) — %s\n",
        $hit->id,
        $hit->score,
        $hit->get('title'),
    );
}

Self-hosted / enterprise deployments

For installs on your own infrastructure, pass the dashboard URL as the second argument — that's the only change needed. Auth header, request / response shapes, retries, error codes: all identical across cloud and self-hosted.

use Lexis\Client;

$lexis = new Client(
    getenv('LEXIS_API_KEY'),              // key from *your* dashboard
    'https://search.my-company.internal', // *your* base URL, no trailing slash
);

Pin the URL and key in your app config (.env, Laravel config, Symfony parameters — whatever fits) so each environment points at the right dashboard: a staging SDK instance talks to the staging Lexis install, prod to prod.

// .env
LEXIS_API_KEY=lexis_live_...
LEXIS_BASE_URL=https://search.my-company.internal

// app code
$lexis = new Client(
    getenv('LEXIS_API_KEY'),
    getenv('LEXIS_BASE_URL') ?: null,   // null falls back to managed cloud
);

Need more than just the URL (custom timeout, retries, user agent, injected HTTP transport)? Build a Config instead — see Configuration reference below.

Sync flow in detail

Full-replace semantics

A sync run is atomic: whatever you push between start() and commit() becomes the entire index content. Documents that were in the index before but aren't in this run are deleted. There's no incremental upsert — if you want to add one product to a catalog of 100k, you still push all 100k in a new run.

Batching

The API caps each /documents call at 1000 documents. The SDK handles this for you: pass as many as you want to push(), they're chunked into 1000-doc batches and POSTed sequentially. One failed chunk aborts the whole thing with an exception — you can then call $run->abort() if you want to mark the run cleanly (otherwise it auto-expires server-side in ~15 minutes).

// Streaming from a large catalog
$run = $lexis->sync->start('products');
foreach (fetchProductsFromDb() as $page) {  // $page = array of up to N docs
    $run->push($page);
}
$run->commit();

Aborting

Call abort() explicitly if your source query fails mid-sync and you don't want to wait 15 minutes for the run to expire:

try {
    $run = $lexis->sync->start('products');
    foreach ($source as $batch) {
        $run->push($batch);
    }
    $run->commit();
} catch (\Throwable $e) {
    $run->abort('source query failed: ' . $e->getMessage());
    throw $e;
}

Custom primary key

By default each document must have an id field. Override with the third argument on the first start() call — it's locked at index creation and ignored thereafter.

// start(indexSlug, indexName, primaryKey, source)
$run = $lexis->sync->start('articles', 'Articles', 'slug');

Search

// search(index, query, limit, offset, filters)
$result = $lexis->search('products', 'adidași nike', 20, 0);

Each hit carries the original document fields plus three synthetic ones — id (the primary-key value), primaryKey, and score:

foreach ($result->hits as $hit) {
    $hit->id;                      // "sku-1"
    $hit->primaryKey;              // "sku-1" (same; exposed for clarity)
    $hit->score;                   // 4.2
    $hit->get('title');            // "Adidași Nike Air"
    $hit->get('price', 0);         // 349 (with default if missing)
    $hit->document;                // full associative array, clean of _ prefixes
}

$result->total;                    // total matches across all pages
$result->tookMs;                   // server-side query time
$result->expandedTerms;            // ["adidas", "nike"] — stemmed/synonym-expanded
$result->suggestion;               // "adidași" when the engine has a did-you-mean

Error handling

All SDK exceptions extend \Lexis\Exception\LexisException. Catch that if you want a single net, or one of the specifics for fine-grained recovery:

Exception HTTP Retryable by SDK
ValidationException 400 no
AuthenticationException 401 no
PlanLimitException 402 no
NotFoundException 404 no
ConflictException 409 no
RateLimitException 429 yes (auto)
ServerException 5xx yes (auto)
NetworkException yes (auto)

Retries are automatic on 429, 5xx, and transport errors — the SDK respects Retry-After on 429 and falls back to exponential backoff (0.5s → 1s → 2s → …) otherwise. You only see the exception if the budget is exhausted.

use Lexis\Exception\AuthenticationException;
use Lexis\Exception\LexisException;
use Lexis\Exception\PlanLimitException;

try {
    $lexis->search('products', 'shoes');
} catch (AuthenticationException $e) {
    // Rotate the key.
} catch (PlanLimitException $e) {
    // Upgrade or free some headroom.
} catch (LexisException $e) {
    // Log $e->getMessage(), $e->getStatusCode(), $e->getResponseBody().
}

Configuration reference

Config is a plain constructor — the positional order is: apiKey, baseUrl, timeout, maxRetries, retryBaseDelay, transport, userAgent.

use Lexis\Client;
use Lexis\Config;

// PHP 7.4-compatible (positional):
$lexis = new Client(new Config(
    'lexis_live_...',            // apiKey
    'https://lexis.florentiu.me', // baseUrl (default — change for self-hosted)
    30.0,                         // timeout (s)
    3,                            // maxRetries on 429/5xx/network; 0 disables
    0.5,                          // retryBaseDelay (s) — doubles each attempt
    null,                         // transport — null = built-in cURL
    'my-app/1.0'                  // userAgent
));

// PHP 8.0+ (named args, same thing):
// $lexis = new Client(new Config(
//     apiKey: 'lexis_live_...',
//     timeout: 60.0,
//     maxRetries: 5,
// ));

Custom HTTP transport

Inject anything implementing \Lexis\Http\Transport — useful for testing (pass a fake) or for corporate proxies that need Guzzle/PSR-18:

$lexis = new Client(new Config(
    'lexis_live_...',
    Config::DEFAULT_BASE_URL,
    30.0,
    3,
    0.5,
    new MyGuzzleAdapter()
));

The default is \Lexis\Http\CurlTransport — pure ext-curl, no extra dependencies.

Rate limits (server-side)

  • /search: 600 requests/minute per API key
  • /sync/*: 30 write requests/minute per API key

Plan quotas (documents, indexes, monthly search calls) surface as PlanLimitException. Check your dashboard for the current numbers.

Testing

composer install
composer test

License

MIT — see LICENSE.