XOOPS Event System
2.5.x: Preloads 4.0.x: PSR-14
Overview
Section titled “Overview”The XOOPS event system enables loose coupling between modules through an observer pattern. Components can emit events that other parts of the system can listen to and respond to.
Event Types
Section titled “Event Types”Core Events
Section titled “Core Events”| Event | Trigger Point |
|---|---|
core.header.start | Before header processing |
core.header.end | After header processing |
core.footer.start | Before footer rendering |
core.footer.end | After footer rendering |
core.exception | When exception occurs |
Module Lifecycle Events
Section titled “Module Lifecycle Events”| Event | Trigger Point |
|---|---|
module.install | After module installation |
module.update | After module update |
module.uninstall | Before module removal |
module.activate | When module activated |
module.deactivate | When module deactivated |
User Events
Section titled “User Events”| Event | Trigger Point |
|---|---|
user.login | After successful login |
user.logout | After logout |
user.register | After registration |
user.delete | Before user deletion |
Preload System (Legacy)
Section titled “Preload System (Legacy)”Creating a Preload
Section titled “Creating a Preload”<?phpnamespace XoopsModules\MyModule;
use Xmf\Module\Helper\AbstractHelper;
final class Preload extends AbstractHelper{ public function eventCoreHeaderStart(array $args): void { // Runs on every page before header }
public function eventCoreFooterStart(array $args): void { // Runs before footer renders }
public function eventUserLogin(array $args): void { $userId = $args['userid']; // Handle login event }
public function eventCoreException(array $args): void { $exception = $args['exception']; // Log or handle exception }}Event Method Naming
Section titled “Event Method Naming”event{Category}{Action}
Examples:- eventCoreHeaderStart- eventUserLogin- eventModuleNewsArticleCreatePSR-14 Event Dispatcher (XOOPS 4.0)
Section titled “PSR-14 Event Dispatcher (XOOPS 4.0)”Event Class
Section titled “Event Class”<?php
declare(strict_types=1);
namespace XoopsModules\MyModule\Event;
final class ArticleCreatedEvent{ public function __construct( public readonly int $articleId, public readonly int $authorId, public readonly string $title, public readonly \DateTimeImmutable $createdAt ) {}}Dispatching Events
Section titled “Dispatching Events”use Psr\EventDispatcher\EventDispatcherInterface;
final class ArticleService{ public function __construct( private readonly ArticleRepository $repository, private readonly EventDispatcherInterface $dispatcher ) {}
public function create(CreateArticleDTO $dto): Article { $article = Article::create($dto); $this->repository->save($article);
// Dispatch event $this->dispatcher->dispatch(new ArticleCreatedEvent( articleId: $article->getId(), authorId: $article->getAuthorId(), title: $article->getTitle(), createdAt: new \DateTimeImmutable() ));
return $article; }}Event Listener
Section titled “Event Listener”<?php
declare(strict_types=1);
namespace XoopsModules\MyModule\Listener;
use XoopsModules\MyModule\Event\ArticleCreatedEvent;
final class SendNotificationOnArticleCreated{ public function __construct( private readonly NotificationService $notifications ) {}
public function __invoke(ArticleCreatedEvent $event): void { $this->notifications->notifySubscribers( 'new_article', [ 'article_id' => $event->articleId, 'title' => $event->title, ] ); }}Registering Listeners
Section titled “Registering Listeners”return [ ArticleCreatedEvent::class => [ SendNotificationOnArticleCreated::class, UpdateSearchIndex::class, ClearArticleCache::class, ],
ArticleUpdatedEvent::class => [ UpdateSearchIndex::class, ClearArticleCache::class, ],
ArticleDeletedEvent::class => [ RemoveFromSearchIndex::class, ClearArticleCache::class, ],];Stoppable Events
Section titled “Stoppable Events”use Psr\EventDispatcher\StoppableEventInterface;
final class ArticlePublishingEvent implements StoppableEventInterface{ private bool $propagationStopped = false; private ?string $rejectionReason = null;
public function __construct( public readonly Article $article ) {}
public function isPropagationStopped(): bool { return $this->propagationStopped; }
public function reject(string $reason): void { $this->propagationStopped = true; $this->rejectionReason = $reason; }
public function getRejectionReason(): ?string { return $this->rejectionReason; }}
// Listener can stop propagationfinal class ContentModerationListener{ public function __invoke(ArticlePublishingEvent $event): void { if ($this->containsProhibitedContent($event->article)) { $event->reject('Content violates community guidelines'); } }}Best Practices
Section titled “Best Practices”- Immutable Events - Events should be read-only
- Specific Events - Create specific events, not generic ones
- Async When Possible - Use queues for slow operations
- No Side Effects in Dispatch - Dispatch should be quick
- Document Events - List available events for module users
Related Documentation
Section titled “Related Documentation”- Module-Development - Module development
- Event-System-Guide - PSR-14 guide
- Hooks-Events - Legacy hooks
- Events-and-Hooks - Event examples