If Apache Iggy is useful to you, give it a star on GitHubStar apache/iggy
Apache Iggy
SDKPHP

The experimental PHP SDK, a native extension wrapping the Rust SDK, and how to build it from source.

The Iggy PHP SDK is a native PHP extension built in Rust with ext-php-rs. It wraps the Rust SDK and exposes a synchronous Iggy\Client: each call drives the extension's internal Tokio runtime and blocks the calling PHP thread until the operation completes. The source code is available on GitHub.

The extension is experimental. It's defined as the apache/iggy-php composer package (php-ext type). To use it today, build it from the repository source.

Requirements

  • Rust and Cargo
  • PHP 8.3 or newer with php-config (non-thread-safe builds only, no ZTS)
  • cargo-php

Build and install

cd foreign/php
cargo build --release
cargo php install --release --yes

# Verify PHP can load it
php -r 'var_dump(extension_loaded("iggy-php"));'

Alternatively, skip the install and point PHP at the built library per invocation:

php -d extension=target/release/libiggy_php.so my-script.php

On macOS the library is libiggy_php.dylib.

Quick start

The sample below targets Iggy 0.9.0 on 127.0.0.1:8090. Start the server:

# Server 0.9.0
docker run --rm \
  --cap-add=SYS_NICE --security-opt seccomp=unconfined --ulimit memlock=-1:-1 \
  -p 8090:8090 \
  -e IGGY_TCP_ADDRESS=0.0.0.0:8090 \
  -e IGGY_NODE_ADVERTISED_ADDRESS=localhost \
  -e IGGY_ROOT_USERNAME=iggy -e IGGY_ROOT_PASSWORD=iggy \
  apache/iggy:0.9.0

# Or from the repository root
cargo run --bin iggy-server -- --fresh --with-default-root-credentials

The environment variables make the server reachable through the published port (it binds to 127.0.0.1 inside the container by default) and bootstrap the iggy/iggy root credentials for a new data directory. Existing stored credentials are not replaced. For the source command, ensure IGGY_ROOT_USERNAME and IGGY_ROOT_PASSWORD are unset or match the sample; environment values take precedence over the flag. --fresh removes the local replica state, so use it only for disposable development data.

For binary transports, credentials in the connection string take effect on connect(). HTTP requires loginUser() explicitly. Alternatively, construct the client with a plain address (new \Iggy\Client('127.0.0.1:8090')) and call loginUser('iggy', 'iggy') explicitly.

<?php

use Iggy\Client;
use Iggy\PollingStrategy;
use Iggy\SendMessage;

$client = Client::fromConnectionString('iggy+tcp://iggy:iggy@127.0.0.1:8090');
$client->connect();

$stream = 'php-stream';
$topic = 'php-topic';

// Re-running this example is fine: only create what is missing.
if ($client->getStream($stream) === null) {
    $client->createStream($stream);
}

if ($client->getTopic($stream, $topic) === null) {
    $client->createTopic($stream, $topic, 1);
}

$messages = [];
foreach (range(1, 10) as $i) {
    $messages[] = new SendMessage("message-{$i}");
}
$client->sendMessages($stream, $topic, 0, $messages);

$polled = $client->pollMessages($stream, $topic, 0, PollingStrategy::first(), 10, true);
foreach ($polled as $message) {
    echo "Offset: {$message->offset()}, Payload: {$message->payload()}", PHP_EOL;
}

Consumer groups

consumerGroup() returns an Iggy\Consumer that creates and joins the group by default. The partition argument is ignored for consumer groups; the server assigns partitions. After the quickstart has sent ten messages, these two loops consume five each. Both wait for messages until their limit is reached:

<?php

$consumer = $client->consumerGroup(
    'php-consumer',
    $stream,
    $topic,
    0,
    \Iggy\PollingStrategy::next(),
    10,
    \Iggy\AutoCommit::disabled(),
);

$limit = 5;
$consumer->consumeMessages(
    function (\Iggy\ReceiveMessage $message) use ($consumer): void {
        echo $message->payload(), PHP_EOL;
        $consumer->storeOffset($message->offset(), $message->partitionId());
    },
    $limit,
);

$consumed = 0;
foreach ($consumer->iterMessages() as $message) {
    echo $message->payload(), PHP_EOL;
    $consumer->storeOffset($message->offset(), $message->partitionId());
    if (++$consumed === $limit) {
        break;
    }
}

AutoCommit::when() may queue an offset commit before the callback runs. To gate commits on the callback succeeding, use AutoCommit::disabled() and call storeOffset() after the work succeeds, as above.

API notes

  • Methods are camelCase and classes live in the Iggy namespace, for example Iggy\Client and Iggy\SendMessage.
  • Partition ids are 0-based: for a topic with one partition, use 0.
  • PHP strings are passed as named identifiers, including strings that contain only digits. PHP integers are passed as numeric identifiers.
  • PollingStrategy::timestamp() and timestampMicros() expect microseconds since the Unix epoch. Use PollingStrategy::timestampSeconds() for PHP time() values.
  • Message IDs and checksums are returned as decimal strings. Offset and timestamp getters return PHP integers and are limited to PHP_INT_MAX.
  • Errors are thrown as exceptions under Iggy\Exception: AuthenticationException, ConnectionException, NotFoundException, TransientException, and the IggyException base.
  • TLS uses the Rust SDK connection-string format, for example iggy+tcp://user:pass@host:port?tls=true&tls_domain=localhost&tls_ca_file=/path/to/ca.pem.
  • The extension owns a lazy global Tokio runtime. Don't call pcntl_fork() after the first Iggy call: the child inherits file descriptors but not the runtime's worker threads.

Current status

Iggy\Client currently exposes stream and topic create/get/delete, message sending and polling, consumer groups with offset management, ping(), loginUser(), and raw binary requests (sendBinaryRequest()). Other administration surfaces (users, permissions, personal access tokens, partitions) aren't exposed yet.

Examples

Working examples are available in the examples/php directory. They read IGGY_CONNECTION_STRING for a binary transport with credentials. Otherwise they connect over TCP using IGGY_HOST and IGGY_PORT, then log in with the literal IGGY_USERNAME and IGGY_PASSWORD values:

# From the repository root
cd examples/php
(cd ../../foreign/php && cargo build)
export PHP_IGGY_EXTENSION="$(pwd)/../../foreign/php/target/debug/libiggy_php.so"

php -d extension="$PHP_IGGY_EXTENSION" getting-started/producer.php
php -d extension="$PHP_IGGY_EXTENSION" getting-started/consumer.php

On this page