PHPackages                             xblabs/ripple - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. xblabs/ripple

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

xblabs/ripple
=============

Ripple is a PHP event dispatcher library that allows you to create event-driven systems by providing a simple and easy-to-use interface for handling events.

2.0.0(2d ago)00MITPHP &gt;=8.1

Since May 10Compare

[ Source](https://github.com/xblabs/Ripple)[ Packagist](https://packagist.org/packages/xblabs/ripple)[ RSS](/packages/xblabs-ripple/feed)WikiDiscussions Synced today

READMEChangelogDependencies (4)Versions (3)Used By (0)

Ripple
======

[](#ripple)

Ripple is a small, fast PHP event dispatcher. It lets you build event-driven systems with a simple interface for registering listeners and dispatching events, with priorities, propagation control, wildcard subscriptions, one-time listeners, subscriber objects, and PSR-14 interoperability.

An event dispatcher decouples the parts of an application: components communicate through events instead of direct calls, which makes the code more maintainable and flexible.

Features
--------

[](#features)

- Simple event dispatching and handling
- Priority-based ordering (higher priority fires first; equal priority is last-in-first-out)
- Stoppable propagation (cancelable events)
- Wildcard listeners (`user.*`, `order:*`, separator-agnostic)
- One-time listeners (`once`)
- Subscriber objects that declare their handlers in one place
- [PSR-14](https://www.php-fig.org/psr/psr-14/) compatible adapter
- Instance or static/singleton usage
- Fully unit-tested; ordering and reflection are resolved once at registration, not on every dispatch

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

[](#requirements)

- PHP 8.1+

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

[](#installation)

```
composer require xblabs/ripple
```

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

$dispatcher = new XB\Ripple\Dispatcher();
```

Usage
-----

[](#usage)

### Dispatching events

[](#dispatching-events)

```
use XB\Ripple\Event;
use XB\Ripple\Dispatcher;

$dispatcher = new Dispatcher();

// Dispatch by name; returns an array of listener responses, or null if nothing handled it.
$responses = $dispatcher->dispatch( 'user.login' );

// Dispatch with a target object and params. $user is the target; ['id' => 123] the params.
$dispatcher->dispatch( 'user.login', $user, [ 'id' => 123 ] );

// Or build the Event yourself.
$dispatcher->dispatch( new Event( 'user.login', $user, [ 'id' => 123 ] ) );

// Dispatch and stop at the first listener that returns a truthy value.
$result = $dispatcher->dispatchUntil( 'user.login' );

// Dispatch and return only the first response.
$result = $dispatcher->dispatchGetFirst( 'user.login' );
```

> Event names are opaque strings. No character is special — `user.login`, `user:login` and `user/login` are all just names. (In 1.x a `:` was reserved for the aggregate system; that is no longer the case. See [UPGRADE.md](UPGRADE.md).)

### Listeners

[](#listeners)

A listener is any `callable`. By default it receives the `Event` object.

```
// Closure
$dispatcher->addListener( 'user.login', static function ( Event $e ) {
    // $e->getType(), $e->getTarget(), $e->getParams()
} );

// Class method
$dispatcher->addListener( 'user.login', [ $service, 'onLogin' ] );
```

If a closure declares more than one parameter and its first parameter is **not** named `e` or `event`, Ripple spreads the event's params as positional arguments instead of passing the `Event`. You can force this with the fourth argument to `dispatch()`:

```
$dispatcher->addListener( 'math.add', static function ( $a, $b ) {
    return $a + $b;
} );
$dispatcher->dispatch( 'math.add', null, [ 2, 3 ] ); // => [5]
```

### Priorities

[](#priorities)

Default priority is `0`. Higher priorities fire first; listeners with equal priority fire last-in-first-out.

```
$dispatcher->addListener( 'event.name', $low, -10 );
$dispatcher->addListener( 'event.name', $high, 100 ); // fires first
```

### Stopping propagation

[](#stopping-propagation)

```
$dispatcher->addListener( 'event.name', static function ( Event $e ) {
    $e->stopPropagation(); // remaining listeners are skipped
} );

// Events created with cancelable=false ignore stopPropagation().
$event = new Event( 'event.name', null, null, cancelable: false );
$dispatcher->dispatch( $event );
```

### One-time listeners

[](#one-time-listeners)

`once()` fires a listener at most once, then removes it. (A once listener that is never reached — e.g. propagation was stopped before it — is retained for a future dispatch.)

```
$dispatcher->once( 'app.boot', static fn( Event $e ) => bootstrap() );
$dispatcher->onceWildcard( 'cache.*', static fn( Event $e ) => warmCache( $e->getType() ) );
```

### Wildcard listeners

[](#wildcard-listeners)

Wildcard patterns use `*` to match any run of characters, including your separator of choice.

```
$dispatcher->addWildcardListener( 'user.*', static function ( Event $e ) {
    // receives user.login, user.logout, user.profile.update, ...
    match ( $e->getType() ) {
        'user.login'  => /* ... */ null,
        'user.logout' => /* ... */ null,
        default       => null,
    };
} );
```

Exact and wildcard listeners compose in a single dispatch and are ordered together by priority (on a tie, exact listeners fire before wildcard ones).

### Subscriber objects

[](#subscriber-objects)

A subscriber declares all the events it handles in one place. Keys may contain `*` (registered as a wildcard).

```
use XB\Ripple\Event;
use XB\Ripple\EventSubscriberInterface;

class UserSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            'user.login'  => 'onLogin',                    // method name
            'user.logout' => [ 'onLogout', 100 ],          // method + priority
            'user.*'      => [ [ 'audit', 200 ], [ 'log', -10 ] ], // multiple handlers
        ];
    }

    public function onLogin( Event $e ): void { /* ... */ }
    public function onLogout( Event $e ): void { /* ... */ }
    public function audit( Event $e ): void { /* ... */ }
    public function log( Event $e ): void { /* ... */ }
}

$dispatcher->addSubscriber( new UserSubscriber() );
// $dispatcher->removeSubscriber( $subscriber ); // detaches everything it registered
```

### Managing listeners

[](#managing-listeners)

```
$dispatcher->hasListener( 'user.login' );
$dispatcher->getListenersForEvent( 'user.login' );
$dispatcher->getAllListeners();
$dispatcher->getWildcardListeners();

$dispatcher->removeListener( 'user.login', $listener );
$dispatcher->removeWildcardListener( 'user.*', $listener );
$dispatcher->removeListenersForEvent( 'user.login' );
$dispatcher->removeAllListeners();
```

### Custom event class

[](#custom-event-class)

```
$dispatcher->setEventClass( MyEvent::class ); // must extend XB\Ripple\Event
```

### Static / singleton usage

[](#static--singleton-usage)

`DispatcherStatic` proxies a single shared dispatcher. It can be reset or injected, which is useful in tests.

```
use XB\Ripple\DispatcherStatic;

DispatcherStatic::addListener( 'user.login', $listener );
DispatcherStatic::dispatch( 'user.login' );

DispatcherStatic::setDispatcher( $myDispatcher ); // inject a specific instance
DispatcherStatic::reset();                        // drop the shared instance entirely
```

### PSR-14 interoperability

[](#psr-14-interoperability)

`Event` implements `Psr\EventDispatcher\StoppableEventInterface`. The `Psr14\Psr14Dispatcher` and `Psr14\ListenerProvider`adapters let Ripple act as a PSR-14 `EventDispatcherInterface`, reusing the same listener storage and priority ordering.

```
use XB\Ripple\Dispatcher;
use XB\Ripple\Psr14\Psr14Dispatcher;

$ripple = new Dispatcher();
$ripple->addListener( 'user.login', $listener );

$psr = new Psr14Dispatcher( $ripple );
$event = $psr->dispatch( new XB\Ripple\Event( 'user.login' ) ); // returns the event
```

Note the difference between the two APIs: native Ripple dispatches event **names** and collects listener return values; PSR-14 dispatches an **object** and returns that same object.

Testing
-------

[](#testing)

```
composer install
composer test        # phpunit
composer analyse     # phpstan
composer cs          # php-cs-fixer (dry run)
```

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE) for details.

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1165 days

Total

2

Last Release

2d ago

Major Versions

1.0.0 → 2.0.02026-07-18

PHP version history (2 changes)1.0.0PHP &gt;=8.0

2.0.0PHP &gt;=8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/34e44fd8d3e77d6649fd6bb287724dbe8af826ad12ed9dab84fcb5c7b1987234?d=identicon)[xblabs](/maintainers/xblabs)

---

Tags

phpeventeventspsr-14listenerobserverdispatcherprioritybroadcaster

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/xblabs-ripple/health.svg)

```
[![Health](https://phpackages.com/badges/xblabs-ripple/health.svg)](https://phpackages.com/packages/xblabs-ripple)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
