bavix/clickhouse-php-client
PHP 7.1+ ClickHouse HTTP client built on Guzzle. Supports single server or clusters, server selection by name/tags, and running queries per cluster. Provides async SELECT and INSERT from local files for efficient ingestion and querying.
Installation
composer require bavix/clickhouse-php-client
Ensure your composer.json includes "minimum-stability": "dev" if using unreleased versions.
Basic Connection
use Bavix\ClickHouse\Client;
$client = new Client('http://localhost:8123');
For secure connections (HTTPS), pass the full URL:
$client = new Client('https://clickhouse.example.com');
First Query
$result = $client->query('SELECT 1 + 1 AS result');
$row = $result->fetchAssoc();
// Output: ['result' => 2]
Key Files
src/Client.php: Core client logic.src/Query.php: Query builder and execution.src/Exception/ClickHouseException.php: Error handling.Simple Queries
$result = $client->query('SELECT * FROM users WHERE id = {id}', ['id' => 1]);
Use parameter binding to avoid SQL injection.
Batch Queries
$client->batch([
'SELECT 1',
'SELECT 2',
])->execute();
Async Queries
$promise = $client->queryAsync('SELECT sleep(1)');
$result = $promise->wait();
Create Table
$client->query('
CREATE TABLE IF NOT EXISTS users (
id UInt32,
name String,
created_at DateTime
) ENGINE = MergeTree()
');
Describe Table
$result = $client->query('DESCRIBE TABLE users');
$columns = $result->fetchAllAssoc();
Single Row
$client->query('
INSERT INTO users (id, name, created_at)
VALUES ({id}, {name}, now())
', [
'id' => 1,
'name' => 'John Doe',
]);
Batch Insert
$client->query('
INSERT INTO users (id, name)
VALUES
{1: 'John'},
{2: 'Jane'}
', [
1 => ['name' => 'John'],
2 => ['name' => 'Jane'],
]);
Service Provider
// config/clickhouse.php
return [
'url' => env('CLICKHOUSE_URL', 'http://localhost:8123'),
'timeout' => 5.0,
];
// app/Providers/ClickHouseServiceProvider.php
public function register()
{
$this->app->singleton(Client::class, function ($app) {
return new Client(config('clickhouse.url'));
});
}
Eloquent-like Query Builder (Custom)
class ClickHouseQueryBuilder
{
protected $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function select($columns = ['*'])
{
$this->query = 'SELECT ' . implode(', ', $columns);
return $this;
}
public function from($table)
{
$this->query .= " FROM {$table}";
return $this;
}
public function where($column, $operator, $value)
{
$this->query .= " WHERE {$column} {$operator} '{$value}'";
return $this;
}
public function get()
{
return $this->client->query($this->query)->fetchAllAssoc();
}
}
Usage:
$query = new ClickHouseQueryBuilder($client);
$users = $query->select(['id', 'name'])->from('users')->where('id', '=', 1)->get();
$client->on('query', function ($query, $params) {
logger()->info("Executing query: {$query}", $params);
});
Connection Timeouts
$client = new Client('http://localhost:8123', [
'timeout' => 10.0, // 10 seconds
]);
Parameter Binding Quirks
// Works:
$client->query('INSERT INTO logs (data) VALUES ({data})', ['data' => ['key' => 'value']]);
// Fails (serializes to JSON string):
$client->query('INSERT INTO logs (data) VALUES ({data})', ['data' => ['key' => 'value']]); // May not work as expected
JSON type in ClickHouse and pass strings:
$client->query('INSERT INTO logs (data) VALUES ({data:raw})', [
'data' => json_encode(['key' => 'value']),
]);
Large Result Sets
fetchAll() for large datasets. Use streaming:
$result = $client->query('SELECT * FROM huge_table');
while ($row = $result->fetchAssoc()) {
process($row);
}
HTTPS Self-Signed Certificates
$client = new Client('https://localhost', [
'verify_peer' => false,
]);
Query Caching
$cacheKey = md5($query . json_encode($params));
$cached = cache()->get($cacheKey);
if (!$cached) {
$result = $client->query($query, $params);
cache()->put($cacheKey, $result, now()->addMinutes(5));
}
Enable Debug Mode
$client = new Client('http://localhost:8123', [
'debug' => true,
]);
Logs HTTP requests/responses to storage/logs/clickhouse.log.
Raw HTTP Requests
$client->getHttpClient()->setDebug(true);
Common Errors
ClickHouseException: Catch and log:
try {
$result = $client->query('INVALID_QUERY');
} catch (ClickHouseException $e) {
logger()->error("ClickHouse Error: " . $e->getMessage());
}
8123 by default) is open.Custom HTTP Client
use Psr\Http\Client\ClientInterface;
use Bavix\ClickHouse\Http\GuzzleHttpClient;
$httpClient = new GuzzleHttpClient(['timeout' => 5]);
$client = new Client('http://localhost:8123', [], $httpClient);
Middleware
$client->getHttpClient()->addMiddleware(function ($request, $handler) {
$request = $request->withHeader('X-Custom-Header', 'value');
return $handler->handle($request);
});
Query Logging
Query class to log slow queries:
$client->on('query.start', function ($query, $params) {
$start = microtime(true);
});
$client->on('query.end', function ($query, $params, $result) use ($start) {
$duration = microtime(true) - $start;
if ($duration > 1.0) {
logger()->warning("Slow query ({$duration}s): {$query}");
}
});
Custom Data Types
DateTime, UUID) by extending the Result class:
class CustomResult extends \Bavix\ClickHouse\Result
{
public function fetchAssoc()
{
$row = parent::fetchAssoc();
if (isset($row['created_at'])) {
$row['created_at'] = (
How can I help you explore Laravel packages today?