Erweiterbarkeit: wie andere Entwickler das Modul erweitern können
Erweiterbarkeit: wie andere Entwickler das Modul erweitern können
~8 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026
Ein Modul, das nur unter größtem Aufwand erweiterbar ist, hat sein Ziel als wiederverwendbare Grundlage verfehlt - unabhängig davon, wie sauber sein eigener Code aussieht. Dieses Kapitel dreht die Perspektive um: nicht mehr "wie baut man dieses Modul", sondern "wie baut ein anderes Team eine eigene Erweiterung darauf, ohne Mironsoft_Loyalty selbst anzufassen".
Die Service Contracts als Erweiterungsfläche
Weil praktisch jede Kernaktion über ein Interface läuft - PointsLedgerRepositoryInterface (Kapitel 6), RewardRepositoryInterface (Kapitel 79), RewardRedemptionManagementInterface (Kapitel 81) - kann ein fremdes Modul an jeder dieser Stellen plugin-fähig andocken, genau wie die drei Lern-Plugins aus Kapitel 38 es an getListByCustomerId() vorführen. Keine dieser Kern-Klassen ist final markiert (im Gegensatz zur fiktiven ErpSync\CorporateRewards\Model\MultiplierResolver aus Kapitel 41, die absichtlich als Gegenbeispiel diente) - Drittanbieter-Code darf sich frei mit Plugins einklinken.
declare(strict_types=1);
namespace Vendor\LoyaltyReviews\Plugin;
use Mironsoft\Loyalty\Api\PointsLedgerRepositoryInterface;
use Mironsoft\Loyalty\Api\Data\PointsLedgerInterface;
/**
* Extends the loyalty ledger read path with a review-bonus hint - a purely
* illustrative third-party plugin, no code change inside Mironsoft_Loyalty itself.
*/
final class HighlightReviewBonusPlugin
{
/**
* Adds a human-readable marker to review-bonus ledger entries after they are
* loaded, without touching the underlying repository implementation.
*
* @param PointsLedgerRepositoryInterface $subject The intercepted repository.
* @param PointsLedgerInterface[] $result The original ledger entries.
* @param int $customerId The customer whose ledger was requested.
* @return PointsLedgerInterface[] The (unmodified) list, side effects only.
*/
public function afterGetListByCustomerId(
PointsLedgerRepositoryInterface $subject,
array $result,
int $customerId
): array {
return $result;
}
}Das eigene Custom Event als Erweiterungspunkt
LoyaltyTierBackend::EVENT_TIER_CHANGED (Kapitel 35) ist der einzige selbst dispatchte Event dieses Moduls - und genau deshalb der kanonische Erweiterungspunkt für alles, was auf eine Tier-Änderung reagieren soll, ohne die Backend-Klasse selbst zu ändern. Ein fiktives Marketing-Modul könnte darauf zum Beispiel eine Glückwunsch-E-Mail auslösen:
declare(strict_types=1);
namespace Vendor\LoyaltyMarketing\Observer;
use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Event\ObserverInterface;
use Psr\Log\LoggerInterface;
/**
* Reacts to the loyalty module's own tier-change event to trigger a marketing
* action - registered externally in etc/events.xml on
* "mironsoft_loyalty_customer_tier_changed" (chapter 35), no change to
* Mironsoft_Loyalty required.
*/
final class SendTierUpgradeCongratulationsObserver implements ObserverInterface
{
/**
* Initializes the observer with a logger dependency.
*
* @param LoggerInterface $logger Logs the congratulations trigger for auditing.
*/
public function __construct(
private readonly LoggerInterface $logger,
) {
}
/**
* Reads the event data payload (customer, previous_tier, new_tier, chapter 35)
* and triggers the actual marketing action.
*
* @param EventObserver $observer The dispatched event wrapper.
* @return void
*/
public function execute(EventObserver $observer): void
{
$newTier = (string) $observer->getEvent()->getData('new_tier');
$this->logger->info("Customer upgraded to tier: {$newTier}");
}
}<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="mironsoft_loyalty_customer_tier_changed">
<observer name="vendor_loyalty_marketing_send_tier_upgrade_congratulations"
instance="Vendor\LoyaltyMarketing\Observer\SendTierUpgradeCongratulationsObserver"/>
</event>
</config>Bewährte Registrierungsmuster zum Nachbauen
Drei Registrierungs-Muster aus dieser Serie eignen sich unverändert als Vorlage für eine eigene, unabhängige "Sub-Erweiterung" desselben Programms - ohne irgendeine Kernklasse zu ändern:
product_types.xml(Kapitel 72): Muster für einen weiteren, ganz eigenen Produkttyp - etwa ein "Punkte-Gutschein"-Produkttyp eines Drittanbieters, analog zuPointsPackageaufgebaut.widget.xml(Kapitel 56): Muster für ein weiteres CMS-Widget, das genauso wiePointsBalanceWidgetein bestehendes ViewModel wiederverwendet, statt eigene Geschäftslogik zu duplizieren.- Zahlungsart-Registrierung über
payment/*/model(Kapitel 62): Muster für eine weitere, eigene Zahlungsart, die sich - wie die Punkte-Zahlungsart selbst - in denselben Checkout einreiht, ohne den Core-Checkout zu verändern.
Die Preference-Ausnahme verstehen, nicht nachahmen
CompanyMultiplierPreference (Kapitel 41) zeigt bewusst den Sonderfall, in dem eine Erweiterung keine Plugin-Option hat - weil das fiktive Ziel ErpSync\CorporateRewards\Model\MultiplierResolver selbst final ist. Kein einziges Ziel innerhalb von Mironsoft_Loyalty selbst ist so markiert (mit Ausnahme der zustandslosen Utility-Klasse PointsFormatter, Kapitel 44, die aus genau diesem Grund keine Instanzmethoden zum Überschreiben anbietet) - Drittanbieter-Code sollte deshalb für praktisch jede Erweiterung dieses Moduls mit einem Plugin auskommen, nicht mit einer Preference.
Tipp: Der neue Configuration Type aus Kapitel 88 ist ebenfalls ein Erweiterungspunkt: jedes fremde Modul kann eigene Werte über dasselbe app/etc/loyalty_flags.php-Muster registrieren, solange es denselben ConfigTypeInterface-Vertrag respektiert - ganz ohne Datenbankzugriff, rein deploy-gesteuert.
So viel Erweiterbarkeit hilft wenig, wenn im Ernstfall niemand weiß, wo im System etwas kaputtgeht - Kapitel 103 liefert genau dafür den Troubleshooting-Leitfaden für das Gesamtprojekt.