Eigene Events auslösen: Custom Events für andere Module dispatchen
Eigene Events auslösen: Custom Events für andere Module dispatchen
~7 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026
Kapitel 29 bis 34 haben dieses Modul ausschließlich als Zuhörer gezeigt: es reagiert auf Bestellungen, Gutschriften und den Uhrzeiger. Ein Aspekt der Spezifikation - Rewards, Marketing, ein zukünftiges E-Mail-Modul - könnte aber genauso gut selbst wissen wollen, wann sich die Treue-Stufe eines Kunden ändert, ohne dafür PointsCalculator, LoyaltyTierBackend oder ExpirePoints zu kennen. Die Lösung: Mironsoft_Loyalty wird selbst zum Absender eines eigenen Events.
Wo die Lücke tatsächlich ist
LoyaltyTierBackend (Kapitel 26) berechnet loyalty_tier bei jedem Kunden-Speichern automatisch neu - aber weder AwardPointsOnOrderPlaced (Kapitel 30) noch ExpirePoints (Kapitel 33), die diese Neuberechnung indirekt auslösen, erfahren jemals, ob sich die Stufe dabei tatsächlich geändert hat - das Backend Model arbeitet still, ohne Rückkanal. Statt diese Prüfung doppelt in beiden Aufrufern nachzubauen, bekommt LoyaltyTierBackend selbst die Aufgabe: alten und neuen Wert vergleichen und bei einer echten Änderung ein eigenes Event dispatchen.
Namenskonvention für eigene Events
Ein generischer Name wie tier_changed würde mit jedem anderen Modul kollidieren können, das zufällig denselben Begriff verwendet. Die Konvention dieser Serie: immer mit dem vollen Modul-Präfix, analog zu den Config-Pfaden aus Kapitel 7 - hier mironsoft_loyalty_customer_tier_changed.
LoyaltyTierBackend erweitert
<?php
declare(strict_types=1);
namespace Mironsoft\Loyalty\Model\Customer\Attribute\Backend;
use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
use Magento\Framework\Event\ManagerInterface;
use Mironsoft\Loyalty\Model\Config\LoyaltyConfig;
use Mironsoft\Loyalty\Model\Service\PointsCalculator;
/**
* Recalculates loyalty_tier on save (chapter 26) and, new in chapter 35, notifies
* other modules of an actual tier change via a custom event.
*/
class LoyaltyTierBackend extends AbstractBackend
{
public const EVENT_TIER_CHANGED = 'mironsoft_loyalty_customer_tier_changed';
/**
* @param PointsCalculator $pointsCalculator Pure business rule for determining a tier from a point total.
* @param LoyaltyConfig $loyaltyConfig Typed reader for the tier_thresholds configuration value.
* @param ManagerInterface $eventManager Dispatches EVENT_TIER_CHANGED for other modules to observe.
*/
public function __construct(
private readonly PointsCalculator $pointsCalculator,
private readonly LoyaltyConfig $loyaltyConfig,
private readonly ManagerInterface $eventManager
) {
}
/**
* Overwrites loyalty_tier with a freshly calculated value and dispatches
* EVENT_TIER_CHANGED when the recalculated value actually differs from the
* value the entity held before this save. $object deliberately carries no
* native type hint, unchanged from chapter 26's contravariance explanation.
*
* @param \Magento\Framework\DataObject $object The entity currently being saved (a Customer model here).
* @return $this
*/
public function beforeSave($object)
{
$previousTier = $object->getOrigData($this->getAttribute()->getAttributeCode());
$pointsBalance = (int) $object->getData('loyalty_points_balance');
$tier = $this->pointsCalculator->determineTier(
$pointsBalance,
$this->loyaltyConfig->getTierThresholdsJson()
);
$object->setData($this->getAttribute()->getAttributeCode(), $tier);
if ($previousTier !== null && $previousTier !== $tier) {
$this->eventManager->dispatch(self::EVENT_TIER_CHANGED, [
'customer' => $object,
'previous_tier' => $previousTier,
'new_tier' => $tier,
]);
}
return parent::beforeSave($object);
}
}So würde ein anderes Modul zuhören
Ein völlig unabhängiges, hypothetisches Marketing-Modul müsste Mironsoft_Loyalty dafür nicht einmal als Abhängigkeit in module.xml eintragen (auch wenn eine sequence in der Praxis sinnvoll ist, damit die Observer-Reihenfolge - Kapitel 36 - vorhersehbar bleibt) - es kennt nur den Event-Namen als String.
<!-- etc/events.xml eines fremden, hier nur illustrativen Moduls -->
<config>
<event name="mironsoft_loyalty_customer_tier_changed">
<observer name="vendor_marketing_notify_tier_upgrade"
instance="Vendor\Marketing\Observer\NotifyTierUpgrade"/>
</event>
</config>Achtung: beforeSave() ist der einzige Ort, an dem alter und neuer Wert am Attribut günstig verglichen werden können - aber er läuft, bevor die Transaktion wirklich committet ist. Schlägt das eigentliche Speichern danach doch noch fehl, wurde das Event trotzdem bereits dispatcht, obwohl die Tier-Änderung nie in der Datenbank ankam. Anders als bei den Entity-Save-Events in Kapitel 31 gibt es für Backend Models kein Äquivalent zu _save_commit_after - dieser Trade-off ist eine bewusste Einschränkung von Attribut-Backend-Modellen als Dispatch-Ort, nicht ein Versehen.
Tipp: Konstanten wie EVENT_TIER_CHANGED direkt an der dispatchenden Klasse zu deklarieren (statt den String an mehreren Stellen zu wiederholen) verhindert genau die Art von Tippfehler, die bei Event-Namen erst zur Laufzeit auffällt - dieselbe Motivation wie bei den TYPE_*-Konstanten in PointsLedgerInterface (Kapitel 6).
Ein Event kann mehrere Observer haben. Kapitel 36 klärt, in welcher Reihenfolge sie tatsächlich laufen - und was zu tun ist, wenn die Reihenfolge wirklich zählt.