PHPackages                             assetplan/herald - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Queues &amp; Workers](/categories/queues)
4. /
5. assetplan/herald

ActiveLibrary[Queues &amp; Workers](/categories/queues)

assetplan/herald
================

Announce events across your Laravel applications using message queues

v0.1.4(5mo ago)08[1 issues](https://github.com/AssetPlan/herald/issues)[1 PRs](https://github.com/AssetPlan/herald/pulls)MITPHPPHP ^8.1|^8.2|^8.3

Since Nov 10Pushed 5mo ago1 watchersCompare

[ Source](https://github.com/AssetPlan/herald)[ Packagist](https://packagist.org/packages/assetplan/herald)[ RSS](/packages/assetplan-herald/feed)WikiDiscussions main Synced today

READMEChangelog (5)Dependencies (9)Versions (9)Used By (0)

Herald
======

[](#herald)

Announce events across your Laravel applications using RabbitMQ.

Herald enables pub-sub messaging between distributed applications. Publish events from any application (Laravel, CakePHP, legacy PHP, etc.) via RabbitMQ, and consume them in Laravel where they're dispatched as native Laravel events to your Horizon queues.

Features
--------

[](#features)

- **Pattern-Based Routing**: Subscribe to specific event patterns using RabbitMQ's topic exchange (`user.*`, `order.#`, etc.)
- **Efficient Message Filtering**: Broker-level routing ensures consumers only receive relevant events
- **Handler Registration**: Simple `Herald::on()` API for mapping message types to handlers
- **Flexible Handler Types**: Queued jobs, sync handlers, closures, or object instances
- **Idempotent Processing**: Immediate acknowledgment with handler errors delegated to the app
- **Signal Handling**: Graceful shutdown on SIGTERM/SIGINT
- **Queue Integration**: Handlers dispatched to Laravel's queue system (Horizon compatible)

Requirements
------------

[](#requirements)

- PHP 8.1 or higher
- Laravel 10.x or 11.x
- RabbitMQ

Installation
------------

[](#installation)

Install via Composer:

```
composer require assetplan/herald
```

**That's it!** Herald automatically registers via Laravel package discovery.

### Setting Up Handler Registration

[](#setting-up-handler-registration)

Run the install command to publish a dedicated service provider for registering your message handlers:

```
php artisan herald:install
```

This creates `app/Providers/HeraldServiceProvider.php` where you can register your handlers. Add it to your `config/app.php`:

```
'providers' => [
    // ...
    App\Providers\HeraldServiceProvider::class,
],
```

**Note:** If you're using Laravel 11+ with automatic provider discovery, the provider will be automatically registered.

### Optional: Customize Connection Settings

[](#optional-customize-connection-settings)

If you need to customize connection settings beyond environment variables, you can optionally publish the configuration file:

```
php artisan vendor:publish --tag=herald-config
```

**Most users won't need to publish the config.** Just set your environment variables and you're good to go.

Configuration
-------------

[](#configuration)

Herald uses environment variables for configuration. Add these to your `.env` file:

```
HERALD_CONNECTION=rabbitmq
RABBITMQ_HOST=localhost
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_VHOST=/
RABBITMQ_EXCHANGE=herald-events
RABBITMQ_QUEUE=my-app-queue  # Each application should have its own queue name
```

**Note:** Herald uses a **topic exchange**, which enables efficient pattern-based routing. Each application has its own queue bound to the exchange, and workers subscribe to specific event patterns (e.g., `user.*`, `order.#`).

### Registering Handlers

[](#registering-handlers)

After running `php artisan herald:install`, register handlers in `app/Providers/HeraldServiceProvider.php`:

```
use Assetplan\Herald\Facades\Herald;
use Assetplan\Herald\Message;

class HeraldServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Queued job (automatically queued if it implements ShouldQueue)
        Herald::on('order.created', \App\Jobs\ProcessOrder::class);

        // Sync handler (executes immediately)
        Herald::on('cache.invalidate', \App\Handlers\CacheInvalidator::class);

        // Closure (queued via Herald wrapper job)
        Herald::on('user.logout', fn (Message $msg) => Log::info("User logged out: {$msg->id}"));

        // Multiple handlers for the same event
        Herald::on('payment.received', \App\Jobs\SendReceipt::class);
        Herald::on('payment.received', \App\Jobs\UpdateInventory::class);

        // Same handler for multiple events
        Herald::onAny([
            'property.pricing.updated',
            'property.reservation.fallen',
            'property.photo.updated',
        ], \App\Jobs\UpdateUnitIndex::class);

        // Legacy job adapter - bridge to existing jobs
        Herald::on('user.registered', function (Message $msg) {
            \App\Jobs\SendWelcomeEmail::dispatch(
                userId: $msg->payload['user_id'],
                email: $msg->payload['email']
            );
        });
    }
}
```

**Tip:** The published service provider includes detailed examples and documentation for all handler types.

**Handler Types:**

1. **Queued Jobs** - Implement `ShouldQueue`, dispatched with YOUR queue settings
2. **Sync Handlers** - Classes with `handle(Message $message)` method
3. **Closures** - Queued via Herald wrapper job (serializable only)
4. **Pre-configured Instances** - Pass configured objects directly

```
// Pre-configured instance example
$emailSender = new \App\Services\EmailSender(
    apiKey: config('services.sendgrid.key')
);
Herald::on('email.send', $emailSender);
```

Usage
-----

[](#usage)

### Running the Worker

[](#running-the-worker)

Start the Herald worker to consume messages:

```
# Process all events (subscribes to all routing keys)
php artisan herald:work '*'

# Process only 'user.*' events (user.created, user.updated, etc.)
php artisan herald:work 'user.*'

# Process specific event
php artisan herald:work 'order.shipped'

# Process multiple patterns using wildcards
# * matches exactly one word
# # matches zero or more words
php artisan herald:work 'user.*.verified'  # Matches: user.email.verified
php artisan herald:work 'order.#'          # Matches: order.created, order.payment.completed

# Use a specific connection (if you have multiple RabbitMQ connections configured)
php artisan herald:work 'user.*' --connection=rabbitmq
```

### Listing Registered Handlers

[](#listing-registered-handlers)

See which events are registered and how they will run:

```
php artisan herald:list
```

#### Topic Pattern Matching

[](#topic-pattern-matching)

Herald uses RabbitMQ's topic exchange for efficient message routing:

- **`*` (asterisk)** - matches exactly one word (e.g., `user.*` matches `user.created`, `user.deleted`)
- **`#` (hash)** - matches zero or more words (e.g., `order.#` matches `order.created`, `order.payment.completed`)
- **Exact match** - subscribe to a specific event (e.g., `user.created`)

**Examples:**

- `user.*` - All user events (`user.created`, `user.updated`, `user.deleted`)
- `*.created` - All creation events (`user.created`, `order.created`, `product.created`)
- `user.*.verified` - Events like `user.email.verified`, `user.phone.verified`
- `order.#` - All order-related events, including nested ones
- `#` - All events

The worker will:

1. Connect to RabbitMQ
2. Subscribe only to messages matching your topic pattern
3. Execute registered handlers for each message type
4. Dispatch queued handlers to Laravel's queue system (Horizon compatible)
5. Acknowledge receipt immediately

### Creating Handlers

[](#creating-handlers)

Herald gives you full flexibility in how you handle messages. Here are the different approaches:

#### 1. Synchronous Handler (Fast Operations)

[](#1-synchronous-handler-fast-operations)

For quick operations that complete in milliseconds:

```
namespace App\Herald\Handlers;

use Assetplan\Herald\Message;
use Illuminate\Support\Facades\Log;

class LogUserActivity
{
    public function handle(Message $message): void
    {
        Log::info('User activity', [
            'event_id' => $message->id,
            'event_type' => $message->type,
            'data' => $message->payload,
        ]);
    }
}
```

Register it:

```
Herald::on('user.activity', LogUserActivity::class);
```

#### 2. Laravel Job (Heavy Operations)

[](#2-laravel-job-heavy-operations)

For time-consuming operations, API calls, or database-intensive work, use a standard Laravel Job:

```
namespace App\Jobs;

use Assetplan\Herald\Message;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessOrderPayment implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $queue = 'payments';
    public $tries = 3;
    public $backoff = [60, 120, 300];

    public function __construct(public Message $message)
    {
    }

    public function handle(): void
    {
        // Heavy operation - automatically queued
        $this->chargeCustomer($this->message->payload);
    }
}
```

Register it (automatically queued because it implements `ShouldQueue`):

```
Herald::on('order.created', ProcessOrderPayment::class);
```

**Note:** When using a Laravel Job as a Herald handler, your `__construct()` method should receive the `public Message $message` parameter. Herald will instantiate the job with the message and dispatch it to your queue.

#### 3. Laravel Event Handler

[](#3-laravel-event-handler)

Dispatch to Laravel's event system for complex workflows:

```
namespace App\Events;

use Illuminate\Foundation\Events\Dispatchable;

class UserRegistered
{
    use Dispatchable;

    public function __construct(public array $data) {}
}
```

Register listeners in `EventServiceProvider`:

```
protected $listen = [
    UserRegistered::class => [
        SendWelcomeEmail::class,
        CreateUserProfile::class,
        NotifyAdmins::class,
    ],
];
```

Register with Herald:

```
Herald::on('user.registered', UserRegistered::class);
```

#### 4. Closure Handler (Prototyping/Simple Logic)

[](#4-closure-handler-prototypingsimple-logic)

Closures are queued via a Herald wrapper job (must be serializable):

```
Herald::on('cache.clear', fn (Message $msg) => Cache::forget($msg->payload['key']));
Herald::on('user.logout', fn (Message $msg) => Log::info("User {$msg->payload['user_id']} logged out"));
```

**Pro tip:** Closures are great for development, but use proper classes in production for better testability and maintainability.

### Publishing Messages from Laravel

[](#publishing-messages-from-laravel)

Herald provides a simple `publish()` method for sending messages:

```
use Assetplan\Herald\Facades\Herald;

// Simple publish
Herald::publish('user.created', [
    'user_id' => 123,
    'email' => 'user@example.com',
]);

// Publish with custom message ID
Herald::publish('order.completed', [
    'order_id' => 456,
    'total' => 99.99,
], id: 'custom-id-123');

// Publish to specific connection
Herald::publish('index.rebuild', [
    'entity_id' => 789,
], connection: 'rabbitmq');
```

The routing key (first parameter) is used for topic-based routing, allowing consumers to subscribe to specific event patterns.

### Publishing from Legacy PHP Applications

[](#publishing-from-legacy-php-applications)

Herald works with any publisher that can send JSON messages. Here's a PHP 5.6+ example for CakePHP or other legacy applications:

**RabbitMQ Publisher (PHP 5.6+):**

```
