Kafka PHP Clients 2026: php-rdkafka, Laravel & Symfony

Most maintained PHP Kafka libraries are built on php-rdkafka, the PECL extension that binds Confluent's librdkafka C library. Laravel Kafka, Enqueue's Kafka transport, the Symfony Messenger transport koco/messenger-kafka, and php-kafka-lib all call it, so installing the extension is the first decision for Laravel, Symfony, and plain PHP projects alike.

A few options avoid the extension: pure PHP clients such as longlang/phpkafka, RoadRunner's Kafka driver, and HTTP through a REST Proxy. Each trades the extension for other limits; see Can you use Kafka without php-rdkafka?

This guide compares what each library does with your records when a send fails, a handler throws, or a process shuts down. It also covers Avro with Schema Registry and the trade-offs of producing from PHP-FPM.

Quick decision

SituationBest starting pointWhy
Laravel applicationLaravel KafkaLaravel producer and consumer APIs with retries and dead-letter topics. Failed records are committed and skipped by default; configure a dead-letter topic or stopOnFailure() to keep them.
Symfony Messengerkoco/messenger-kafkaA Kafka transport for Messenger. Set enable.auto.offset.store to false so records are not auto-committed before handlers finish. Cooperative rebalancing and KIP-848 are not supported.
Plain PHP service or scriptphp-rdkafka directlyFull librdkafka configuration, producer transactions, and the high-level consumer, with nothing in between. Use php-kafka-lib if you want builders, manual commits by default, and Avro encoders.
Code already built on EnqueueEnqueueA queue-interop transport that works with or without a framework. Set group.id yourself, and replace the default serializer if other services read the topic.
Hyperf or Swoolehyperf/kafka in Hyperf; longlang/phpkafka in other Swoole appsPure PHP and coroutine-friendly, without php-rdkafka. hyperf/kafka is built on longlang/phpkafka, which has not shipped a new version since October 2023.
Cannot install additional PHP extensionsHTTP through a REST Proxy, or a pure PHP clientNeither needs php-rdkafka, but check their TLS, authentication, and compression dependencies. RoadRunner's Kafka driver is another option if you can run its binary. See Can you use Kafka without php-rdkafka?
Avro with Schema RegistryLaravel Kafka's Avro serializer, or flix-tech/avro-serde-php with other librariesLaravel Kafka has Avro built in; other libraries need a separate Avro serializer and Schema Registry client. See Avro and Schema Registry in PHP.

PHP extensions, wrappers, and framework integrations

PHP Kafka libraries sit in three layers:

  • Extension: php-rdkafka exposes librdkafka's producer, high-level consumer, and metadata APIs to PHP. Protocol handling, batching, retries, and consumer group membership happen inside librdkafka.
  • General wrappers: php-kafka-lib and Enqueue add builders, message objects, and serializers on top of php-rdkafka, without tying you to a framework.
  • Framework integrations: Laravel Kafka and the Symfony Messenger transports connect php-rdkafka to framework configuration, commands, and handlers.

The wrappers call php-rdkafka rather than replace it. Which Kafka features work depends on the librdkafka version the extension loads at runtime, and on limits that php-rdkafka and each wrapper add. php --ri rdkafka shows the librdkafka runtime and build versions.

php-rdkafka installs like other PECL extensions: with pecl install rdkafka, or with PIE, the PHP Foundation's newer extension installer, as rdkafka/rdkafka. Building from source requires librdkafka and its development headers.

Docker images usually install the extension in the Dockerfile, and Linux distributions package it for their PHP builds. Shared and managed hosting often allows only preinstalled extensions; see the options without php-rdkafka.

Composer checks the extension version. Laravel Kafka 2.13.0 requires ext-rdkafka ^6.0, and Enqueue 0.10.27 and php-kafka-lib 2.0.2 accept versions 4 to 6, so none of them installs next to php-rdkafka 7.0.0alpha1.

koco/messenger-kafka only suggests ext-rdkafka, so Composer does not check it, but its native Kafka transport still needs the extension at runtime.

PECL also lists simple_kafka_client, a separate extension that binds librdkafka as well, so it does not avoid native code. Its last PECL release is 0.1.4, from 4 Aug 2021.

Kafka PHP clients compared

Capabilities verified against each release's source and documentation: 16 Oct 2026

Areaphp-rdkafka 6.0.5php-kafka-lib 2.0.2Enqueue rdkafka 0.10.27Laravel Kafka 2.13.0koco/messenger-kafka 0.19
RequirementsPHP 7.0+, librdkafkaPHP 8.0+, ext-rdkafka 4–6PHP 8.1+, ext-rdkafka 4–6PHP 8.2+, Laravel 12–13, ext-rdkafka 6PHP 7.4+, Symfony ^5.4 / ^6.4 / ^7.4 / ^8.1
SendingYou call flush(); errors via your callbackYou call flush(); failed deliveries throwFlushed at close() or shutdown; errors via your callbacksend() flushes; errors via your callbacksend() flushes; delivery errors not reported
Default commitsAuto-commit of every record consume() returnsNone; you call commit()On acknowledge(); librdkafka also auto-commits every record consume() returnsEvery handled record, failed ones tooOn ack(); librdkafka also auto-commits every record consume() returns
Failed recordsYour codeYour codereject($message, true) commits, then republishes to the topicRetries, dead-letter topic, or stopMessenger retries produce copies to the topic
Cooperative and KIP-848 groupsYes; use incrementalAssign() in callbacksYes, without the bundled callbackYesYes, without assignment callbacksNo
TransactionsProducer onlyProducer onlyNoProducer onlyNo
Avro with Schema RegistryExternal serializerWith flix-tech/avro-serde-phpCustom serializerBuilt inCustom serializer; koco/avro-regy needs Symfony ^5.4 / ^6.0
Latest release6.0.5 (4 Nov 2024); 7.0.0alpha1 (7 May 2026)v2.0.2 (15 Jun 2026)0.10.27 (4 Apr 2026)v2.13.0 (7 Oct 2026)v0.19 (1 Oct 2026)

All five run on librdkafka, so SASL, TLS, and compression are librdkafka properties in each, and their default key partitioner differs from Java's; see Do PHP Kafka clients partition keys the same way as Java?

Cooperative rebalancing moves only the partitions that change owner instead of pausing every consumer. KIP-848, generally available since Kafka 4.0, moves partition assignment to the brokers; it needs librdkafka 2.12.0+ and group.protocol=consumer.

In the versions compared here, producer transactions cannot include consumer offsets; php-rdkafka 7.0.0alpha1 adds sendOffsetsToTransaction().

php-rdkafka

php-rdkafka is the PECL extension rdkafka. Its API follows librdkafka closely: RdKafka\Conf takes librdkafka configuration properties, RdKafka\Producer produces, and RdKafka\KafkaConsumer is the high-level, group-aware consumer.

Capabilities and limitations

  • Producing: producev() only queues a record; delivery reports reach your setDrMsgCb() callback during poll() or flush(). Call flush() before the script ends, because the destructor purges records still queued or in flight.
  • Default commits: librdkafka commits in two steps. With enable.auto.offset.store, consume() marks each returned record's offset; with enable.auto.commit, marked offsets are committed every 5 seconds, so a record can be committed before its processing finishes.
  • Manual commits: Set enable.auto.commit to false and call commit($message) or commitAsync($message) after each record succeeds.
  • Automatic commits after success: Keep auto-commit, set enable.auto.offset.store to false, and call offsetStore() after each record succeeds. Reuse one newTopic() handle per topic; 6.0.5 does not release them.
  • Commit order: With either approach, do not store or commit past an earlier record in the same partition that has not succeeded.
  • Consumer groups: KIP-848 works with librdkafka 2.12.0+ and Kafka 4.0+ once you set group.protocol=consumer; the default is classic. A rebalance callback must then use incrementalAssign() and incrementalUnassign(), as with cooperative-sticky.
  • Transactions: Producer supports initTransactions(), beginTransaction(), commitTransaction(), and abortTransaction(). 6.0.5 cannot add consumer offsets to a transaction; 7.0.0alpha1 adds sendOffsetsToTransaction().
  • OAUTHBEARER: setOauthbearerTokenRefreshCb() registers a token callback, but in 6.0.5 only Producer and the low-level Consumer have oauthbearerSetToken(). KafkaConsumer gains it in 7.0.0alpha1.
  • Admin operations: Neither 6.0.5 nor 7.0.0alpha1 has an Admin API, so creating topics or changing their configuration needs another tool.
  • 7.0 changes: 7.0.0alpha1 requires PHP 8.1 and librdkafka 1.5.3 or later. KafkaConsumer::consume() now returns null on timeout, so check for null before reading $message->err.
  • Maintenance: The repository moved from arnaud-lb/php-rdkafka to the php-rdkafka organization in April 2026 and had commits through September 2026. The extension is also published for PIE as rdkafka/rdkafka, with 8,010 installs through PIE in 30 days.

When to choose: Choose php-rdkafka directly for plain PHP services and CLI workers that need full control over librdkafka settings, commits, and transactions, or as the base of your own thin wrapper.

php-kafka-lib

php-kafka-lib from Jobcloud wraps php-rdkafka in builders and message objects and adds JSON and Avro encoders. It suits plain PHP applications that want safer consumer defaults than php-rdkafka's.

Capabilities and limitations

  • Producing: produce() queues a record and polls with a zero timeout, but blocks while librdkafka's queue is full. Call flush() before the script ends and check its return code.
  • Delivery errors: The default delivery report callback throws KafkaProducerException from poll() or flush() when a record fails.
  • Producer settings: The builder hardcodes socket.timeout.ms, librdkafka's network request timeout, to 50 ms, so test with realistic latency. It sets your queue.buffering.max.ms to 1 ms, or drops it when pcntl_sigprocmask() exists so librdkafka's default applies.
  • Consumer defaults: Auto-commit and automatic offset storing are off, and auto.offset.reset is earliest. Call commit() with the message after processing. Without withConsumerGroup(), the group ID is default, shared by every consumer that does not set one.
  • Consumer groups: No rebalance callback is registered unless you pass one. The bundled KafkaConsumerRebalanceCallback uses eager assign(), so do not use it with cooperative-sticky or KIP-848. A low-level consumer type covers manual partition assignment.
  • Transactions: beginTransaction() initializes transactions on first use. Transaction errors are mapped to retry, abort, and fatal exception classes.
  • Avro: AvroEncoder and AvroDecoder need flix-tech/avro-serde-php; see Avro and Schema Registry in PHP.
  • Maintenance: v2.0.1 and v2.0.2 shipped in March and June 2026. The 16 Oct 2026 snapshot found no commits in the preceding 90 days.

When to choose: Choose php-kafka-lib for plain PHP services that want manual commits by default and an Avro encoder, without adopting a framework.

Enqueue

Enqueue is a messaging library built on the queue-interop interfaces. Its Kafka transport, enqueue/rdkafka, works on its own, as a Laravel queue connection through enqueue/laravel-queue, and as a Symfony Messenger transport through sroze/messenger-enqueue-transport.

Capabilities and limitations

  • Default group ID: Without a group.id under global, the connection factory generates one with uniqid(). A restarted consumer then joins a new group and does not resume from committed offsets.
  • Message format: The default serializer stores a JSON object with body, properties, and headers as the record value. Consumers outside Enqueue see that envelope, not your payload, so use a custom serializer for shared topics.
  • Producing: send() queues a record and polls without flushing. close() and a shutdown function flush, waiting without limit unless you set shutdown_timeout, and both ignore the result.
  • Delivery errors: To confirm delivery, flush explicitly, check the return code, and register a dr_msg_cb delivery report callback.
  • Commits: acknowledge() commits, asynchronously by default. librdkafka's defaults can commit a returned record before you acknowledge it, so set enable.auto.offset.store to false under global and acknowledge after success.
  • Failures: reject($message, true) acknowledges the record, then sends a copy to the same topic. The two steps are not atomic, so requeueing can lose or duplicate records. The producer rejects delivery delays with a LogicException.
  • Coverage: There is no transaction API. A rebalance callback, delivery report callback, and partitioner can be set through the rebalance_cb, dr_msg_cb, and partitioner options.
  • Maintenance: 0.10.27 was released on 4 Apr 2026 from the enqueue-dev monorepo; see the repository metrics for its backlog.

When to choose: Choose Enqueue if your application already uses it, or if you need one queue-interop API across several brokers. For Kafka-only code, the other libraries map more directly onto Kafka's model.

Kafka in Laravel and Symfony

Laravel Kafka: producers and consumers

Laravel Kafka is the most installed library in this guide, with 184,251 Packagist installs in 30 days. Version 2.13.0, released on 7 Oct 2026, supports PHP 8.2 to 8.5 and Laravel 12 and 13, and requires php-rdkafka 6.x.

Producing

  • Synchronous sends: send() builds a new php-rdkafka producer for each call and flushes it with a 1-second timeout, making up to 10 attempts by default.
  • Delivery errors: A successful flush() only means the local queue is empty; records that failed delivery are removed from it too. Register a withDrMsgCb() callback to catch per-record errors.
  • Asynchronous sends: Kafka::asyncPublish() reuses one producer, and its send() returns before delivery. The producer is flushed when it is destroyed, at the end of the request under PHP-FPM.
  • Headers: Each record gets a laravel-kafka::message-id header with a UUID; the header name comes from the kafka.message_id_key setting.
  • Transactions: transactional() producers support begin, commit, and abort. As with php-rdkafka 6.x, consumer offsets cannot be committed inside the transaction.

Consuming

  • Running: Start consumers with php artisan kafka:consume, or build one in code and call consume().
  • Default commits: librdkafka's auto-commit stays on, so a record can be committed before its handler finishes. The consumer also commits each handled record synchronously, success or failure, so failed records are not consumed again.
  • Batch and manual commits: withCommitBatchSize() runs the library's own commit every N records, while librdkafka's auto-commit keeps running. withManualCommit() turns both off; call $consumer->commit($message) in the handler.
  • Retries: retryFailedMessages() reruns the handler in process; exhausted retries still follow the dead-letter, stop, or skip behavior. Keep each record's total time, including all attempts and backoff, below max.poll.interval.ms (five minutes by default).
  • Dead-letter topic: withDlq() sends a failed record to <topic>-dlq with kafka_throwable_* headers, then commits the original, even with manual commits. It ignores the result of its 12-second flush, so a failed DLQ write can still lose the record.
  • Stopping on failure: Without a dead-letter topic, stopOnFailure() closes the consumer without committing the failed record and throws ConsumerException. Run it under a process manager that restarts it with the same group ID to read the record again.
  • Shutdown: With pcntl loaded, SIGTERM, SIGINT, and SIGQUIT stop the loop after the current record. php-rdkafka closes the consumer, leaving the group, when the consumer object is destroyed.
  • Queueable handlers: A handler that implements ShouldQueue dispatches one Laravel queue job per record. The record counts as handled once the job is dispatched, so failures inside the job are handled by Laravel's queue, not by the Kafka consumer.
  • Consumer groups: No rebalance callback is registered by default. withPartitionAssignmentCallback() and assignPartitionsWithOffsets() register one that uses eager assign(), so do not combine them with cooperative-sticky or KIP-848.

Laravel Kafka produces and consumes records; it does not register a Laravel queue connection, so dispatch() does not send jobs to Kafka. Using Kafka as a queue backend needs a separate queue driver, such as enqueue/laravel-queue.

A smaller alternative, ensi/laravel-phprdkafka, defines php-rdkafka producers and consumers in config/kafka.php; companion packages add higher-level producers and consumers. Version 0.4.6 (23 Jan 2026) supports Laravel 9 to 12, with 2,667 installs in 30 days.

Symfony Messenger Kafka: koco vs Enqueue

Symfony Messenger has no built-in Kafka transport. Symfony's documentation points to Enqueue's transport for brokers such as Kafka; the more direct option is koco/messenger-kafka.

koco/messenger-kafka v0.19 supports Symfony ^5.4 / ^6.4 / ^7.4 / ^8.1 on PHP 7.4 or later (8.4 for Symfony 8.1). Each transport has one DSN (kafka:// or kafka+ssl://), one topic, and kafka_conf and topic_conf maps for librdkafka.

  • Serializer: Messenger's default serializer stores a serialized PHP envelope in the record value. For topics shared with other services, write a serializer that maps the key, headers, and body fields, as in the README's example.
  • TLS: kafka+ssl:// only selects the transport. It does not set security.protocol, so set that in kafka_conf.
  • Sending: Each send() produces one record and flushes, with a 10-second timeout and no extra attempts unless flushRetries is set. A flush that does not complete throws TransportException; delivery reports are not checked.
  • Receiving: ack() commits the record, synchronously unless commitAsync is set. Set enable.auto.offset.store: 'false', as the README recommends; otherwise librdkafka can auto-commit a record before its handler finishes, even if it then fails.
  • Retries: reject() does nothing. Messenger retries a failed message by producing a copy to the same topic, and koco ignores the retry delay.
  • Final failures: The next ack() on the partition commits past the failed record. Configure Messenger's failure_transport to keep messages that exhaust their retries; otherwise later commits skip them. Resending and committing are not atomic.
  • Consumer groups: The transport always registers a rebalance callback that uses eager assign(), so cooperative-sticky and KIP-848 do not work.
  • REST Proxy: v0.19 also includes a send-only transport (kafka+rest://) that posts records to a REST Proxy; receiving through it is not implemented.

Enqueue through sroze/messenger-enqueue-transport 1.0.0 (8 Feb 2026) adapts any Enqueue transport, including enqueue/rdkafka, to Messenger. It requires PHP ^8.1 and Symfony Messenger ^6.4 / ^7.0 / ^8.0.

Enqueue's behavior applies: set group.id and a serializer, and turn off automatic offset storing. Its Kafka producer rejects any delivery delay, but this transport skips a zero delay, so set Messenger's retry delay to 0; the one-second default throws.

Two smaller packages also exist: exoticca/kafka-transport, another Messenger transport with 1,864 installs in 30 days, and simpod/kafka-bundle, which integrates php-rdkafka with Symfony without Messenger, with 3,651.

Avro and Schema Registry in PHP

php-rdkafka sends and receives bytes. Avro with Confluent Schema Registry comes from separate packages, and the libraries in this guide use them differently:

  • flix-tech/avro-serde-php 3.0.0 (25 Dec 2024, PHP ^8.1) serializes and deserializes Avro records in Confluent's wire format, using flix-tech/confluent-schema-registry-api 8.1.0 for schemas. php-kafka-lib's Avro encoder and decoder build on it.
  • Laravel Kafka uses forks of both, mateusjunges/avro-serde-php (v3.1.0, 8 Oct 2026) and mateusjunges/confluent-schema-registry-api. It encodes and decodes only keys and values whose topic has a schema mapping; others pass through unchanged.
  • koco/avro-regy v1.2.0 (October 2022) connects the koco transport to Schema Registry but requires Symfony ^5.4 or ^6.0 and serde ^2.0. On Symfony 7 or 8, call flix-tech/avro-serde-php from a custom serializer instead.
  • jobcloud/php-kafka-schema-registry-client v3.0.0 (4 Feb 2026, PHP ^8.4) registers and looks up schemas and checks compatibility. It does not serialize Avro records, so it cannot replace the FlixTech serde that php-kafka-lib uses.
  • Enqueue has no Avro support; use a custom serializer.

Use a caching registry client, such as FlixTech's CachedRegistry; without one, every record you serialize or deserialize makes an HTTP request to Schema Registry.

None of these libraries ships Protobuf or JSON Schema serializers for Schema Registry. If you exchange those formats, follow Confluent's wire format and test round trips in both directions with your existing producers and consumers.

PHP-FPM producers and long-running consumers

PHP-FPM (FastCGI Process Manager) manages the worker processes that handle PHP web requests. Flush producers before requests end, and run consumers as separate CLI workers.

Producing from web requests

  • A producer per request: Under PHP-FPM, objects do not survive the request, so each producing request creates a new librdkafka producer, with new connections, TLS and SASL handshakes, and a metadata fetch. Laravel Kafka's send() does this on every call.
  • Flush before the request ends: php-rdkafka's producer destructor purges what is left, so without flush(), queued records can be discarded and in-flight delivery can remain unconfirmed. The comparison table shows which libraries flush for you.
  • Flushing blocks the response: Choose a timeout, check the return value, and use delivery reports for per-record failures.
  • Outbox: If a response must not wait for Kafka, write the event to an outbox table in the same database transaction as the business change, and publish it from a long-running worker.
  • Worker-mode runtimes: RoadRunner, Swoole or Hyperf, and FrankenPHP's worker mode can reuse a producer retained for the worker's lifetime; Laravel Octane runs on RoadRunner, Swoole, and FrankenPHP. Keep calling poll(), and flush when the worker stops.

Running consumers

  • Process model: Run consumers as CLI processes under Supervisor, systemd, or Kubernetes, not inside PHP-FPM requests. php artisan kafka:consume and Symfony's messenger:consume are such processes.
  • Commits: Commit only after processing succeeds. Defaults differ: php-kafka-lib waits for your commit(), Laravel Kafka commits failed records, and php-rdkafka, Enqueue, and koco auto-store offsets; see the comparison table.
  • Poll interval: Keep the time between consume() calls, including processing, retries, and commits, below max.poll.interval.ms (five minutes by default). Exceeding it can trigger a rebalance and make commits fail.
  • Signals: Laravel Kafka and messenger:consume register SIGTERM handlers when pcntl is loaded and stop after the current record. With php-rdkafka or php-kafka-lib, register your own handler that sets a stop flag.
  • Shutdown: After the last record, close the consumer so it leaves the group and, with auto-commit on, commits stored offsets. Give the process manager a grace period that covers the current record, commits, and close.
  • Restarts: Long-running PHP processes accumulate memory and stale state. Restart them periodically with Laravel Kafka's withMaxMessages() or withMaxTime(), or Messenger's --limit, --time-limit, and --memory-limit, and let the process manager start a new one.

Can you use Kafka without php-rdkafka?

Two different needs lead here. Some teams want to avoid php-rdkafka itself, for example to skip building librdkafka or to run inside Swoole coroutines. Others deploy to hosts where they cannot install additional extensions.

Pure PHP clients and HTTP avoid php-rdkafka, but check what their authentication, TLS, and compression support depends on. RoadRunner needs its own binary, and FFI needs librdkafka.

  • longlang/phpkafka implements Kafka in PHP for PHP-FPM and Swoole, with a group consumer, SSL, and SASL/PLAIN, but no SCRAM, transactions, or KIP-848. Its latest version points to an October 2023 commit; Hyperf's hyperf/kafka builds on it.
  • RoadRunner's Kafka driver (v6.0.0, 8 Oct 2026) runs franz-go in RoadRunner's Go process. PHP workers receive records as jobs and push new ones through spiral/roadrunner-jobs. It supports SASL/PLAIN, SCRAM, and AWS MSK IAM, and needs the RoadRunner binary.
  • RoadRunner acknowledgements mark a record for periodic offset commits. A negative acknowledgement does not hold the partition back, so acknowledging a later record commits past it; requeued jobs are retried in memory only.
  • HTTP through a REST Proxy needs only an HTTP client: producing is one POST per batch, while consumer instances live on the proxy. See Kafka REST Proxy; koco/messenger-kafka 0.19 also has a send-only REST Proxy transport.
  • lisachenko/kafka-client 4.3.1 is a pure PHP client for PHP 8.4+ implementing the Kafka 4.3.1 protocol, including KIP-848 groups, transactions, and KIP-932 share consumers for queue-style reading. It supports only SASL/PLAIN and had 10 Packagist installs in 30 days.
  • idealo/php-rdkafka-ffi called librdkafka through PHP's FFI extension. It still needed librdkafka installed and FFI enabled, and it was archived on 27 Oct 2025.
  • nmred/kafka-php, once a common pure PHP client, was archived on 10 Jun 2022, and its last tag dates from 2017. It still had 8,251 installs in 30 days.

GitHub repository metrics

The tables below compare the four repositories' GitHub activity, maintenance, and public usage, with Packagist installs as the usage signal. Snapshot: 16 Oct 2026.

php-rdkafka moved from arnaud-lb/php-rdkafka to the php-rdkafka organization in April 2026 and kept its creation date, stars, and history.

Enqueue's figures come from the php-enqueue/enqueue-dev monorepo, of which enqueue/rdkafka is a read-only split, so they cover every Enqueue transport, not only Kafka.

Project overview

php-rdkafkaLaravel Kafkakoco/messenger-kafkaEnqueue
Repository created30 Mar 201516 Aug 202110 Jun 201929 Dec 2016
LicenseMITMITMITMIT

Activity

Metricphp-rdkafkaLaravel Kafkakoco/messenger-kafkaEnqueue
Latest default-branch commit8 Sep 20267 Oct 20261 Oct 202630 Aug 2026
Latest GitHub Release6.0.5 (4 Nov 2024)v2.13.0 (7 Oct 2026)v0.19 (1 Oct 2026)0.10.27 (4 Apr 2026)
GitHub Releases (12 mo)01313
Commits14 (90 d) · 53 (12 mo)13 (90 d) · 38 (12 mo)1 (90 d) · 1 (12 mo)2 (90 d) · 47 (12 mo)
Issue flow (90 d)0 opened · 3 closed3 opened · 7 closed0 opened · 0 closed1 opened · 0 closed
PR flow (90 d)18 opened · 7 merged11 opened · 11 merged2 opened · 1 merged1 opened · 1 merged
Activity assessment🟢 14 commits and 7 merged PRs in the last 90 days.🟢 13 commits and 11 merged PRs in the last 90 days.🟡 1 commit and 1 merged PR in the last 90 days.🟡 2 commits and 1 merged PR in the last 90 days.

Commit counts cover the default branch, which is 7.x for php-rdkafka. Release figures count GitHub Releases only: php-rdkafka 7.0.0alpha1 was published to PECL on 7 May 2026 without a GitHub Release.

Maintenance

Metricphp-rdkafkaLaravel Kafkakoco/messenger-kafkaEnqueue
Active commit authors (12 mo)6713
PR merge distribution (12 mo)1 account · 100% of non-bot merges1 account · 100% of non-bot merges1 account · 100% of non-bot merges1 account · 100% of non-bot merges
Issue backlog3 open · median age 114 d0 open issues8 open · median age 4.1 y36 open · median age 2.9 y
PR backlog12 open · median age 21 d1 open · age 1.1 y16 open · median age 2.2 y25 open · median age 2.7 y
Median PR merge time (90 d)94.5 d (n=7)<1 h (n=11)1.2 d (n=1 — small sample)1.8 d (n=1 — small sample)
Responsiveness assessment🔴 Median PR merge time 94.5 d exceeds 90 d.🟡 For the 11 PRs merged in the last 90 d, the median creation-to-merge time was <1 h, but the one open PR is 1.1 y old.🔴 Only 1 PR merged in 90 d, while 16 open PRs have a median age of 2.2 y.🔴 Only 1 PR merged in 90 d, while 25 open PRs have a median age of 2.7 y.

PR merge distribution counts non-bot mergedBy accounts, not maintainer headcount. Median PR merge time measures creation to merge for PRs merged in the last 90 days; unmerged PRs are excluded. None of the four repositories has a published GitHub security advisory.

Public usage and interest

Metricphp-rdkafkaLaravel Kafkakoco/messenger-kafkaEnqueue
Stars2,179736922,222
Forks27610750427
Packagist installs (30 d)8,010184,25142,72650,976
Packagist installs (all time)46,7344,221,2191,313,2712,786,064
Dependent packages on Packagist124119

Installs include CI builds and transitive dependencies, not unique users. The php-rdkafka column counts only PIE installs of rdkafka/rdkafka; PECL, distribution packages, and Docker images are not counted.

Composer packages declare ext-rdkafka, not rdkafka/rdkafka, so php-rdkafka's dependent count stays near zero. The Enqueue column covers only the enqueue/rdkafka package.

Other libraries in this guide

Metricphp-kafka-liblonglang/phpkafka
Latest default-branch commit15 Jun 202613 Oct 2023
Latest GitHub Releasev2.0.2 (15 Jun 2026)v1.2.2 (15 Mar 2023)
GitHub Releases (12 mo)20
Commits0 (90 d) · 2 (12 mo)0 (90 d) · 0 (12 mo)
PR backlog4 open · median age 4.4 y7 open · median age 2.7 y
Stars60278
Packagist installs (30 d)9,42490,811

longlang/phpkafka is developed in the swoole/phpkafka repository. Its later tags, v1.2.3 to v1.2.5, have no GitHub Releases; v1.2.5 points to the 13 Oct 2023 commit.

Overall repository signals

  • php-rdkafka is active again under its new organization, with 14 commits and 7 merged PRs in the last 90 days; those PRs had a median creation-to-merge time of 94.5 days. Its newest GitHub Release is still 6.0.5; 7.0.0alpha1 has none.
  • Laravel Kafka ships most often, with 13 GitHub releases in the past year, and its 11 merged PRs in the last 90 days had a median merge time under an hour. It also leads Packagist installs, with 184,251 in 30 days.
  • koco/messenger-kafka had one default-branch commit in the past year, on 1 Oct 2026, the day v0.19 shipped. Its 16 open PRs have a median age of 2.2 years, yet it still had 42,726 installs in 30 days.
  • Enqueue had 47 commits across all transports in the past year but only 2 in the last 90 days. Its 25 open PRs have a median age of 2.7 years; enqueue/rdkafka still had 50,976 installs in 30 days.
  • Among the other libraries, longlang/phpkafka has had no commits since 13 Oct 2023, yet its 90,811 installs in 30 days are second only to Laravel Kafka in these tables. php-kafka-lib shipped v2.0.1 and v2.0.2 in 2026 but had no commits in the last 90 days.

Each of the four main repositories had one recorded non-bot PR merger in the past year. These are maintenance and adoption signals, not evidence of runtime quality or feature fit.

Test PHP Kafka clients with Kafma

In PHP, a send() that returns true and a log line from a handler are weak evidence. A flush can succeed after a record failed delivery, and a consumer can commit past a record its handler rejected.

Kafma is a desktop Kafka GUI for checking from outside your PHP process. Its Kafka console shows the records on your test cluster and their raw bytes, and it reads consumer group positions without joining the group or committing offsets.

Inspect what each library writes

Send a few records from your application and open them in Kafma. Compare each key, value, and header with what the code meant to write, and check the exact bytes in Raw.

Several libraries write more than your payload: Laravel Kafka adds a laravel-kafka::message-id header, Enqueue's default serializer wraps the payload in a JSON object, and Messenger's default serializer writes a serialized PHP envelope.

For Avro records, configure Schema Registry in Kafma. Decoded shows the value resolved through its schema ID. With Confluent's schema-ID wire format, Raw starts with 00 followed by the four-byte schema ID.

Browse subjects and versions in Kafma's Schema Registry UI to compare them with what the PHP serializer registered.

Kafma showing an Avro record decoded through schema ID 7, with the Decoded and Raw views

Compare partitions for the same keys

Produce the same keys from your PHP service and a Java service to a test topic, then compare each record's partition in Kafma. Use identical non-empty UTF-8 string keys, and let both producers choose the partition.

Libraries built on php-rdkafka hash keys with CRC32 by default and Java uses murmur2, so the same key can land on different partitions; set partitioner to murmur2_random to align them.

longlang/phpkafka also hashes keys with CRC32, while RoadRunner's driver uses franz-go's default, which matches Java for keyed records.

Send bad records to a running consumer

Keep your consumer running. From Kafma's producer panel, send three records in the consumer's expected format to one partition: a valid record, one that deserializes but makes the handler throw, and another valid record.

Then check what happened to the middle record:

  • Laravel Kafka: With the defaults, it is committed and skipped. With withDlq(), check the configured dead-letter topic (<topic>-dlq by default) for the record and its kafka_throwable_* headers.
  • koco/messenger-kafka: Messenger's retries appear as new copies at the end of the same topic.
  • Enqueue: reject($message, true) also produces a copy at the end of the topic.

Check committed offsets after failures and restarts

Open Watch Group for your consumer group and repeat the test. Check whether the committed offset moves past the failed record, and compare it with your application logs.

Then use Loop to keep records arriving, send the consumer SIGTERM, restart it with the same group ID, and check which records it reads again. Kafma's consumer group UI shows lag across every member.

Kafma watching pending and consumed records around a consumer group's committed offsets

Download Kafma and connect it to the same test cluster as your PHP service.

Frequently asked questions

Is there an official Kafka client for PHP?

No. Apache Kafka's first-party client is Java, and Confluent does not publish a PHP client. php-rdkafka is a community-maintained extension around Confluent's librdkafka, and the other libraries in this guide are community projects.

Why does my PHP producer exit without sending records?

If the script ends before flush(), php-rdkafka's producer destructor purges records that are still queued or in flight. Call flush() with a timeout before the request or script ends, check its return value, and use a delivery report callback for per-record errors.

Does Laravel Kafka require php-rdkafka?

Yes. Laravel Kafka 2.13.0 requires ext-rdkafka ^6.0, so it installs only next to php-rdkafka 6.x, not 7.0.0alpha1. pcntl is optional, but the consumer needs it to stop cleanly on SIGTERM.

Does Symfony Messenger support Kafka?

Not with a built-in transport. Use koco/messenger-kafka, or Enqueue's Kafka transport through sroze/messenger-enqueue-transport, which Symfony's documentation points to. Both need php-rdkafka; see Symfony Messenger Kafka: koco vs Enqueue.

Can Kafka replace Laravel's queue backend?

Only through a separate queue driver such as enqueue/laravel-queue; Laravel Kafka has none. With enqueue/rdkafka, that driver sets a delivery delay, even a zero one, for delayed jobs and every job release, and Enqueue's Kafka producer rejects it by throwing.

To start Laravel jobs from Kafka records instead, use Laravel Kafka's queueable handlers, which dispatch one queue job per record.

Should I use php-rdkafka 7.0 yet?

Not in production. 7.0.0alpha1 is an alpha release, and Laravel Kafka, Enqueue, and php-kafka-lib cap ext-rdkafka at 6.x. When you test it, note that KafkaConsumer::consume() returns null on timeout and that PHP 8.1 and librdkafka 1.5.3 are the new minimums.

It adds OAUTHBEARER tokens for KafkaConsumer and Producer::sendOffsetsToTransaction(); see php-rdkafka.

Do PHP Kafka clients partition keys the same way as Java?

Not by default. php-rdkafka and its wrappers use librdkafka's consistent_random, which hashes non-empty keys with CRC32; Java uses murmur2. Set partitioner to murmur2_random to match Java, given identical key bytes and the same partition count.

longlang/phpkafka also uses CRC32, while RoadRunner's Kafka driver uses franz-go's default, which matches Java for keyed records.

Which PHP Kafka clients support KIP-848?

php-rdkafka supports it when linked against librdkafka 2.12.0 or later, with Kafka 4.0+ brokers and group.protocol=consumer; php --ri rdkafka shows the librdkafka version.

Wrappers keep that support unless they register an eager rebalance callback. Laravel Kafka, Enqueue, and php-kafka-lib register none by default; koco/messenger-kafka always does, so KIP-848 does not work with it.

Without php-rdkafka, longlang/phpkafka supports only the classic protocol, and lisachenko/kafka-client implements KIP-848 in pure PHP.

Does php-rdkafka work with AWS MSK IAM?

Not directly. librdkafka does not generate MSK IAM tokens, so your code must create a signed token in an OAUTHBEARER refresh callback, using a custom signer built on the AWS SDK for PHP's SigV4 support.

In php-rdkafka 6.0.5, KafkaConsumer cannot set that token; 7.0.0alpha1 adds oauthbearerSetToken(), but Laravel Kafka, Enqueue, and php-kafka-lib do not allow 7.0 yet. RoadRunner's Kafka driver supports MSK IAM directly.

SASL/SCRAM is an alternative on MSK Provisioned clusters that enable it; MSK Serverless requires IAM.

Conclusion

Laravel applications should start with Laravel Kafka. It commits and skips failed records by default: use retries for transient failures, and configure a dead-letter topic or stopOnFailure() for records that still fail.

Symfony Messenger applications can use koco/messenger-kafka, or Enqueue's transport if they already use Enqueue.

Plain PHP services can use php-rdkafka directly, or php-kafka-lib for manual commits by default.

Stay on php-rdkafka 6.0.5 until 7.0 is stable and your wrapper allows it. Flush producers before PHP-FPM requests end. Run consumers as CLI workers under a process manager, commit only after processing succeeds, and handle SIGTERM for graceful shutdown.

If you cannot install the extension, use HTTP through a REST Proxy or a pure PHP client, and weigh their maintenance and feature limits.

This guide is maintained by the team behind Kafma.

Ready to try the Kafka IDE?

Available for macOS, Windows, Linux