Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpclickhouse Laravel Package

smi2/phpclickhouse

PHP client for ClickHouse with sync and async queries, native parameter binding, bulk inserts, generators, gzip compression, cluster support, streaming, and progress callbacks. Requires PHP 8.1+, ext-curl, ext-json.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Check the runtime requirement first. 1.26.610+ requires PHP 8.1 (composer.json now pins php: ^8.1, and the CI matrix drops 8.0). If you are still on PHP 8.0, pin 1.26.4 (the last 8.0-compatible release) — otherwise composer update will fail or silently hold you back on the typed >= 1.24.406 line.

  2. Installation:

    composer require smi2/phpclickhouse
    
  3. Bind the client once (in a service provider, not in controllers):

    // app/Providers/ClickHouseServiceProvider.php
    public function register(): void
    {
        $this->app->singleton(\ClickHouseDB\Client::class, fn () => new \ClickHouseDB\Client([
            'host'     => config('clickhouse.host'),
            'port'     => config('clickhouse.port'),
            'username' => config('clickhouse.username'),
            'password' => config('clickhouse.password'),
            'database' => config('clickhouse.database'),
        ]));
    }
    
  4. First Query — note that with the new type wrappers you can pass typed values instead of raw string interpolation:

    $users = app(\ClickHouseDB\Client::class)->select(
        'SELECT * FROM users WHERE id = :id',
        ['id' => new \ClickHouseDB\Type\UInt64(1)]
    );
    
    return $users->fetchOne();
    

Where to Look First

  • README.md version-compatibility table (recently corrected — trust it over older blog posts).
  • doc/types.md — the authoritative reference for the new StringableType / ScalarNumericType wrappers and the IEEE 754 notes.
  • tests/Type/Ieee754MantissaTest.php — 31 executable cases that document exactly how floats/decimals round, overflow, and serialize. It is the fastest way to learn the numeric edge cases.
  • The published Pages site for the same content rendered.

First Use Case: Safe Ingestion of Untrusted Values

The most valuable day-one upgrade is replacing hand-built SQL strings with typed values. Type objects (the legacy base class) are interpolated into SQL unescaped; the new StringableType implementations are escaped by ValueFormatter, by bindings, and by native parameters. That makes the wrappers the default choice for anything originating from a request:

use ClickHouseDB\Type\{StringType, UInt32, DateTime, Enum8};

$client->insert('events', [[
    new StringType($request->input('name')),   // escaped
    new UInt32($request->integer('user_id')),  // fromString() validates ^-?\d+$
    new DateTime($request->date('at')),        // optional non-mutating TZ conversion
    new Enum8('click'),
]], ['name', 'user_id', 'at', 'kind']);

Implementation Patterns

1. Typed Repository (recommended baseline)

Inject the singleton and expose typed methods. Keep the type construction at the boundary so callers cannot accidentally pass a raw string where escaping matters:

class EventRepository
{
    public function __construct(private \ClickHouseDB\Client $client) {}

    public function byUser(int $userId): iterable
    {
        return $this->client->selectGenerator(
            'SELECT * FROM events WHERE user_id = :id',
            ['id' => new \ClickHouseDB\Type\UInt32($userId)]
        );
    }

    public function bulkInsert(array $rows): void
    {
        $this->client->insert('events', $rows, ['name', 'user_id', 'at', 'kind']);
    }
}

2. Numeric Types for Anything Numeric

The new ScalarNumericType family covers Int8–Int256, UInt8–UInt256, Float32/Float64, and Decimal32–Decimal256. Bool intentionally maps to the pre-existing Boolean class rather than introducing a second boolean type.

use ClickHouseDB\Type\{Int64, Float64, Decimal64};

$client->insert('metrics', [[
    new Int64($delta),
    new Float64($ratio),
    new Decimal64('123.4500', 4),   // exact — do not use Float64 for money
]], ['delta', 'ratio', 'amount']);

fromString() on these validates input (^-?\d+$ for integers, is_numeric() for floats/decimals), which is what closes the injection vector — use it when hydrating from CSV/queue payloads.

3. String, Date and Enum Wrappers

  • StringType (named that way because String is reserved in PHP).
  • FixedString(N) — enforces strict byte length, not character length.
  • Date, DateTime — DateTime supports an optional timezone conversion that is non-mutating (returns a new instance).
  • Enum8, Enum16. All implement StringableType, so they are escaped everywhere a value is rendered into SQL.

4. Per-Query Settings Instead of Global Mutations

Recent CH 26 tests verify per-query max_execution_time is visible to getSetting() without touching global settings. Prefer this over mutating the client, which leaks across a long-lived queue worker:

$client->select('SELECT * FROM big', [], [
    'max_execution_time' => 60,
    'stacktrace'         => 1,
]);

5. Structured Exception Handling

DatabaseException now exposes getServerVersion() and getServerStackTrace(). The server stack trace is deliberately kept out of getMessage() — you must call the getter, and the trace is only populated when the query is sent with ['stacktrace' => 1]:

try {
    $client->select('SELECT * FROM missing', [], ['stacktrace' => 1]);
} catch (\ClickHouseDB\Exception\DatabaseException $e) {
    logger()->error('ClickHouse error', [
        'message'     => $e->getMessage(),
        'server'      => $e->getServerVersion(),
        'serverTrace' => $e->getServerStackTrace(),
    ]);
}

Do not blanket-enable stacktrace on every query in production — it adds server-side work; enable it per call site or via a config flag you can toggle while debugging.

6. Queue Jobs and Streaming

Bind the client as a singleton, resolve it in handle() (not the constructor) so serialization stays cheap, and use selectGenerator() for large result sets. Keep selectAsync() for fan-out reads where you genuinely iterate the statement; a job that resolves the client in its constructor risks serializing connection state.

7. Model Events / Outbox

Mirror writes into ClickHouse from Eloquent events, but enqueue rather than insert inline for anything user-facing. Constructing typed values in the job — not in the model — keeps HTTP requests fast and puts validation next to the retry logic.

Gotchas and Tips

Breaking / Version Traps

  1. PHP 8.1 minimum. Bumping php: ^8.1 also means your CI images and any platform.php config in composer.json must move. Pin 1.26.4 if you cannot upgrade.
  2. Only StringableType values are escaped. Legacy Type objects are still interpolated raw. Audit every place you build a Type from request data — that class has no validation and no escaping.
  3. String is a reserved word in PHP. Import ClickHouseDB\Type\StringType; do not alias it to String.

Numeric Precision

  1. 0.1 + 0.2 != 0.3 for Float32/Float64. This is asserted in the test suite and documented in the README. Use Decimal* for money and any value where exactness matters.
  2. Mantissa limits collapse silently: Float32 exact integers up to 2^24, Float64 up to 2^53, BFloat16 up to 2^8 (CH 24.11+ only — do not use it on older servers).
  3. Decimal precision trap on read. json_decode() loses precision beyond ~16 significant digits, and Decimal arrives as an unquoted JSON number on every supported server. If you need exact reads of large decimals, fetch them via toString() in the SELECT rather than relying on the decoded PHP value.
  4. Scale-0 Decimal truncates toward zero, not with rounding.
  5. inf / nan become null in JSON output. Handle null defensively in aggregations.

FixedString and Enum

  1. FixedString(N) validates byte length — a 10-character UTF-8 string of multibyte characters will fail or be rejected. Pad/truncate intentionally and assert byte length before constructing.
  2. Enum8/Enum16 reject values outside the declared set; the resulting error is a server-side exception, so validate against your own enum list first if you want a clean 422 rather than a 500.

Debugging

  1. Use the enriched exception getters. getServerVersion() tells you immediately whether you are hitting a schema/feature mismatch (e.g. BFloat16 on a pre-24.11 server) that the raw message does not reveal.
  2. Server stack trace requires opt-in. If getServerStackTrace() returns empty, you forgot ['stacktrace' => 1] on that specific query.
  3. Query logging / progress. setDebug(true) plus getLastQuery() for ad-hoc profiling; use the progress callback for long-running reads rather than guessing from PHP memory usage.

Performance

  1. selectGenerator() for anything you cannot bound in memory; rows() for small, known-sized result sets.
  2. Batch inserts (thousands of rows per request) instead of row-at-a-time — CH punishes small parts.
  3. Enable compression for large payloads; set connect and execution timeouts explicitly, and add retry with backoff on the queue side.
  4. Avoid LIMIT/OFFSET loops on large tables — the server still scans. Prefer keyset pagination or selectGenerator().

Compatibility

  1. The CI matrix now validates against ClickHouse 21.9, 26.3 and 26.9 (26.9 runs PHP 8.1–8.4). If your production cluster is outside that range, run the relevant suite yourself before upgrading — feature support (JSON number handling for Decimal, BFloat16) varies by server version.
  2. Scrutinizer was removed in favour of GitHub Actions (tests, PHPStan, PHPCS). If you fork and extend, replicate those three locally rather than relying on an external quality gate.

Extension Points

  1. Keep custom formatters and degenerators small and unit-tested against tests/Type/Ieee754MantissaTest.php expectations — that suite encodes the rounding and overflow contract your custom types must not violate. If you add a type, implement StringableType (escaped) or ScalarNumericType (validated) rather than extending the legacy Type class, and you inherit the safety guarantees automatically.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo