# BunnyPHP is a pure-PHP AMQP client built for ReactPHP fibers

> BunnyPHP is a RabbitMQ client written entirely in PHP, with an asynchronous ReactPHP interface and, since version 0.6, a synchronous API built on fibers. Its pitch is a single library that works in classic CLI or FPM applications and in event loop applications, and its benchmark table claims large throughput gains over php-amqplib and the ext-amqp extension.

**jakubkulhan/bunny** — Performant pure-PHP AMQP (RabbitMQ) sync/async (ReactPHP) library

- Repository: https://github.com/jakubkulhan/bunny
- Stars: 749 · Forks: 105
- Language: PHP
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/jakubkulhan-bunny

## One library for both classic PHP and event loop code

BunnyPHP is a performant pure-PHP AMQP library, and the README describes it as a pure-PHP AMQP non-blocking ReactPHP library.

The requirement is PHP 8.1 and newer, and installation is a Composer dependency:

```sh
$ composer require bunny/bunny:@^0.6dev
```

Note the constraint. The `^0.6dev` constraint with the dev stability flag is what you need to install the 0.6 line at all, because the current releases are alpha builds. The recent tags are v0.6.0-alpha.4 from 2026-08-03, v0.6.0-alpha.3 from 2026-07-13 and v0.6.0-alpha.2 from 2025-11-10, and the default branch is 0.6.x. The last push was on 2026-10-15.

The design argument in the README is a comparison with the two existing options, ext-amqp as a PHP extension and php-amqplib as a pure-PHP AMQP protocol implementation. Three reasons are given for choosing BunnyPHP instead.

The first is an idiomatic PHP API, with the note aimed at people who know php-amqplib, and the claim that the BunnyPHP interface follows PHP's common coding standards and naming conventions.

The second is that you cannot or do not want to install a PECL extension that has its latest stable version in 2014. The author is careful here: BunnyPHP is not as such marked as stable yet, but it is already being used in production.

The third is the one that matters most for a mixed codebase. You have both classic CLI or FPM applications and ReactPHP applications and need to connect to RabbitMQ. BunnyPHP comes with an asynchronous client with a synchronous API using Fibers.

That last point is the differentiator. A library that serves both a blocking and a non-blocking style, in the same version, without you maintaining two clients, is not something the other two options offer.

## Version 0.6 rebuilt the library around fibers

The most consequential change in the project is stated under a heading of its own: 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 is done with a `Loop::futureTick` call, and in applications it might be some other trigger like an HTTP request.

That is a hard constraint rather than a style preference, and it is the one thing to check in your own code before upgrading. The old API was blocking, and code that called `$bunny->channel()->publish()` from an ordinary function no longer works. The migration is not a small one if you have a codebase with many call sites.

What it buys you is the synchronous API. A fiber-based sync call is a call that suspends the fiber while it waits on the network, so the code reads like blocking code while the event loop keeps serving other work. That is a different thing from the asynchronous client, where the continuation style is explicit.

Both styles are visible in the quick start. The producer example is short, and it publishes inside a future tick:

```php
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'
);
```

The named arguments in the `Configuration` constructor are a small quality-of-life detail worth noting, since the defaults for vhost, user and password are all shown in the comments, which means the common case of a default broker needs no configuration at all.

## The consumer has to be able to stop cleanly

The consumer example is longer, and the reason is the one every long-running consumer has to deal with: shutting down without losing work or leaving a connection dangling.

The consume call takes an async callback and a queue name, and the callback is where your handling goes. The callback receives the message, the channel and the client, and it returns either an acknowledgement or a negative acknowledgement:

```php
    $response = $channel->consume(
        async(static function (Message $message, Channel $channel, Client $bunny) {
            $success = handleMessage($message); // Handle your message here
```

The distinction matters. If handling succeeded, the message is acknowledged, and if it failed, the message is nacked and will be redelivered. That is the mechanism that turns a consumer into something that survives a downstream failure, and it is the reason the callback returns a success flag rather than just handling the message.

The cleanup is the other half. A consumer keeps a reference to the consumer cleanup closure, and on shutdown it cancels the consumer with the consumer tag from the consume response. Then there is the signal handling, with a list of SIGINT, SIGTERM and SIGHUP, a handler that removes the signal listeners, runs the cleanup and disconnects the client, and a loop that adds each signal to its handler.

The source comment on the producer example explains the difference in one line: unlike the producer example, we do need signal handlers because this process can run for weeks and we want to shut down cleanly. A producer publishes and disconnects inside a single future tick, so it needs no cleanup. A consumer runs for weeks, so it does.

If you take one thing from the example, take that: the shutdown path is not optional for a long-running consumer, and the library gives you the pieces to write it.

## The benchmark table favours the pure-PHP client on throughput

The README states that BunnyPHP is more performant than the main competing library, php-amqplib, and points at a benchmark directory in this repository and one in php-amqplib's own repository, with a note that ext-amp is used for the ext-amqp row. The benchmarks were run as:

```sh
php benchmark/producer.php N & php benchmark/consumer.php
```

The table covers php-amqplib, ext-amqp and bunnyphp across message counts from 100 to 1000000, with produce and consume seconds and messages per second, and a summary row of the difference.

The headline numbers are consistent. At 1000 messages, bunnyphp produces 626202 messages per second against 207167 for php-amqplib and 416846 for ext-amqp, and consumes at 89773 against 65937 and 12760. The summary row gives the deltas as plus 302.2% and plus 150.2% for producing, and plus 136.1% and plus 703.5% for consuming.

The most interesting column is the ext-amqp consume figure, which stays around 11000 to 13000 messages per second across every message count from 1000 to 1000000, while the pure-PHP consumers climb to 89773 and 83700. A flat number across a thousandfold change in message count says the bottleneck is per-message overhead rather than throughput, which is consistent with a C extension paying a fixed cost per call into PHP. That is the trade you make with ext-amqp, and the table makes it concrete rather than leaving it as folklore.

At the largest size, 1000000 messages, the produce figures are 53644 for php-amqplib, 77710 for ext-amqp and 85953 for bunnyphp, and the consume figures are 52651, 11182 and 83700.

The caveat is the one the author states himself about stability, and the benchmark setup is a local producer and consumer pair, which does not tell you about behaviour over a real network.

## The development environment runs RabbitMQ in Docker with TLS

The repository comes with a compose file for the test environment, and it is more elaborate than a single broker container.

The first service is rabbit_node_1 on the rabbitmq:4-management image, with an entrypoint that runs a project-specific script before the standard RabbitMQ one, and a command of rabbitmq-server. The environment sets a default user, password and vhost, an Erlang cookie, and a CONFIG_NAME that defaults to `rabbitmq.tls.verify_peer`, which is the name of a TLS configuration. Three ports are published, 5672 and 5673 for AMQP and 15672 for the management UI, and the whole tree is mounted at /opt/bunny so the container and the host share the project.

The second service is bunny itself, built from docker/bunny, with the SSL environment variables pointing at a client certificate, a client key, a CA and a peer name of server.rmq. Its test connection URI uses the compose service hostname, bunny-rabbit_node_1-1, which shows the container-to-container DNS in action.

There is a separate examples/tls/ directory, and the examples directory also holds wait-for-connection.php and worker.php.

The Makefile explains the development workflow, and it is unusual in a way worth noting. It detects whether you are already inside the wyrihaximus PHP docker image by checking for a marker file, and if so runs the commands directly, and if not wraps them in a docker run of the PHP image with the composer cache directory mounted. The PHP version is derived by reading the require key out of composer.json using jq inside a container. The targets are generate, which runs spec/generate.php to generate code from the spec, cs-fix and cs for code style with phpcs and phpcbf against a phpcs.xml standard, and stan for static analysis with PHPStan.

So the code in src/ is generated from a specification, which is why there is a spec/ directory and a generate step, and why the PHPStan and phpcs configuration are both present.

## Conclusion

Adopt BunnyPHP if you run PHP and want RabbitMQ without a PECL extension, especially if some of your services are event loop based and others are classic PHP, since the same library covers both. Skip it if you need the very latest stable release today, because the current 0.6 line is still published as alpha builds, or if you depend on a PECL extension feature the library does not cover. Verify first that your PHP is 8.1 or newer, that every Client and Channel call in your code sits inside a fiber, since that is a hard requirement of the 0.6 rewrite, and that the alpha tag you install is one you are willing to run in production.

## FAQ

### How do I install BunnyPHP and what does it require?

It requires PHP 8.1 and newer, and you add it as a Composer dependency with `composer require bunny/bunny:@^0.6dev`. The dev stability flag is needed because the current 0.6 releases are alpha builds.

### Why do all BunnyPHP calls need to be inside a fiber?

Since v0.6 the library has been rebuilt using Fibers, which means every method on Client and Channel must be called inside a fiber. The examples use a Loop::futureTick call, and in an application it might be triggered by an HTTP request or a similar event.

### How does BunnyPHP compare with ext-amqp and php-amqplib?

The benchmark table in the project compares all three from 100 to 1000000 messages. At 1000 messages bunnyphp produces 626202 messages per second against 207167 for php-amqplib and 416846 for ext-amqp, and consumes at 89773 against 65937 and 12760. The three reasons the project gives for choosing it are an idiomatic PHP API, not needing a PECL extension whose latest stable version is from 2014, and one library covering both classic and ReactPHP applications.

## Sources

- [Issues](https://github.com/jakubkulhan/bunny/issues)
- [jakubkulhan/bunny on GitHub](https://github.com/jakubkulhan/bunny)
- [License: MIT](https://github.com/jakubkulhan/bunny/blob/0.6.x/LICENSE)
- [README](https://github.com/jakubkulhan/bunny/blob/0.6.x/README.md)
- [Releases](https://github.com/jakubkulhan/bunny/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jakubkulhan-bunny
