phine/observer
Phine Observer provides a clean, flexible observer implementation for Laravel Eloquent models. Hook into events like creating, updating, and deleting with reusable observer classes, keeping side effects out of your models and your code organized.
phine/observer is a single-purpose, MIT-licensed implementation of the GoF observer pattern, archived by its author and sitting at 5 stars with zero dependents. Treat it as source code to read and vendor, not as a dependency to adopt for the long haul.
Minimal steps:
composer require phine/observer
Because the repo is archived, prefer pinning an exact version or copying src/ into your own namespace — you will not get security patches or PHP-version compatibility fixes.src/ and read the observer/subject contracts before writing anything. They are the entire API surface: an observer registration method (attach/detach), a notification method (notify/update), and a payload parameter. There is no container integration, no priority queue, no configuration.// Illustrative of the typical shape — verify names against src/
$subject = new SomeObservable();
$subject->attach(new SendWelcomeEmail());
$subject->attach(new WriteAuditLog());
$subject->notify($user); // each observer's update() runs in registration order
If you are in Laravel, pause here: Laravel's "observers" are Eloquent model observers and are a completely different concept. This package is the generic GoF pattern. Do not mix the two vocabularies in one codebase without a comment explaining which is which.
Implement the observer interface per concern. One class per reaction, not one "GodObserver" that switches on event type. This is the only thing that makes the pattern pay off.
final class SendWelcomeEmail implements ObserverInterface
{
public function __construct(private Mailer $mailer) {}
public function update($subject, $arguments = null): void
{
$this->mailer->sendWelcome($arguments);
}
}
Constructor injection, not service location. The library knows nothing about DI containers. Build your observer list in a service provider or a small factory, resolve observers from the container there, and attach them to the subject. This keeps the subject free of framework coupling and makes the whole thing testable with plain doubles.
Attach at composition time, notify at runtime. Register observers once during boot (service provider, bootstrap script, module init) and only call the notify method inside business logic. Don't attach inside request handlers — you'll leak observers across a long-running worker (Octane, RoadRunner, queue workers).
Payload discipline. The notify signature carries a single arguments value. Pass an immutable DTO or an associative array with a stable shape, never a whole Eloquent model you might mutate. Observers that both read and write the payload create invisible ordering dependencies.
Wrap it behind your own interface. Define EventDispatcher-ish contract in your domain, implement it with phine/observer today, swap it for Symfony's EventDispatcher or Laravel's dispatcher tomorrow. Given the archived status, this is the single most important pattern: keep the third-party type out of your public signatures.
Test as a plain unit. Attach a spy observer, invoke the subject, assert the spy recorded the call and payload. No framework bootstrapping required — that's the one genuine advantage this library has over heavier event dispatchers.
src/ for mixed-heavy signatures and any dynamic property access before enabling E_ALL or strict deprecation handling — dynamic properties are fatal-adjacent in modern PHP.app/Support/Observer if you use it.Event::dispatch() + listeners gives you priority ordering, queued listeners, wildcard matching, and Event::fake() in tests. Reach for this package only if you specifically want a dependency-free, framework-agnostic subject/observer pair inside a library you distribute.finally or scope the subject per job. This is the most likely production bug you'll hit.var_dump/dd the attached list, or add a temporary spl_object_id-keyed debug map in your wrapper. If you cannot see who is attached, add a getObservers() accessor in your wrapper class rather than patching the vendor code.How can I help you explore Laravel packages today?