nats

package module
v0.1.0-alpha.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 25, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

FrankenPHP Extension for NATS

A high-performance NATS client for PHP, designed to work with FrankenPHP. It leverages the official NATS Go client.

This extension enables the creation of global, shared NATS connections that persist across requests and worker script instances, giving PHP applications first-class access to NATS messaging without the per-request reconnect cost of pure-PHP clients.

[!NOTE]

frankenphp-nats follows the pattern established by dunglas/frankenphp-etcd and is built using FrankenPHP's Extension Generator. The v0.1.0 scope is core pub/sub (publish, subscribe, request/reply, headers, basic auth, TLS).

JetStream, Key-Value, Object Store, push subscriptions and the NATS Micro framework are planned for follow-up releases — see Roadmap.

Status

Feature Version
Core pub/sub, request/reply, headers v0.1.0
JetStream (streams, consumers, JS publish) v0.2.0
Key-Value & Object Store v0.3.0
Push subscriptions, Caddyfile-declared conns v0.4.0
NATS Micro / Services framework v0.5.0

Installation

First, if not already done, follow the instructions to install a ZTS version of libphp and xcaddy. Then, use xcaddy to build FrankenPHP with the frankenphp-nats module:

CGO_ENABLED=1 \
CGO_CFLAGS="-D_GNU_SOURCE $(php-config --includes)" \
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
XCADDY_GO_BUILD_FLAGS="-tags=nobadger,nomysql,nopgx,nowatcher" \
xcaddy build \
    --output frankenphp \
    --with github.com/abderrahimghazali/frankenphp-nats \
    --with github.com/dunglas/frankenphp/caddy
    # Add extra Caddy modules and FrankenPHP extensions here

The repo ships the pre-generated C/PHP boilerplate (nats.c, nats.h, nats_arginfo.h, nats.stub.php, nats_generated.go) so consumers do not need a PHP build environment to run xcaddy.

That's all — your custom FrankenPHP build now exposes the Abderrahim\Nats namespace to PHP.

Usage

<?php

use function Abderrahim\Nats\connect;
use function Abderrahim\Nats\publish;
use function Abderrahim\Nats\subscribe;
use function Abderrahim\Nats\nextMessage;
use function Abderrahim\Nats\unsubscribe;
use function Abderrahim\Nats\request;
use const Abderrahim\Nats\SECOND;

// Open (or reuse) a globally registered NATS connection. Persists across
// requests and worker reboots.
connect('default', ['nats://127.0.0.1:4222']);

// Fire-and-forget publish, with optional headers.
publish('default', 'orders.created', json_encode(['id' => 42]), [
    'X-Trace-Id' => 'abc123',
]);

// Subscribe and pull one message synchronously.
$sub = subscribe('default', 'orders.>');
$msg = nextMessage($sub, 5 * SECOND);
if ($msg !== null) {
    echo "got {$msg['subject']}: {$msg['data']}\n";
}
unsubscribe($sub);

// Synchronous request/reply.
$reply = request('default', 'svc.echo', 'ping', 1 * SECOND);
echo $reply['data'] ?? 'timeout';
API surface (v0.1.0)

All symbols live under Abderrahim\Nats:

Function Purpose
connect(name, servers, …auth…) Create or reuse a globally registered connection.
publish(name, subject, data, ?headers) Fire-and-forget publish.
request(name, subject, data, timeout) Synchronous request/reply, returns array|null.
subscribe(name, subject, ?queue) Returns a subscription ID (string).
nextMessage(subId, timeout) Pulls one message synchronously.
subscriptionValid(subId) True if the subscription is still in the registry and the underlying NATS sub is live; lets polling loops distinguish a timeout from a connection that was closed underneath them.
unsubscribe(subId) Removes a subscription.
flush(name, timeout) Block until pending publishes are flushed.
isConnected(name) True if currently connected.
stats(name) Returns counters: in_msgs, out_msgs, in_bytes, out_bytes, reconnects.
close(name) Close and remove from the global registry.

Time-unit constants are exposed as NANOSECOND, MICROSECOND, MILLISECOND, SECOND, MINUTE.

Message arrays returned by request() and nextMessage() have the shape:

[
    'subject' => string,
    'data'    => string,
    'reply'   => ?string,
    'headers' => array<string, string[]>,
]
Error handling

Because the Extension Generator does not yet expose a Go-callable API for raising PHP exceptions, failures (connection errors, publish errors, unknown connection names) are logged via the FrankenPHP error log and surface to PHP as zero values:

Function On failure
connect(), publish(), flush(), unsubscribe(), close() Logs and returns. Subsequent calls (isConnected(), stats()) reveal state.
request(), nextMessage() Returns null (also returned on legitimate timeout).
subscribe() Returns an empty string.
stats() Returns an empty array.
isConnected() Returns false.

A future release will introduce explicit exception types once the upstream generator gains exception-throwing primitives.

Authentication

connect() accepts the full set of NATS auth options. Exactly one auth method may be set per call — passing more than one (e.g. username+password and a token) is rejected with a logged error and no connection is registered. username without password (or vice versa) is treated as no-auth and falls through.

Argument Description
username + password Basic auth.
token Token auth.
credsFile Path to a NATS .creds file (NGS / decentralised auth).
nkeyFile Path to an NKey seed file.
tls Enable TLS with sane defaults (TLS 1.2+). When true, server URLs of the form nats://host:port (or bare host:port) are rewritten to tls://host:port before dialing — the underlying nats.go client picks the dialer from the URL scheme, not from the Secure option alone.

Development

This extension is built using FrankenPHP's Extension Generator. After editing nats.go, regenerate the C/PHP boilerplate:

GEN_STUB_SCRIPT=path/to/php-src/build/gen_stub.php \
  frankenphp extension-init nats.go

This refreshes nats.c, nats.h, nats_arginfo.h, nats.stub.php, and nats_generated.go in the repo root. All five files are committed.

[!IMPORTANT]

FrankenPHP v1.12.2's generator emits RETURN_EMPTY_ARRAY() for functions whose stubs declare ?array. PHP's nullable-array semantics require RETURN_NULL(). Two sed patches need to be reapplied after every regeneration:

sed -i '/zend_array \*result = go_request/,/^}/{s/RETURN_EMPTY_ARRAY/RETURN_NULL/}' nats.c
sed -i '/zend_array \*result = go_nextMessage/,/^}/{s/RETURN_EMPTY_ARRAY/RETURN_NULL/}' nats.c

Without these, request() and nextMessage() return an empty array on timeout, and PHP code that checks $msg !== null runs into "Undefined array key" warnings on every key access.

Run the Go tests against a real nats-server:

docker run -d --name nats-test -p 4222:4222 nats:latest
go test -race -tags nobadger,nomysql,nopgx,nowatcher,nobrotli -v ./...
docker rm -f nats-test

Credits

Created by Abderrahim Ghazali, inspired by dunglas/frankenphp-etcd and dunglas/frankenphp-grpc.

Documentation

Overview

Package nats is a FrankenPHP extension exposing the NATS messaging system to PHP via the official Go client (github.com/nats-io/nats.go).

All exported PHP symbols live under the "Abderrahim\Nats" namespace. Connections are stored in a process-wide registry keyed by name and persist across requests and worker reboots, mirroring the pattern established by frankenphp-etcd and frankenphp-grpc.

Index

Constants

View Source
const MICROSECOND = 1000

export_php:const

View Source
const MILLISECOND = 1000000

export_php:const

View Source
const MINUTE = 60000000000

export_php:const

View Source
const NANOSECOND = 1

export_php:const

View Source
const SECOND = 1000000000

export_php:const

Variables

This section is empty.

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL