Bunny
Performant pure-PHP AMQP (RabbitMQ) sync/async (ReactPHP) library
Install / Use
/learn @jakubkulhan/BunnyREADME
BunnyPHP
Performant pure-PHP AMQP (RabbitMQ) non-blocking ReactPHP library
Requirements
BunnyPHP requires PHP 8.1 and newer.
Installation
Add as Composer dependency:
$ composer require bunny/bunny:@^0.6dev
Comparison
You might ask if there isn't a library/extension to connect to AMQP broker (e.g. RabbitMQ) already. Yes, there are multiple options:
- ext-amqp - PHP extension
- php-amqplib - pure-PHP AMQP protocol implementation
Why should you want to choose BunnyPHP instead?
-
You want nice idiomatic PHP API to work with (I'm looking at you, php-amqplib). BunnyPHP interface follows PHP's common coding standards and naming conventions. See tutorial.
-
You can't (don't want to) install PECL extension that has latest stable version in 2014. BunnyPHP isn't as such marked as stable yet. But it is already being used in production.
-
You have both classic CLI/FPM and ReactPHP applications and need to connect to RabbitMQ. BunnyPHP comes with an asynchronous client with a synchronous API using
Fibers.
Apart from that BunnyPHP is more performant than main competing library, php-amqplib. See benchmark/ directory
and php-amqplib's benchmark/. (For ext-amp https://gist.github.com/WyriHaximus/65fd98e099820aded1b79e9111e02916 is used.)
Benchmarks were run as:
$ php benchmark/producer.php N & php benchmark/consumer.php
| Library | N (# messages) | Produce sec | Produce msg/sec | Consume sec | Consume msg/sec | |-------------|---------------:|------------:|----------------:|------------:|----------------:| | php-amqplib | 100 | 0.000671 | 148998 | 0.001714 | 58343 | | ext-amqp | 100 | 0.000302 | 331042 | 0.008915 | 11217 | | bunnyphp | 100 | 0.000194 | 515271 | 0.000939 | 106508 | | bunnyphp +/-| | | +345.8%/+155.6% | | +182.5%/+949.5% | | php-amqplib | 1000 | 0.004827 | 207167 | 0.015166 | 65937 | | ext-amqp | 1000 | 0.002399 | 416846 | 0.078373 | 12760 | | bunnyphp | 1000 | 0.001597 | 626202 | 0.011139 | 89773 | | bunnyphp +/-| | | +302.2%/+150.2% | | +136.1%/+703.5% | | php-amqplib | 10000 | 0.060204 | 166102 | 0.147772 | 67672 | | ext-amqp | 10000 | 0.022735 | 439853 | 0.754800 | 13249 | | bunnyphp | 10000 | 0.016441 | 608232 | 0.106685 | 93734 | | bunnyphp +/-| | | +366.1%/+138.2% | | +138.5%/+707.4% | | php-amqplib | 100000 | 1.158033 | 90276 | 1.477762 | 67670 | | ext-amqp | 100000 | 0.952319 | 105007 | 7.494665 | 13343 | | bunnyphp | 100000 | 0.812430 | 123088 | 1.073454 | 93157 | | bunnyphp +/-| | | +136.3%/+117.2% | | +137.6%/+698.1% | | php-amqplib | 1000000 | 18.64132 | 53644 | 18.992902 | 52651 | | ext-amqp | 1000000 | 12.86827 | 77710 | 89.432139 | 11182 | | bunnyphp | 1000000 | 11.63421 | 85953 | 11.947426 | 83700 | | bunnyphp +/-| | | +160.2%/+110.6% | | +158.9%/+748.5% |
Quick Start
Producing
use Bunny\Client;
use Bunny\Configuration;
use React\EventLoop\Loop;
use function React\Async\async;
$configuration = new Configuration(
host: 'HOSTNAME',
vhost: 'VHOST', // The default vhost is '/'
user: 'USERNAME', // The default user is 'guest'
password: 'PASSWORD', // The default password is 'guest'
);
$bunny = new Client($configuration);
Loop::futureTick(async(static function (): void {
$bunny->channel()->publish(
body: $message, // The message you're publishing as a string
routingKey: 'queue_name', // Routing key, in this example the queue's name
);
$bunny->disconnect();
}));
// Unlike the consumer example, we're not setting signal handlers before this a very quick operation
Consuming
use Bunny\Client;
use Bunny\Configuration;
use React\EventLoop\Loop;
use function React\Async\async;
$configuration = new Configuration(
host: 'HOSTNAME',
vhost: 'VHOST', // The default vhost is '/'
user: 'USERNAME', // The default user is 'guest'
password: 'PASSWORD', // The default password is 'guest'
);
$consumerCleanUp = static function () {};
$bunny = new Client($configuration);
Loop::futureTick(async(static function () use (&$consumerCleanUp): void {
$channel = $bunny->channel();
$response = $channel->consume(
async(static function (Message $message, Channel $channel, Client $bunny) {
$success = handleMessage($message); // Handle your message here
if ($success) {
$channel->ack($message); // Acknowledge message
return;
}
$channel->nack($message); // Mark message fail, message will be redelivered
}),
'queue_name',
);
$consumerCleanUp = static fn () => $channel->cancel($response->consumerTag);
}));
// Unlike the producer example, we do need signal handlers because this process can run for weeks and we want to shut down cleanly
$signals = [SIGINT, SIGTERM, SIGHUP];
$signalHandler = async(static function () use ($signals, &$signalHandler, &$consumerCleanUp, $bunny): void {
foreach ($signals as $signal) {
Loop::removeSignal($signal, $signalHandler);
}
$consumerCleanUp();
$bunny->disconnect();
}))
foreach ($signals as $signal) {
Loop::addSignal($signal, $signalHandler);
}
Always run moving parts in a fiber
Since v0.6 Bunny has been rebuilt using Fibers. This means that every method on Client and Channel must be called inside a fiber. In the examples this will be called using a Loop::futureTick call. In your applications this might be some other trigger like a HTTP request. As long as somewhere in the direct call stack this call is inside a fiber you'll be good.
All the examples from this point on will include a Full Example on how to use that specific example using a full connection cycle inside a fiber the example. For the above that would be the following, even tho it's not doing anything:
use Bunny\Client;
use Bunny\Configuration;
use React\EventLoop\Loop;
use function React\Async\async;
$configuration = new Configuration(
host: 'HOSTNAME',
vhost: 'VHOST', // The default vhost is '/'
user: 'USERNAME', // The default user is 'guest'
password: 'PASSWORD', // The default password is 'guest'
);
$bunny = new Client($configuration);
Loop::futureTick(async(static function (): void {
$bunny->connect();
}));
Tutorial
Connecting
When instantiating the BunnyPHP Client accepts an array with connection options:
use Bunny\Client;
use Bunny\Configuration;
$configuration = new Configuration(
host: 'HOSTNAME',
vhost: 'VHOST', // The default vhost is '/'
user: 'USERNAME', // The default user is 'guest'
password: 'PASSWORD', // The default password is 'guest'
);
$bunny = new Client($configuration);
$bunny->connect();
<details>
<summary>Using DSN</summary>
use Bunny\Client;
use Bunny\Configuration;
$configuration = Configuration::fromDSN('amqp://USERNAME:PASSWORD@HOSTNAME/VHOST');
$bunny = new Client($configuration);
$bunny->connect();
</details>
Connecting securely using TLS(/SSL)
Options for TLS-connections should be specified as array tls:
use Bunny\Client;
use Bunny\Configuration;
$configuration = new Configuration(
host: 'HOSTNAME',
vhost: 'VHOST', // The default vhost is '/'
user: 'USERNAME', // The default user is 'guest'
password: 'PASSWORD', // The default password is 'guest'
tls: [
'cafile' => 'ca.pem',
'local_cert' => 'client.cert',
'local_pk' => 'client.key',
],
);
$bunny = new Client($configuration);
$bunny->connect();
<details>
<summary>Using DSN</summary>
use Bunny\Client;
use Bunny\Configuration;
$configuration = Configuration::fromDSN(
'amqp://USERNAME:PASSWORD@HOSTNAME/VHOST?tls[cafile]=ca.pem&tls[local_cert]=client.cert&tls[local_pk]=client.key',
);
$bunny = new Client($configuration);
$bunny->connect();
</details>
For options description - please see SSL context options.
Note: invalid TLS configuration will cause connection failure.
See also common configuration variants.
Providing client properties
Client Connections can present their capabilities to
a server by presenting an optional client_properties table when establishing a connection.
For example, a connection name may be provided by setting the
[connection_name property](https://www
Related Skills
node-connect
327.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
80.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
327.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
80.6kCommit, push, and open a PR
