PHP extension bindings for Apache Iggy, built in Rust with ext-php-rs.
This repository is experimental. The Rust Iggy SDK is async and Tokio-based, but this extension exposes Iggy\Client as a blocking synchronous PHP API. Each call drives the lazy global Tokio runtime and blocks the calling PHP thread until the future resolves; it does not provide fiber-aware or non-blocking I/O.
php-configcargo-phpOn macOS with Homebrew PHP:
export PATH="/opt/homebrew/opt/php/bin:$PATH" export PHP=/opt/homebrew/opt/php/bin/php export PHP_CONFIG=/opt/homebrew/opt/php/bin/php-config
cargo build --release
Generate IDE stubs after changing the exported PHP API:
cargo php stubs --manifest Cargo.toml -o iggy-php.stubs.php
The CI lint job regenerates this file and fails if the checked-in stubs drift from the Rust signatures.
cargo php install --release --yes
If the extension is already enabled, reinstall it with:
cargo php remove --yes cargo php install --release --yes
Verify PHP can load it:
php -r 'var_dump(extension_loaded("iggy-php"));'
docker run --rm --name iggy-php-test \ -p 8090:8090 \ -p 3000:3000 \ apache/iggy:latest
You can also run a local server from the repository root:
cargo run --bin iggy-server --fresh --with-default-root-credentials
The tests assume:
127.0.0.18090iggyiggyOverride them with IGGY_HOST, IGGY_PORT, IGGY_USERNAME, and IGGY_PASSWORD.
<?php $client = new \Iggy\Client('127.0.0.1:8090'); $client->connect(); $client->loginUser('iggy', 'iggy'); $stream = 'php-stream'; $topic = 'php-topic'; $partitionId = 0; $client->createStream($stream); $client->createTopic($stream, $topic, 1, null, null, null, null); $client->sendMessages($stream, $topic, $partitionId, [ new \Iggy\SendMessage('hello from PHP'), ]); $messages = $client->pollMessages( $stream, $topic, $partitionId, \Iggy\PollingStrategy::first(), 10, true, ); foreach ($messages as $message) { echo $message->payload(), PHP_EOL; }
Consumer group callbacks require a finite message limit:
<?php $consumer = $client->consumerGroup( 'php-consumer', $stream, $topic, $partitionId, \Iggy\PollingStrategy::next(), 10, \Iggy\AutoCommit::disabled(), true, true, 1_000_000, null, null, null, false, ); $consumer->consumeMessages( function (\Iggy\ReceiveMessage $message) use ($consumer): void { process($message->payload()); $consumer->storeOffset($message->offset(), $message->partitionId()); }, 100, ); foreach ($consumer->iterMessages() as $message) { process($message->payload()); $consumer->storeOffset($message->offset(), $message->partitionId()); if (shouldStop()) { break; } }
Run the Dockerized integration suite:
docker compose -f docker-compose.test.yml up --build --abort-on-container-exit --exit-code-from php-tests
Run the PHP test suite:
composer install
composer test
Run Rust verification:
cargo test
TLS tests are opt-in because they require a TLS-enabled Iggy server and certificate setup. Set IGGY_TLS_CONNECTION_STRING to enable TLS connection tests. Set IGGY_TLS_PLAINTEXT_ADDRESS to run the negative plaintext-to-TLS test.
TLS connection strings use the Rust SDK connection-string format, for example:
iggy+tcp://iggy:iggy@127.0.0.1:8090?tls=true&tls_domain=localhost&tls_ca_file=/path/to/ca.pem
createStream() and pollMessages().Iggy namespace, for example Iggy\Client and Iggy\SendMessage.0.null as the partition to storeOffset() or deleteOffset() uses the current consumer partition, and is rejected until at least one message has been polled. Pass an explicit partition id before the first poll.consumeMessages() requires an explicit finite limit. It does not run forever by default.iterMessages() returns a PHP Iterator and can be used with foreach. Break out of the loop when the caller's processing limit or shutdown signal is reached.AutoCommit::when() may queue an offset commit before the PHP callback runs. If callback success must control commits, use AutoCommit::disabled() and call storeOffset() after the callback work succeeds.Iggy\PollingStrategy::timestamp() and Iggy\PollingStrategy::timestampMicros() expect microseconds since the Unix epoch. Use Iggy\PollingStrategy::timestampSeconds() for PHP time() values.Iggy\SendMessage::payload and Iggy\ReceiveMessage::payload() copy the payload bytes into a PHP string on each read. Cache large payloads in PHP if they will be read repeatedly.Iggy\Client is synchronous and blocks the current PHP thread.pcntl_fork() after the first Iggy SDK call; the child process inherits file descriptors but not Tokio worker threads. Runtime initialization failure is unrecoverable and aborts extension use.