PHP code example of idct / php-nats-jetstream-client
1. Go to this page and download the library: Download idct/php-nats-jetstream-client library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
idct / php-nats-jetstream-client example snippets
use IDCT\NATS\Exception\UnsupportedFeatureException;
try {
$js->batch()->add('orders.created', $payload)->commit()->await();
} catch (UnsupportedFeatureException $e) {
// e.g. "Atomic batch publish
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Auth\NkeySeedSigner;
// Token auth.
$tokenClient = new NatsClient(new NatsOptions(
servers: ['nats://127.0.0.1:4222'],
token: 's3cr3t-token',
));
// Username/password.
$passwordClient = new NatsClient(new NatsOptions(
servers: ['nats://127.0.0.1:4222'],
username: 'alice',
password: 's3cr3t',
));
$signer = new NkeySeedSigner('SU...USER NKEY SEED...');
$jwtClient = new NatsClient(new NatsOptions(
servers: ['nats://127.0.0.1:4222'],
jwt: 'your-jwt-token',
nkey: $signer->publicKey(),
nonceSigner: $signer,
));
// Standalone NKey (Ed25519 challenge signing, no JWT): set nkey + nonceSigner and omit jwt.
$nkeyClient = new NatsClient(new NatsOptions(
servers: ['nats://127.0.0.1:4222'],
nkey: $signer->publicKey(),
nonceSigner: $signer,
));
// TLS with CA and client cert/key.
$tlsClient = new NatsClient(new NatsOptions(
servers: ['tls://127.0.0.1:4222'],
tlsRequired: true,
tlsCaFile: '/path/to/ca.pem',
tlsCertFile: '/path/to/client-cert.pem',
tlsKeyFile: '/path/to/client-key.pem',
));
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Transport\WebSocketTransport;
$options = new NatsOptions(
servers: ['ws://127.0.0.1:8080'], // or 'wss://...' for TLS
webSocketCompression: true, // negotiate permessage-deflate (
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
$client = new NatsClient(new NatsOptions(servers: ['nats://127.0.0.1:4222']));
$client->connect()->await();
$sid = $client->subscribe('orders.created', static function (NatsMessage $message): void {
// Handle delivery.
echo $message->payload . PHP_EOL;
})->await();
$client->publish('orders.created', '{"id":123}')->await();
$client->processIncoming()->await();
$client->unsubscribe($sid)->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$reply = $client->request('svc.echo', '{"hello":"world"}', 2000)->await();
echo $reply->payload . PHP_EOL;
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
// Stop after 3 replies, or after the 2000ms total budget, whichever comes first.
$replies = $client->requestMany('svc.scan', 'who-is-there', null, 3, 2000)->await();
foreach ($replies as $reply) {
echo $reply->payload . PHP_EOL;
}
// Time-bounded only: keep collecting until 200ms pass with no new reply.
$discovered = $client->requestMany('svc.scan', 'ping', null, null, 5000, 200)->await();
echo count($discovered) . ' responders' . PHP_EOL;
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
// Create the stream, or update its config if it already exists.
$js->createOrUpdateStream('ORDERS', ['orders.created', 'orders.updated'])->await();
// Create or update a durable consumer in one idempotent call.
$consumer = $js->addOrUpdateConsumer('ORDERS', 'PROC', 'orders.created')->await();
echo $consumer->name . PHP_EOL;
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$js->createStream('ORDERS', ['orders.created'])->await();
$js->createConsumer('ORDERS', 'PULL', 'orders.created')->await();
$js->publish('orders.created', '{"id":123}')->await();
$message = $js->fetchNext('ORDERS', 'PULL', 3000)->await();
$js->ack($message)->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$message = $js->fetchNext('ORDERS', 'PULL', 3000)->await();
// Double-ack: block until the server confirms the ACK (250ms confirmation timeout).
$js->ackSync($message, 250)->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$js->createStream('JOBS', ['jobs.>'])->await();
$js->createConsumer('JOBS', 'WORKER', 'jobs.>')->await();
$js->publish('jobs.process', '{"task":"rebuild"}')->await();
$message = $js->fetchNext('JOBS', 'WORKER', 3000)->await();
// Signal work-in-progress to extend the ack deadline.
$js->inProgress($message)->await();
// NAK: redeliver the message immediately.
$js->nak($message)->await();
// NAK with delay: redeliver after 5 seconds.
// $js->nakWithDelay($message, 5000)->await();
// TERM: terminate delivery, do not redeliver.
// $js->term($message)->await();
$js->deleteConsumer('JOBS', 'WORKER')->await();
$js->deleteStream('JOBS')->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
// Subscribe with a queue group for load-balanced delivery across workers.
$sid = $client->subscribe('tasks.process', static function (NatsMessage $message): void {
echo 'Worker received: ' . $message->payload . PHP_EOL;
}, queue: 'workers')->await();
$client->publish('tasks.process', '{"job":"build"}')->await();
$client->processIncoming()->await();
$client->unsubscribe($sid)->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
// subscribeQueue() returns a SubscriptionQueue for polling-style consumption.
$queue = $client->subscribeQueue('events.>', queue: 'workers')->await();
$queue->setTimeout(5.0);
// Non-blocking fetch - returns null if nothing available.
$msg = $queue->fetch();
// Blocking fetch - waits up to the configured timeout, returns null on timeout.
// With no timeout configured it performs a single processIncoming() cycle (like fetch()).
$msg = $queue->next();
// Batch fetch - collects up to 10 messages within the timeout window.
$messages = $queue->fetchAll(limit: 10);
$client->unsubscribe($queue->sid)->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$js->createStream('ORDERS', ['orders.created'])->await();
$sid = $js->subscribePushConsumer(
stream: 'ORDERS',
consumer: 'PUSH_PROC',
handler: static function (NatsMessage $message) use ($js): void {
// Heartbeats / flow-control are handled automatically by helper.
$js->ack($message)->await();
},
filterSubject: 'orders.created',
)->await();
$js->publish('orders.created', '{"id":123}')->await();
$client->processIncoming()->await();
$client->unsubscribe($sid)->await();
$js->deleteConsumer('ORDERS', 'PUSH_PROC')->await();
$js->deleteStream('ORDERS')->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$js->createStream('ORDERS', ['orders.created'])->await();
// Ephemeral pull consumer.
$ephemeral = $js->createEphemeralConsumer('ORDERS', 'orders.created')->await();
$js->publish('orders.created', '{"id":123}')->await();
$pullMessage = $js->fetchNext('ORDERS', $ephemeral->name)->await();
$js->ack($pullMessage)->await();
// Ephemeral push consumer.
$js->subscribeEphemeralPushConsumer(
stream: 'ORDERS',
handler: static function (NatsMessage $message) use ($js): void {
$js->ack($message)->await();
},
filterSubject: 'orders.created',
)->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\JetStream\Schedule;
use DateTimeImmutable;
$client = new NatsClient(new NatsOptions(servers: ['nats://127.0.0.1:4222']));
$client->connect()->await();
$jetStream = $client->jetStream();
// The backing stream must cover the schedule and target subjects and enable scheduling.
// allow_msg_schedules is tl: '5m',
)->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions(servers: ['nats://127.0.0.1:4222']));
$client->connect()->await();
$js = $client->jetStream();
// The backing stream must enable allow_msg_counter (NATS 2.12+). allow_direct is ment: {$total}\n"; // "5"
$js->incrementCounter('counters.visits', '+3')->await();
$js->incrementCounter('counters.visits', '-1')->await();
// Read the current value via Direct Get ("0" if nothing stored yet).
$current = $js->counterValue('COUNTERS', 'counters.visits')->await();
echo "Current value: {$current}\n"; // "7"
$client->disconnect()->await();
declare(strict_types=1);
use Amp\CancelledException;
use Amp\TimeoutCancellation;
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\JetStream\KeyValue\KeyValueEntry;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$kv = $client->jetStream()->keyValue('cfg');
$kv->create()->await();
// Register the watcher BEFORE the writes it should observe: watch() delivers live updates only
// (deliver_policy=new) and does not replay pre-existing values. Each entry carries its revision.
$watchSid = $kv->watch(static function (KeyValueEntry $entry): void {
echo $entry->key . ':' . ($entry->value ?? '<deleted>') . ' (rev ' . ($entry->revision ?? 0) . ')' . PHP_EOL;
}, 'theme')->await();
$kv->put('theme', 'dark')->await();
$entry = $kv->get('theme')->await();
echo $entry?->value . PHP_EOL;
if ($entry !== null) {
$kv->update('theme', 'light', $entry->revision ?? 1)->await();
}
$all = $kv->getAll()->await();
echo ($all['theme'] ?? '') . PHP_EOL;
$status = $kv->getStatus()->await();
echo $status['stream'] . PHP_EOL;
$kv->delete('theme')->await();
$kv->purge('theme')->await();
// Drive delivery so the watcher receives the buffered updates, bounded so it cannot block forever.
try {
$cancellation = new TimeoutCancellation(2.0);
while (true) {
$client->processIncoming($cancellation)->await();
}
} catch (CancelledException) {
}
$client->unsubscribe($watchSid)->await();
$kv->deleteBucket()->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$kv = $client->jetStream()->keyValue('cfg');
// Load the value of 'theme' as it existed at stream sequence 2.
$historical = $kv->getRevision('theme', 2)->await();
echo $historical?->value ?? '<none>', PHP_EOL;
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$store = $client->jetStream()->objectStore('assets');
// Link to another object (optionally in a different bucket).
$store->addLink('shortcut', 'real.bin')->await();
// Link to a whole bucket.
$store->addBucketLink('mirror', 'other-bucket')->await();
// Rename without re-uploading; the stored chunks are preserved by NUID.
$store->updateMeta('logo.txt', 'brand.txt')->await();
// Replace only the metadata bag (no rename, no re-upload).
$store->updateMeta('brand.txt', null, ['team' => 'brand'])->await();
// Make the bucket permanently read-only (irreversible).
$store->seal()->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$store = $client->jetStream()->objectStore('assets');
$store->create()->await();
$store->put('logo.txt', 'hello-object')->await();
// getToCallback streams the object chunk-by-chunk: the callback is invoked once per stored
// chunk as it is downloaded (the whole object is never buffered in memory), and the SHA-256
// digest is verified incrementally after the final chunk.
$info = $store->getToCallback('logo.txt', static function (string $chunk): void {
echo $chunk;
})->await();
echo PHP_EOL;
echo $info?->name . PHP_EOL;
$store->deleteBucket()->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$store = $client->jetStream()->objectStore('assets');
$store->create()->await();
// putStream() pulls the object's bytes from a producer callback (return the next block, or null at
// end of stream), so the whole payload is never held in memory. Blocks of any size are re-chunked to
// the bucket's chunk size, published in bounded in-flight windows, and the SHA-256 digest is computed
// incrementally - the streaming counterpart to getToCallback().
$handle = fopen('/path/to/large.bin', 'rb');
$info = $store->putStream('large.bin', static function () use ($handle): ?string {
$block = fread($handle, 1 << 16);
return ($block === '' || $block === false) ? null : $block;
})->await();
fclose($handle);
echo $info->size . ' bytes in ' . $info->chunks . ' chunks' . PHP_EOL;
$store->deleteBucket()->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
$serviceClient = new NatsClient(new NatsOptions());
$serviceClient->connect()->await();
$service = $serviceClient->service('echo', '1.0.0', 'Echo demo')
->addEndpoint('echo', 'svc.echo', static function (NatsMessage $message): string {
return 'reply:' . $message->payload;
});
// Handlers can also be provided as objects implementing
// IDCT\NATS\Services\ServiceEndpointHandlerInterface or class-string adapters.
$service->addGroup('svc')->addGroup('v1')->addEndpoint(
'echo-v1',
'echo',
static function (NatsMessage $message): string {
return 'v1:' . $message->payload;
},
);
$service->start()->await();
// In another client you can call discovery or endpoint subjects:
// - $SRV.PING.echo
// - $SRV.INFO.echo
// - $SRV.STATS.echo
// - $SRV.SCHEMA.echo
// - svc.echo
$service->stop()->await();
$serviceClient->disconnect()->await();
// Optional runtime helper: start + process loop + auto-stop on timeout.
// $service->run(timeoutSeconds: 30.0)->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$service = $client->service('echo', '1.0.0')
->withRequestValidator(static function (NatsMessage $message, array $schema): ?string {
// Return null to accept, or a string describing why the request is rejected.
return $message->payload === '' ? 'payload must not be empty' : null;
})
->addEndpoint('echo', 'svc.echo', static fn (NatsMessage $message): string => $message->payload, schema: ['type' => 'object']);
$service->start()->await();
// Inspect live counters.
$stats = $service->statsSnapshot();
echo $stats['endpoints'][0]['num_requests'] . PHP_EOL;
// Zero the runtime statistics.
$service->reset();
$service->stop()->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
use IDCT\NATS\Services\BasicJsonSchemaValidator;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$service = $client->service('calc', '1.0.0', 'Calculator')
->withSchemaValidator(new BasicJsonSchemaValidator())
->addObserver(static function (string $event, $endpoint, NatsMessage $message, array $context): void {
// Example events: request_start, request_error, request_end
// Example context key: correlation_id (from X-Request-Id/traceparent headers)
})
->addEndpoint('add', 'calc.add', static function (NatsMessage $message): string {
return 'result';
}, schema: [
'type' => 'object',
'
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$client->subscribe('events.>', static function (NatsMessage $message): void {
echo $message->payload . PHP_EOL;
})->await();
// Gracefully drain: unsubscribes all SIDs, delivers pending messages, then closes.
$client->drain()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$js->createStream('EVENTS', ['events.>'])->await();
// Ordered consumer: ephemeral push consumer with flow control,
// idle heartbeat, and ack_policy=none for ordered delivery.
$sid = $js->subscribeOrderedConsumer(
stream: 'EVENTS',
handler: static function (NatsMessage $message): void {
echo $message->payload . PHP_EOL;
},
filterSubject: 'events.>',
)->await();
$js->publish('events.order', '{"id":1}')->await();
$client->processIncoming()->await();
$client->unsubscribe($sid)->await();
$js->deleteStream('EVENTS')->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$js->createStream('ORDERS', ['orders.>'])->await();
$js->createConsumer('ORDERS', 'PROC', 'orders.created')->await();
// Pause the consumer until a specific time (ISO 8601 format).
$js->pauseConsumer('ORDERS', 'PROC', '2026-03-12T00:00:00Z')->await();
// Resume the consumer immediately.
$js->resumeConsumer('ORDERS', 'PROC')->await();
$js->deleteConsumer('ORDERS', 'PROC')->await();
$js->deleteStream('ORDERS')->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$js->createStream('LOGS', ['logs.>'])->await();
$js->createConsumer('LOGS', 'BATCH', 'logs.>')->await();
for ($i = 0; $i < 5; $i++) {
$js->publish('logs.app', "log entry $i")->await();
}
// Fetch up to 5 messages in one batch.
$messages = $js->fetchBatch('LOGS', 'BATCH', batch: 5, expiresMs: 3000)->await();
foreach ($messages as $message) {
$js->ack($message)->await();
}
$js->deleteConsumer('LOGS', 'BATCH')->await();
$js->deleteStream('LOGS')->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$js->createStream('LOGS', ['logs.>'])->await();
$js->publish('logs.app', 'entry 1')->await();
$js->publish('logs.app', 'entry 2')->await();
// Purge all messages from the stream.
$result = $js->purgeStream('LOGS')->await();
echo 'Purged: ' . $result['purged'] . PHP_EOL;
// Purge by subject filter.
// $js->purgeStream('LOGS', ['filter' => 'logs.app'])->await();
// List all streams.
$streams = $js->listStreams()->await();
foreach ($streams as $stream) {
echo $stream->name . PHP_EOL;
}
$js->deleteStream('LOGS')->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$js->createStream('EVENTS', ['events.>'])->await();
$js->publish('events.order', '{"id":1}')->await();
// Fetch message by stream sequence number.
$message = $js->getStreamMessage('EVENTS', 1)->await();
echo $message->payload . PHP_EOL;
$js->deleteStream('EVENTS')->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
// Fast delete (default): unlink sequence 7, leave the bytes on disk.
$js->deleteMessage('ORDERS', 7)->await();
// Secure erase: overwrite the data for sequence 8 before removing it.
$js->deleteMessage('ORDERS', 8, secureErase: true)->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
$js->createStream('EVENTS', ['events.>'], ['allow_direct' => true])->await();
$js->publish('events.order', '{"id":1}')->await();
// Direct Get by stream sequence.
$bySeq = $js->directGetStreamMessage('EVENTS', 1)->await();
echo $bySeq->subject . ': ' . $bySeq->payload . PHP_EOL;
// Direct Get the last message stored on a subject.
$last = $js->directGetLastMessageForSubject('EVENTS', 'events.order')->await();
echo $last->payload . PHP_EOL;
$js->deleteStream('EVENTS')->await();
$client->disconnect()->await();
$js = $client->jetStream();
$js->createStream('EVENTS', ['events.>'], ['allow_direct' => true])->await();
$js->publish('events.order', '{"id":1}')->await();
$js->publish('events.user', '{"id":2}')->await();
// Latest message per subject, in a single request.
$latest = $js->directGetLastForSubjects('EVENTS', ['events.order', 'events.user'])->await();
foreach ($latest as $msg) {
echo $msg->subject . ': ' . $msg->payload . PHP_EOL;
}
// Raw batched get over a sequence range (here: up to 10 messages from sequence 1).
$range = $js->directGetBatch('EVENTS', ['seq' => 1, 'batch' => 10])->await();
echo count($range) . ' messages' . PHP_EOL;
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
// The stream must allow atomic batches (NATS 2.12+).
$js->createStream('ORDERS', ['orders.>'], ['allow_atomic' => true])->await();
// Stage messages, then commit them all atomically.
$batch = $js->batch()
->add('orders.created', '{"id":1}')
->add('orders.created', '{"id":2}')
->add('orders.created', '{"id":3}');
echo $batch->count() . " messages staged in batch {$batch->batchId()}" . PHP_EOL;
$ack = $batch->commit()->await();
echo "Committed {$ack->batchCount} messages (batch {$ack->batchId})" . PHP_EOL;
$js->deleteStream('ORDERS')->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Auth\CredentialsParser;
use IDCT\NATS\Auth\NkeySeedSigner;
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
// Parse a .creds file to extract JWT and NKey seed.
$creds = CredentialsParser::fromFile('/path/to/user.creds');
$signer = new NkeySeedSigner($creds['nkeySeed']);
$client = new NatsClient(new NatsOptions(
servers: ['nats://127.0.0.1:4222'],
jwt: $creds['jwt'],
nkey: $signer->publicKey(),
nonceSigner: $signer,
));
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\JetStream\Enum\AckPolicy;
use IDCT\NATS\JetStream\Enum\DeliverPolicy;
use IDCT\NATS\JetStream\Enum\DiscardPolicy;
use IDCT\NATS\JetStream\Enum\ReplayPolicy;
use IDCT\NATS\JetStream\Enum\RetentionPolicy;
use IDCT\NATS\JetStream\Enum\StorageBackend;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
// Create stream with typed configuration.
$js->createStream('ORDERS', ['orders.>'], [
'retention' => RetentionPolicy::Limits->value,
'storage' => StorageBackend::Memory->value,
'discard' => DiscardPolicy::Old->value,
'max_msgs' => 100_000,
'max_bytes' => 50 * 1024 * 1024,
'max_age' => 86_400_000_000_000, // 24h in nanoseconds
'num_replicas' => 1,
'duplicate_window' => 120_000_000_000, // 2 min in nanoseconds
])->await();
// Create consumer with typed configuration.
$js->createConsumer('ORDERS', 'PROC', 'orders.created', [
'deliver_policy' => DeliverPolicy::New->value,
'ack_policy' => AckPolicy::Explicit->value,
'replay_policy' => ReplayPolicy::Instant->value,
'max_deliver' => 5,
'max_ack_pending' => 1000,
'ack_wait' => 30_000_000_000, // 30s in nanoseconds
])->await();
$js->deleteConsumer('ORDERS', 'PROC')->await();
$js->deleteStream('ORDERS')->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
use IDCT\NATS\JetStream\JetStreamContext;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
// Process messages in batches of 10, up to 5 iterations.
$totalProcessed = $js->pullConsumer('ORDERS', 'PROC')
->setBatching(10)
->setExpiresMs(5000)
->setIterations(5)
->handle(function (NatsMessage $msg, JetStreamContext $js): void {
echo 'Processing: ' . $msg->payload . PHP_EOL;
$js->ack($msg)->await();
})->await();
echo "Processed {$totalProcessed} messages total." . PHP_EOL;
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
use IDCT\NATS\JetStream\JetStreamContext;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
// Create a pull consumer with a pinned-client priority group (NATS 2.11+).
$js->createConsumer('ORDERS', 'PROC', null, [
'priority_groups' => ['g1'],
'priority_policy' => 'pinned_client',
])->await();
// Pull under the group. The iterator captures and resends the Nats-Pin-Id
// automatically, and re-pins transparently if the pin goes stale (423).
$totalProcessed = $js->pullConsumer('ORDERS', 'PROC')
->setGroup('g1')
->setBatching(10)
->setExpiresMs(5000)
->setIterations(5)
->setMaxBytes(1048576)
->handle(function (NatsMessage $msg, JetStreamContext $js): void {
// Inspect the pin id carried by the first message of the pinned group.
$pinId = $js->pinIdOf($msg); // string|null
echo 'Processing: ' . $msg->payload . PHP_EOL;
$js->ack($msg)->await();
})->await();
echo "Processed {$totalProcessed} messages total." . PHP_EOL;
// Release the active pin so another client can take over the group.
$js->unpinConsumer('ORDERS', 'PROC', 'g1')->await();
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\JetStream\Configuration\StreamSource;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$mirror = StreamSource::mirror('ORDERS')->toArray();
$aggregateSources = [
StreamSource::source('ORDERS')->filterSubject('orders.>')->toArray(),
StreamSource::source('PAYMENTS')->startSeq(100)->toArray(),
];
$remoteMirror = StreamSource::mirror('ORIGIN')
->external('$JS.hub.API', '_DELIVER.hub')
->toArray();
var_dump($mirror, $aggregateSources, $remoteMirror);
$client->disconnect()->await();
declare(strict_types=1);
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\JetStream\Configuration\Republish;
use IDCT\NATS\JetStream\Configuration\SubjectTransform;
$client = new NatsClient(new NatsOptions());
$client->connect()->await();
$js = $client->jetStream();
// Republish all order messages to a monitoring subject.
$js->createStream('ORDERS', ['orders.>'], [
'republish' => Republish::create('orders.>', 'monitor.orders.>')->toArray(),
])->await();
// Republish headers only (strip payload) for lightweight notifications.
$js->createStream('EVENTS', ['events.>'], [
'republish' => Republish::create('events.>', 'notify.events.>')->headersOnly()->toArray(),
])->await();
// Apply a subject transform to remap subjects on ingest.
$js->createStream('MAPPED', ['raw.>'], [
'subject_transform' => SubjectTransform::create('raw.>', 'processed.>')->toArray(),
])->await();
$client->disconnect()->await();
// Process all available messages for up to 1 second.
$deadline = microtime(true) + 1.0;
while (microtime(true) < $deadline) {
$frames = $client->processIncoming()->await();
if ($frames === 0) {
break;
}
}
declare(strict_types=1);
use Amp\CancelledException;
use Amp\DeferredCancellation;
use IDCT\NATS\Connection\NatsOptions;
use IDCT\NATS\Core\NatsClient;
use IDCT\NATS\Core\NatsMessage;
use function Amp\async;
$iterations = 5000;
$subject = 'bench.echo';
$server = new NatsClient(new NatsOptions());
$client = new NatsClient(new NatsOptions());
$server->connect()->await();
$client->connect()->await();
$server->subscribe($subject, static function (NatsMessage $message) use ($server): void {
if ($message->replyTo !== null) {
$server->publish($message->replyTo, 'ok')->await();
}
})->await();
// Drive the responder from one continuous background read loop.
$serverCancel = new DeferredCancellation();
$serverLoop = async(static function () use ($server, $serverCancel): void {
$cancellation = $serverCancel->getCancellation();
while (!$cancellation->isRequested()) {
try {
$server->processIncoming($cancellation)->await();
} catch (CancelledException) {
break;
}
}
});
$start = hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$client->request($subject, 'x', 2000)->await();
}
$elapsedNs = hrtime(true) - $start;
$serverCancel->cancel();
$serverLoop->await();
$totalMs = $elapsedNs / 1_000_000;
$rps = $iterations / max(0.001, ($elapsedNs / 1_000_000_000));
echo 'iterations=' . $iterations . PHP_EOL;
echo 'total_ms=' . number_format($totalMs, 2, '.', '') . PHP_EOL;
echo 'req_per_sec=' . number_format($rps, 2, '.', '') . PHP_EOL;
$client->disconnect()->await();
$server->disconnect()->await();
bash
docker compose up -d nats
NATS_URL=nats://127.0.0.1:14222 BENCH_ITER=5000 php scripts/benchmark.php
docker compose down
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.