Native PHP extension for ClickHouse, built on the official ClickHouse/clickhouse-cpp client. Connects over the binary TCP protocol with LZ4 / ZSTD compression and optional TLS.
Soft fork of SeasX/SeasClick, which appears unmaintained. The last accepted external PR there is from 2020, and several follow-up PRs have been pending for years. This fork:
- Renames the extension to
php_clickhouse(moduleclickhouse, classesClickHouse/ClickHouseException) - Upgrades the vendored client from artpaul-fork v1.x to the official ClickHouse/clickhouse-cpp v2.6.1
- Adds Date32 / Time / Time64 / DateTime64 / Int128 / UInt128 / Decimal128 / LowCardinality / Map column types, multi-endpoint failover, ZSTD compression, query_id propagation, and TLS
- Ships an updated test suite, CI, PIE-based packaging, and benchmarks
The original SeasClick and SeasClickException class names continue
to work as deprecated aliases for the 0.5.x cycle.
via PIE (the PHP Foundation's PECL successor):
pie install iliaal/php_clickhousewith TLS support:
pie install iliaal/php_clickhouse --enable-clickhouse-opensslBuilding from source:
git clone https://github.com/iliaal/php_clickhouse.git
cd php_clickhouse
phpize
./configure # default build
./configure --enable-clickhouse-openssl # with TLS, requires OpenSSL development headers
# (libssl-dev on Debian/Ubuntu, openssl-devel
# on RHEL/Fedora, openssl-dev on Alpine)
make && sudo make installAdd extension=clickhouse.so to your php.ini. The build needs a
C++17-capable compiler (GCC 8+, Clang 7+); LZ4, ZSTD, abseil-int128,
and CityHash are vendored under lib/clickhouse-cpp/contrib/.
Only NTS PHP builds are supported. The extension stores Client state in process-global maps; under ZTS the same handle space is shared across worker threads, so the wrapper refuses to load.
For development and integration tests, the simplest path is the official ClickHouse server image:
docker run -d --name clickhouse-test \
--ulimit nofile=262144:262144 \
-p 9000:9000 -p 8123:8123 -p 9440:9440 \
-e CLICKHOUSE_USER=test \
-e CLICKHOUSE_PASSWORD=test \
clickhouse/clickhouse-server:latestStop and clean up: docker rm -f clickhouse-test.
<?php
$ch = new ClickHouse([
"host" => "127.0.0.1",
"port" => 9000,
"database" => "test",
"user" => "default",
"passwd" => "",
"compression" => "lz4", // or "zstd" / true / false
]);
$ch->execute("CREATE TABLE IF NOT EXISTS events (
id UInt32, ts DateTime64(3), tag LowCardinality(String)
) ENGINE = Memory");
$ch->insert("events", ["id", "ts", "tag"], [
[1, time(), "alpha"],
[2, time(), "beta"],
]);
foreach ($ch->select("SELECT id, ts, tag FROM events ORDER BY id",
[], ClickHouse::DATE_AS_STRINGS) as $row) {
print_r($row);
}Array(T)(single-level)Date,Date32,DateTime,DateTime64(N[, timezone])Time,Time64(N)(server side requires ClickHouse 25.x or later)Decimal,Decimal32,Decimal64,Decimal128(P, S)(read/write as scaled-integer strings)Enum8,Enum16FixedString(N)Float32,Float64Int8…Int64,UInt8…UInt64Int128,UInt128(round-trip as decimal strings; PHP integers are 64-bit)IPv4,IPv6LowCardinality(String),LowCardinality(FixedString(N))Map(K, V)for(String, String),(String, Int64),(String, UInt64),(String, Float64),(Int64, String). Other K/V pairs are read-only via the lib's generic factory.Nullable(T)StringTuple(read-only)UUID
All keys go in the array passed to new ClickHouse([...]).
| Key | Type | Default | Description |
|---|---|---|---|
host |
string | 127.0.0.1 |
Server host |
port |
int | 9000 |
Native TCP port (or 9440 for TLS) |
database |
string | default |
Default database |
user |
string | default |
Username |
passwd |
string | (empty) | Password |
endpoints |
array | (none) | List of [{host, port}, ...] for round-robin failover. Tried in order on connect failure. |
| Key | Type | Default | Description |
|---|---|---|---|
compression |
bool / string | false |
false/"none" = uncompressed; true/"lz4" = LZ4 (fast); "zstd" = ZSTD (denser) |
max_compression_chunk_size |
int | 65535 |
Block size used by the compressor |
| Key | Type | Default | Description |
|---|---|---|---|
connect_timeout |
int (sec) | 5 |
TCP connect deadline |
receive_timeout |
int (sec) | 0 |
Read deadline (0 = no timeout) |
send_timeout |
int (sec) | 0 |
Write deadline |
retry_count |
int | 1 |
Send retries on transient failure |
retry_timeout |
int (sec) | 5 |
Sleep between retries |
tcp_nodelay |
bool | true |
TCP_NODELAY |
tcp_keepalive |
bool | false |
TCP keepalive |
tcp_keepalive_idle |
int (sec) | 60 |
Idle time before first keepalive probe |
tcp_keepalive_intvl |
int (sec) | 5 |
Interval between probes |
tcp_keepalive_cnt |
int | 3 |
Failed probes before declaring dead |
| Key | Type | Default | Description |
|---|---|---|---|
ssl |
bool | false |
Enable TLS |
ssl_min_protocol_version |
string | tls1.2 |
Minimum protocol; one of tls1.0, tls1.1, tls1.2, tls1.3 |
ssl_skip_verify |
bool | false |
Skip cert validation; dev only |
ssl_use_default_ca |
bool | true |
Trust the system CA bundle |
ssl_ca_files |
string | array | (none) | PEM CA file path(s) |
ssl_ca_directory |
string | (none) | OpenSSL hashed-cert directory |
Building without --enable-clickhouse-openssl and passing
ssl => true raises ClickHouseException at construct time.
$ch = new ClickHouse(array $config);
// Schema / DDL
$ch->execute(string $sql, array $params = [], string $query_id = "");
// Read
$rows = $ch->select(string $sql,
array $params = [],
int $fetch_mode = 0,
string $query_id = "");
// Bulk insert (entire dataset in one call)
$ch->insert(string $table, array $columns, array $values,
string $query_id = "");
// Streaming insert (open block, append, close)
$ch->writeStart(string $table, array $columns, string $query_id = "");
$ch->write(array $values);
$ch->write(array $more_values);
$ch->writeEnd();
$ch->ping(); // returns true on success, throws on failurefetch_mode is a bitmask of ClickHouse::FETCH_ONE,
ClickHouse::FETCH_KEY_PAIR, ClickHouse::FETCH_COLUMN, and
ClickHouse::DATE_AS_STRINGS.
PHP 8.4.22 / ClickHouse 26.3.9.8 / localhost loopback / Memory table
(no disk).
Compared against smi2/phpClickHouse, the most popular pure-PHP HTTP client.
Each cell is total wall-clock seconds for selectCount queries plus
a single bulk insert of dataCount rows.
| dataCount × selectCount × limit | phpClickHouse (HTTP) | php_clickhouse (uncompressed) | php_clickhouse (LZ4) | php_clickhouse (ZSTD) |
|---|---|---|---|---|
| 10000 × 1 × 5000 | 0.112 | 0.085 | 0.074 | 0.023 |
| 10000 × 1 × 5000 | 0.104 | 0.030 | 0.024 | 0.081 |
| 10000 × 100 × 5000 | 0.298 | 0.263 | 0.209 | 0.218 |
| 10000 × 100 × 10000 | 0.303 | 0.210 | 0.265 | 0.215 |
| 1000 × 200 × 500 | 0.558 | 0.416 | 0.415 | 0.413 |
| 1000 × 200 × 1000 | 0.611 | 0.408 | 0.410 | 0.395 |
| 1000 × 500 × 500 | 1.428 | 1.063 | 0.976 | 0.982 |
| 1000 × 500 × 1000 | 1.383 | 0.959 | 1.025 | 1.030 |
| 1000 × 800 × 500 | 2.477 | 1.533 | 1.569 | 1.543 |
| 1000 × 800 × 1000 | 2.498 | 1.588 | 1.563 | 1.519 |
At high select counts the native binary protocol runs 30-40% faster
than the HTTP client. On small bursts (dataCount=10000, selectCount=1), php_clickhouse with ZSTD or LZ4 is fastest. To
reproduce, see bench/.
The PHP-side wrapper is licensed under PHP-3.01.
The vendored client library at lib/clickhouse-cpp/ is
ClickHouse/clickhouse-cpp,
licensed under the Apache License 2.0.
The vendored compression libraries (lib/clickhouse-cpp/contrib/lz4/,
contrib/zstd/, contrib/cityhash/) carry BSD-style licenses; abseil
int128 (contrib/absl/) is Apache 2.0. See each subdirectory for the
exact text.
php_clickhouse started as a fork of
SeasX/SeasClick by SeasX Group
(ahhhh.wang@gmail.com). The original PR-4 work to add fetch modes
landed in 2019 and the upstream maintainer hasn't accepted external
PRs since. Independent re-vendoring, port to clickhouse-cpp v2.6.1,
new types, TLS, and packaging are by Ilia Alshanetsky ilia@ilia.ws.
See CONTRIBUTING.md. Security issues: SECURITY.md.