Skip to content

Events & Observers

Events let modules communicate without depending on each other directly. A module dispatches an event; other modules observe it and react. Neither side knows about the other.

Events are PHP classes that extend the Event base class. Dispatch them through the EventDispatcherInterface:

PostService.php
<?php
declare(strict_types=1);
namespace App\Blog\Services;
use Marko\Core\Event\EventDispatcherInterface;
use App\Blog\Events\Post\PostCreated;
readonly class PostService
{
public function __construct(
private EventDispatcherInterface $eventDispatcher,
) {}
public function createPost(string $title, string $body): Post
{
$post = new Post(title: $title, body: $body);
// ... save to database
$this->eventDispatcher->dispatch(new PostCreated(post: $post));
return $post;
}
}

Events extend the Marko\Core\Event\Event base class, which provides propagation stopping:

PostCreated.php
<?php
declare(strict_types=1);
namespace App\Blog\Events\Post;
use App\Blog\Entity\PostInterface;
use Marko\Core\Event\Event;
readonly class PostCreated extends Event
{
public function __construct(
private PostInterface $post,
) {}
public function getPost(): PostInterface
{
return $this->post;
}
}

An observer is a class with the #[Observer] attribute that handles a specific event. The event parameter specifies which event class to listen for:

TrackPostCreation.php
<?php
declare(strict_types=1);
namespace App\Analytics\Observer;
use App\Blog\Events\Post\PostCreated;
use Marko\Core\Attributes\Observer;
#[Observer(event: PostCreated::class)]
class TrackPostCreation
{
public function handle(PostCreated $event): void
{
// Send to analytics, update counters, notify admins, etc.
$postTitle = $event->getPost()->title;
// ...
}
}

Observers are discovered automatically from module src/ directories — no manual registration needed.

When multiple observers listen to the same event, they run by priority (higher values first). Use the priority parameter to control order:

#[Observer(event: PostCreated::class, priority: 10)]
class HighPriorityObserver
{
public function handle(PostCreated $event): void { /* ... */ }
}
#[Observer(event: PostCreated::class, priority: 0)]
class DefaultPriorityObserver
{
public function handle(PostCreated $event): void { /* ... */ }
}

Marko packages dispatch events at meaningful points:

EventWhen
LoginEventUser successfully logs in
LogoutEventUser logs out
FailedLoginEventLogin attempt fails
PasswordResetEventPassword is reset
EventWhen
PostCreatedNew post is created
PostUpdatedPost is modified
PostDeletedPost is deleted
CommentCreatedNew comment is added

Events are the right tool when:

  • The action and reaction belong to different modules
  • You want zero coupling between the modules
  • Multiple reactions might happen for the same action
  • The reaction is optional (the system works fine without any observers)

If you need to modify a method’s behavior directly, use Plugins instead.