Compact PHP client for hlquery. No framework required, no extra runtime dependencies beyond curl and json.
Included in the current client:
- Collections
- Documents
- Search
- SQL
- Keys
- Aliases
- Overrides
- Synonyms
- Stopwords
- System helpers, including
etc()
Requirements:
- PHP
>= 7.0 ext-curlext-json
Local usage:
require_once __DIR__ . '/lib/autoload.php';
use Hlquery\Client;
$client = new Client(getenv('HLQ_BASE_URL') ?: (getenv('HLQUERY_BASE_URL') ?: 'http://localhost:9200'));Composer:
composer require hlquery/php-clientrequire_once __DIR__ . '/vendor/autoload.php';
use Hlquery\Client;
$client = new Client('http://localhost:9200');Auth:
$client = new Client('http://localhost:9200', [
'token' => 'your_token_here',
'auth_method' => 'bearer', // or 'api-key'
]);
// or later
$client->setAuthToken('your_token_here', 'bearer');require_once __DIR__ . '/lib/autoload.php';
use Hlquery\Client;
$client = new Client('http://localhost:9200');
$health = $client->health();
if ($health->isSuccess()) {
$body = $health->getBody();
echo "status: " . ($body['status'] ?? 'ok') . PHP_EOL;
}
$collections = $client->listCollections(0, 10);
print_r($collections->getBody());Captured from a local http://localhost:9200 server.
$client->health()->getBody():
{
"server": "hlquery",
"status": "ok",
"version": "1.0"
}$client->search('readme_demo', ['q' => 'search', 'query_by' => 'title,content', 'limit' => 10])->getBody():
{
"hits": [
{
"document": {
"id": "doc-2",
"title": "Search Engineering Notes"
},
"highlights": {
"title": "<em>Search</em> Engineering Notes"
}
}
],
"found": 1
}Use executeRequest() to call custom module routes directly:
$moduleResponse = $client->executeRequest('GET', '/modules/<name>/<route>', null, [
'q' => 'example query',
]);
print_r($moduleResponse->getBody());This client uses a service-based API similar to Typesense. The main difference is that hlquery expects the collection name as the first argument, instead of inside the schema array.
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Hlquery\Client;
$client = new Client('http://localhost:9200');
$collections = $client->collections();
$schema = [
'fields' => [
['name' => 'id', 'type' => 'string'],
['name' => 'title', 'type' => 'string'],
['name' => 'author', 'type' => 'string'],
['name' => 'year', 'type' => 'int32'],
],
];
$response = $collections->create('books', $schema);
print_r($response->getBody());$documents = $client->documents();
$documents->add('books', [
'id' => '1',
'title' => 'The Hobbit',
'author' => 'J.R.R. Tolkien',
'year' => 1937,
]);
$documents->import('books', [
[
'id' => '2',
'title' => 'Dune',
'author' => 'Frank Herbert',
'year' => 1965,
],
[
'id' => '3',
'title' => 'Neuromancer',
'author' => 'William Gibson',
'year' => 1984,
],
]);If q is set and query_by is omitted, the client tries to use the collection's searchable_fields.
$results = $client->search('books', [
'q' => 'tolkien',
'limit' => 10,
]);Use the SQL service object for a nested API style:
$sql = $client->sql();Basic SQL example:
$sql = $client->sql();
$results = $sql->query('products', 'SELECT id, title FROM products ORDER BY title ASC LIMIT 3;');
if ($results->isSuccess()) {
$body = $results->getBody();
print_r($body['rows'] ?? []);
}Collection-bound SQL SELECT:
$sql = $client->sql();
$results = $sql->query(
'products',
'SELECT id, title, price FROM products WHERE price >= 100 ORDER BY price DESC LIMIT 5;'
);Top-level SQL execution:
$sql = $client->sql();
$rows = $sql->raw('SHOW COLLECTIONS;');
$insert = $sql->execute(
"INSERT INTO products (id, title, price) VALUES ('sku-9', 'Camp Stove', 89);"
);For vector search, the important part is usually not the raw embedding array in the example, but the search knobs around it.
field_namemust match the vector field stored in your collection.topkcontrols how many nearest matches you ask for back.thresholdcan cut off weak matches early.nprobeis the main recall/speed tradeoff on IVF-style indexes.
Briefly: a higher nprobe checks more partitions, which usually improves recall but costs more CPU and latency. Start small, then raise it only if you are missing obvious neighbors. If you are tuning quality, nprobe is one of the first parameters worth testing.
$doc = $client->getDocument('books', '1');
print_r($doc->getBody());$client->documents()->update('books', '1', [
'year' => 1938,
]);
$client->documents()->delete('products', 'sku-3');$client->synonyms()->create('products', 'shoe_terms', [
'root' => 'shoe',
'synonyms' => ['sneaker', 'trainer'],
]);
$synonyms = $client->synonyms()->list('products');
print_r($synonyms->getBody());Global synonyms are also supported:
$client->synonyms()->createGlobal('global_shoe_terms', [
'root' => 'shoe',
'synonyms' => ['sneaker', 'trainer'],
]);