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

Event Laravel Package

phug/event

phug/event provides a lightweight event emitter and dispatcher for the Phug template engine, letting you register listeners and trigger events around parsing, compilation, and rendering. Simple to integrate and framework-agnostic.

View on GitHub
Deep Wiki
Context7

Getting Started

phug/event is a tiny, dependency-free event emitter (the one Phug's compiler/renderer uses internally). It is not a Laravel replacement for the dispatcher — it is what you reach for when you want decoupled hooks inside a package or a Phug-powered renderer without dragging illuminate/events or symfony/event-dispatcher into something that must also run outside a Laravel app.

composer require phug/event

Where to look first, in order:

  1. src/EventEmitterTrait.php — the whole API surface (on, once, off, trigger, hasListener). This is the file you will actually read; everything else is a thin interface.
  2. src/Event.php / EventInterface — the payload object (getName, getTarget, getParams, setParams).
  3. src/EventEmitterInterface.php — type-hint against this, not the trait, so you can swap implementations later.

First use case — an in-process hook on a class you own:

use Phug\Event\Event;
use Phug\Event\EventEmitterTrait;

class InvoiceImporter
{
    use EventEmitterTrait;

    public function import(string $path): int
    {
        $count = 0;
        foreach ($this->rows($path) as $row) {
            $this->trigger('row.parsed', $row, $count);
            $count++;
        }

        return $count;
    }
}

$importer = new InvoiceImporter();
$importer->on('row.parsed', function (Event $event) {
    Log::debug('row', $event->getParams());
});

If you are here because of Phug itself: the Renderer already uses this emitter, so $renderer->on('renderer.render', ...) works out of the box with no extra wiring.

Implementation Patterns

Payload object, not return values. Listener return values are discarded; the only way to communicate back is a mutable Event payload. Pass an array or a small DTO by reference inside the event, or mutate via setParams():

$emitter->on('markdown.before', function (Event $e) {
    $params = $e->getParams();
    $params['html'] = Str::markdown($params['text']);
    $e->setParams($params);
});
$emitter->trigger('markdown.before', $text);

Order is registration order. There are no priorities (unlike Symfony's dispatcher). Treat registration order as API: register listeners in boot() in a deliberate sequence, and never assume a listener registered by a third party runs before yours.

Bridging into Laravel. The common production pattern is one-directional forwarding in a service provider — Phug events become Laravel events so the rest of your app (queues, broadcasting, Event::fake() in tests) keeps working:

public function boot(): void
{
    $renderer = $this->app->make(Renderer::class);

    $renderer->on('renderer.render', function (Event $event) {
        event(new TemplateRendered($event->getParams()));
    });
}

Bind the Renderer as a singleton in register() so the listener registrations in boot() land on the same instance that renders.

once for bootstrapping. Cache priming, one-shot deprecation warnings, and "first render only" setup are cleaner than a boolean flag:

$renderer->once('renderer.render', fn () => $this->warmCache());

Guard expensive work.

if ($emitter->hasListener('renderer.render')) {
    $event = $this->buildExpensivePayload();
}

Testing. There is no Event::fake() equivalent — spy on the boundary instead:

$emitter->on('row.parsed', $spy = Mockery::mock(\stdClass::class));

or, more simply, push into a captured array in the test and assert counts/order.

Keep it behind your own interface. Given the effectively zero dependent count on Packagist, define interface TemplateHooks { public function onRender(callable $cb): void; } and implement it with phug/event today, Symfony's dispatcher tomorrow. One file of indirection buys you an exit ramp.

Gotchas and Tips

  • Not PSR-14. No EventDispatcherInterface, no stoppable-event contract unless the shipped Event class implements one — check EventInterface before you rely on propagation control. If you need PSR-14 compliance, wrap it; do not advertise the class as PSR-14.
  • Trait property collisions. EventEmitterTrait declares its own storage property. If your class (or a parent) already has a similarly named property, PHP fatals on conflicting definitions. Use composition — an EventEmitter instance behind a getter — when your class already has a busy property set.
  • Closure retention leaks. Listeners stored on a singleton hold $this and everything it references. Under Octane or long-running queue workers this is a slow memory bleed; call off() for your listener in a terminate hook, or scope listeners to per-request instances.
  • Re-entrancy. Triggering the same event name from inside its own listener recurses until the stack dies. Guard with an instance flag or a distinct event name (row.parsed → row.parsed.internal).
  • No container, no autowiring. Listeners are plain callables. Resolve dependencies at registration time inside the service provider (fn ($e) => $this->app->make(Foo::class)->handle($e)), not by expecting the emitter to inject anything.
  • Listeners are synchronous and in-process. Do not put queueable work directly in a listener — forward to Laravel's dispatcher and let ShouldQueue do its job. Same for anything needing DB::transaction() boundaries; the emitter knows nothing about transactions or rollbacks.
  • Don't serialize Event objects. They may carry a target reference (your renderer, your importer); put only scalar arrays on the wire.
  • Debugging: there is no logging or introspection tooling. When a hook "doesn't fire," verify you registered on the same instance (singleton vs. new) before anything else — that is the cause roughly nine times out of ten. eventNames() / listener inspection on the trait is your only visibility.
  • Version discipline. Pin with a caret and read the changelog on upgrade; with no dependents to shake out regressions upstream, you are the integration test.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route