Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Signal Slot Laravel Package

zetacomponents/signal-slot

zetacomponents/signal-slot provides a lightweight signal/slot implementation for PHP. Connect callbacks to named signals and emit them to decouple components, enabling clean event-driven code without heavy dependencies.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require zetacomponents/signal-slot

There is no Laravel auto-discovery, no service provider, and no facade — this is a standalone eZ/Zeta Components library, not a Laravel package. You wire it up yourself.

Look here first

  1. src/ezcSignalCollection.php — the whole API surface you will actually use (connect(), disconnect(), emit(), isConnected()).
  2. src/ezcSignalStaticConnections.php — global/static wiring, the part most likely to bite you.
  3. src/ezcSignalCollectionOptions.php — the small options bag passed to the constructor.
  4. tests/ — the fastest way to learn exact call signatures because the documentation is thin and dated.

First use case

Decouple one class from its listeners without dragging in Laravel's dispatcher (useful when porting legacy eZ Publish / Zeta code into a Laravel app):

use ezcSignalCollection;

$signals = new ezcSignalCollection();
$signals->connect('order.placed', fn($order) => logger("Order {$order->id} placed"));
$signals->emit('order.placed', $order);

That is the entire mental model: a named signal, a callable slot, and emit() to fire it. Think "Qt signals/slots in PHP", which is exactly what it was modeled on.

Register it in Laravel so the rest of your app can resolve it:

// app/Providers/SignalSlotServiceProvider.php
public function register(): void
{
    $this->app->singleton(ezcSignalCollection::class, fn() => new ezcSignalCollection('app'));
}

Bind it as a singleton only if you want one global bus; bind it scoped() or as a plain bind() if you want isolated signal scopes per request/job.

Implementation Patterns

Per-service signal collections (the pattern the library actually wants)

Do not create one god bus. Give each domain object (or small domain) its own collection and expose it:

class CheckoutService
{
    public function __construct(private ezcSignalCollection $signals) {}

    public function place(Order $order): void
    {
        // ... work ...
        $this->signals->emit('checkout.placed', $order);
    }
}

Register listeners in a service provider, or co-locate them where the service is constructed.

Closures vs. [$object, 'method'] callables

Both work and are stored as-is:

$signals->connect('user.registered', [$listener, 'onRegistered']);
$signals->connect('user.registered', function ($user) { /* ... */ });

Prefer [$object, 'method'] when you need disconnect() later — closures cannot be reliably compared, so disconnecting an anonymous function is fragile.

Static/cross-cutting connections

ezcSignalStaticConnections is a process-wide singleton. It is the mechanism for listeners that must apply regardless of who instantiated the collection:

use ezcSignalStaticConnections;

ezcSignalStaticConnections::getInstance()
    ->connect('CheckoutService', 'checkout.placed', $auditLogger);

The first argument is the signal collection name you passed to the ezcSignalCollection constructor. If you construct with new ezcSignalCollection('CheckoutService'), those static connections are merged in at construction — which is why naming your collections matters. Skip the name and your static connections will silently never fire.

Laravel integration pattern: bridge to native events

The pragmatic day-to-day move is to make SignalSlot emit into Laravel's dispatcher (or vice versa), so you get one integration point instead of two systems:

$this->app->singleton(ezcSignalCollection::class, function ($app) {
    $signals = new ezcSignalCollection('app');
    $signals->connect('user.registered', fn($user) =>
        $app['events']->dispatch(new UserRegistered($user))
    );
    return $signals;
});

Now your team keeps using Event::listen()/queued listeners, and only the legacy-facing code touches ezcSignalCollection.

Workflow for porting legacy eZ code

  1. Find every ezcSignalStaticConnections::getInstance()->connect(...) in the legacy app; those are your real integration points.
  2. Reproduce the collection names exactly — they are the hidden contract between emitter and static listeners.
  3. In Laravel, register a single service provider that seeds those static connections, and bridge each to a Laravel event or job.
  4. Only after the ports are done, replace the emitter call sites with native Laravel events.

Testing pattern

Instantiate a fresh ezcSignalCollection per test and inject it. Avoid the static singleton in tests — it leaks across test cases (see Gotchas).

Gotchas and Tips

The static singleton is global mutable state.

ezcSignalStaticConnections::getInstance() persists for the whole PHP process. In a long-running worker (Octane, queues, Pest/PHPUnit with process reuse) listeners accumulate across "requests". Reset it between tests, or avoid it entirely and use instance connections.

Constructor name is load-bearing.

new ezcSignalCollection() with no argument means static connections for that instance are not applied (or applied under an empty name, depending on version). Always pass an explicit name if you intend to use static connections — silently missing listeners is the #1 debugging trap here.

No PSR-14, no Laravel, no $event object convention.

Listener signatures are whatever emit() passes — a bare argument list, not an event object. That means no Event::fake(), no listener discovery, no queueing, no ShouldQueue, no wildcard listening, and no automatic model-event integration. You are getting ~200 lines of callback registry; weigh that against Illuminate\Events\Dispatcher and Symfony's EventDispatcher, both of which you already have.

License metadata reads NOASSERTION.

Zeta Components was historically Apache-2.0, but the NOASSERTION tag means Composer/your SBOM tool cannot confirm it. Read LICENSE in the package before shipping to production, and check with whoever owns compliance.

It is effectively an abandoned package.

Three stars, zero dependents, no recent releases. Assume no upstream fixes. Budget for vendoring or forking if a PHP minor version breaks it, and check it against your target PHP version before committing — Zeta-era code predates strict types and modern deprecations, so expect notices on PHP 8.x.

Exception behavior on emit().

Slots are called synchronously in connection order; an exception thrown in one slot aborts the remaining slots. There is no try/catch wrapping, no async, no error isolation. Wrap your own listeners if a failure must not kill the emit loop.

Disconnecting closures is unreliable.

disconnect('signal', $closure) compares by identity and generally will not match what you registered. Use method arrays or a named invokable object you can hold a reference to.

Signal name typos fail silently.

connect('user.registerd', ...) succeeds; emit('user.registered', ...) just fires nothing (and returns falsy). Centralize signal names as constants on the emitting class — this is the single highest-value habit with this library.

Extension point if you need one.

Subclass ezcSignalCollection to add logging in emit() or to enforce a signal-name whitelist. There is no middleware/interceptor hook, so subclassing is the supported extension route. Do not subclass the static connections class — the singleton will defeat your override.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast