Skip to content

Getting started ​

Hermes 6.x is a PHP library. There is no Hermes binary. A web request constructs a Message and passes it to Emitter. A separate PHP CLI process constructs a Dispatcher, registers handlers, and calls handle(), which blocks.

The package constraint in composer.json is php: ^8.4. CI runs PHP 8.4, PHP 8.4 with the lowest dependencies, and PHP 8.5.

Install ​

bash
composer require tomaj/hermes

That pulls in ramsey/uuid (^4.7) and psr/log (^2 || ^3). tracy/tracy is optional: Hermes writes exceptions to it when you install and enable it. You still pick a queue client:

DriverWhat you install
RedisSetDriverThe phpredis extension (ext-redis). Composer does not install the extension.
PredisSetDrivercomposer require predis/predis (Predis 2.3 or newer, or 3.x)
LazyRabbitMqDrivercomposer require php-amqplib/php-amqplib
AmazonSqsDrivercomposer require aws/aws-sdk-php

Logging is optional. Any PSR-3 logger works. Monolog is the usual choice and is listed under suggest, not require.

Quickstart ​

This is the Redis extension driver, matching examples/redis. The third constructor argument of Message is a message id, not a priority. Priority is the second argument of emit().

php
<?php

declare(strict_types=1);

use Redis;
use Tomaj\Hermes\Dispatcher;
use Tomaj\Hermes\Driver\RedisSetDriver;
use Tomaj\Hermes\Emitter;
use Tomaj\Hermes\Message;

require __DIR__ . '/vendor/autoload.php';

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$driver = new RedisSetDriver($redis);
$emitter = new Emitter($driver);

$emitter->emit(new Message('send-email', [
    'to' => 'test@test.com',
    'subject' => 'Testing hermes email',
    'message' => 'Hello from hermes!',
]));

RedisSetDriver registers the key hermes at Dispatcher::DEFAULT_PRIORITY (100). emit() without a second argument uses that same constant.

The worker lives in another file and is started with php worker.php. EchoHandler is included in the library. It prints the id, the type, and json_encode() of the payload, then returns true.

php
<?php

declare(strict_types=1);

use Redis;
use Tomaj\Hermes\Dispatcher;
use Tomaj\Hermes\Driver\RedisSetDriver;
use Tomaj\Hermes\Handler\EchoHandler;

require __DIR__ . '/vendor/autoload.php';

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$driver = new RedisSetDriver($redis);
$dispatcher = new Dispatcher($driver);
$dispatcher->registerHandler('send-email', new EchoHandler());

$dispatcher->handle();

handle() does not return until the driver stops the loop (shutdown, max items, or a driver error). Do not call it from a web request. The docblock on Dispatcher::handle() says the same thing.

Terminal showing EchoHandler output

Stdout from EchoHandler looks like this:

text
Received message: #6f1c0a2e-9b44-4d1a-8c33-0a9e5d77b214 (type send-email)
Payload: {"to":"test@test.com","subject":"Testing hermes email","message":"Hello from hermes!"}

Those lines are the only output unless you pass a logger. See Logging.

A handler of your own ​

HandlerInterface has one method. Return true when the work succeeded. Return false to mark that call as failed. Throw to mark it as failed and, when the handler is retryable (RetryableHandlerInterface with RetryTrait), to schedule another attempt.

php
<?php

declare(strict_types=1);

use Tomaj\Hermes\Handler\HandlerInterface;
use Tomaj\Hermes\MessageInterface;

class SendEmailHandler implements HandlerInterface
{
    public function handle(MessageInterface $message): bool
    {
        $payload = $message->getPayload();
        if ($payload === null) {
            return false;
        }

        mail($payload['to'], $payload['subject'], $payload['message']);

        return true;
    }
}

Register it with the same string you used as the message type:

php
$dispatcher->registerHandler('send-email', new SendEmailHandler());

Returning false does not put the message back. The Redis driver has already removed it with SPOP. Retry explains the only built-in second chance.

FAQ ​

Does Hermes need a server or daemon of its own? ​

No. Hermes is a PHP library. Your web code emits messages, and a PHP CLI script that you write runs the worker with Dispatcher::handle(). The only shared piece is the broker: Redis, RabbitMQ, or SQS.

Which driver should I start with? ​

Use PredisSetDriver if you cannot install a PHP extension, or RedisSetDriver if you already have phpredis. Both support priorities. On 5.x only Predis moves delayed messages and retries back to the queue; RedisSetDriver does too from 6.0. See the driver comparison.

Can I call handle() from a web request? ​

No. handle() blocks until shutdown, max items, or a driver error. Run it in a separate CLI process under a process manager. See Production deployment.

What should a handler return? ​

true when the work succeeded and false when it failed for good. Throw an exception when a later attempt could succeed: with RetryableHandlerInterface and RetryTrait the dispatcher then sends a retry copy. See Retry.

Is there a complete project I can copy? ​

Yes. The welcome email tutorial builds an app from an empty folder, with Redis and Mailpit in Docker Compose, a real email handler, retries, a test, and Supervisor.

Released under the MIT License.