Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

System/Config/Setting: eine eigene Konfigurationsseite für das Treuepunkte-Programm

System/Config/Setting: eine eigene Konfigurationsseite für das Treuepunkte-Programm

~7 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026

Vier Werte aus der Spezifikation - aktiviert oder nicht, Punkte pro Euro, Ablaufzeit in Monaten, Tier-Schwellenwerte - brauchen eine Oberfläche, über die ein Shop-Betreiber sie ohne Codeänderung anpassen kann. Magentos System/Config-Framework deklariert das komplett über XML: system.xml für die Formularstruktur, config.xml für Standardwerte, acl.xml für die Berechtigung.

acl.xml: Berechtigungen für Menü und Konfiguration

Zwei ACL-Ressourcen werden gebraucht: Mironsoft_Loyalty::loyalty als übergeordnete Ressource, unter der spätere Blöcke ihre eigenen Admin-Berechtigungen einhängen (zum Beispiel das Prämien-Grid aus Kapitel 16), und Mironsoft_Loyalty::config_section speziell für diese Konfigurationsseite unter Stores > Configuration.

app/code/Mironsoft/Loyalty/etc/acl.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="Mironsoft_Loyalty::loyalty" title="Loyalty &amp; Rewards" sortOrder="50"/>
            </resource>
            <resource id="Magento_Backend::stores">
                <resource id="Magento_Backend::stores_settings">
                    <resource id="Magento_Config::config">
                        <resource id="Mironsoft_Loyalty::config_section"
                                  title="Loyalty &amp; Rewards Configuration"/>
                    </resource>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

etc/adminhtml/system.xml

Jedes Feld bekommt showInDefault/showInWebsite/showInStore entsprechend der in der Spezifikation festgelegten Scope: enabled und points_per_euro sind auf Website-Ebene einstellbar, points_expiry_months und tier_thresholds bewusst nur auf Default-Ebene - eine unterschiedliche Ablaufzeit pro Website würde die Ledger-Auswertung in Kapitel 33 unnötig verkomplizieren.

app/code/Mironsoft/Loyalty/etc/adminhtml/system.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="mironsoft" translate="label" sortOrder="300">
            <label>Mironsoft</label>
        </tab>
        <section id="mironsoft_loyalty" translate="label" sortOrder="200"
                 showInDefault="1" showInWebsite="1" showInStore="0">
            <tab>mironsoft</tab>
            <label>Loyalty &amp; Rewards</label>
            <resource>Mironsoft_Loyalty::config_section</resource>
            <group id="general" translate="label" sortOrder="10"
                   showInDefault="1" showInWebsite="1" showInStore="0">
                <label>General Settings</label>
                <field id="enabled" translate="label" type="select" sortOrder="10"
                       showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Enable Loyalty Program</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="points_per_euro" translate="label" type="text" sortOrder="20"
                       showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Points per Euro Spent</label>
                    <validate>validate-number validate-greater-than-zero</validate>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                </field>
                <field id="points_expiry_months" translate="label" type="text" sortOrder="30"
                       showInDefault="1" showInWebsite="0" showInStore="0">
                    <label>Points Expiry (Months)</label>
                    <validate>validate-digits validate-greater-than-zero</validate>
                </field>
                <field id="tier_thresholds" translate="label,comment" type="textarea" sortOrder="40"
                       showInDefault="1" showInWebsite="0" showInStore="0">
                    <label>Tier Thresholds (JSON)</label>
                    <comment>JSON object with the point totals required to reach the
                        "silver" and "gold" tier, e.g. {"silver":500,"gold":2000}.</comment>
                </field>
            </group>
        </section>
    </system>
</config>

config.xml: Standardwerte

app/code/Mironsoft/Loyalty/etc/config.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <mironsoft_loyalty>
            <general>
                <enabled>0</enabled>
                <points_per_euro>1.0000</points_per_euro>
                <points_expiry_months>12</points_expiry_months>
                <tier_thresholds>{"silver":500,"gold":2000}</tier_thresholds>
            </general>
        </mironsoft_loyalty>
    </default>
</config>

Achtung: config.xml setzt einen sicheren Standard: das Programm ist enabled = 0, solange niemand es aktiv einschaltet. Ein Modul, das sich standardmäßig selbst aktiviert, überrascht Shop-Betreiber mit unerwartetem Verhalten direkt nach der Installation - ein häufiger, vermeidbarer Fehler bei eigenen Modulen.

Ein typsicherer Config-Reader statt roher ScopeConfigInterface-Aufrufe

ScopeConfigInterface::getValue() direkt in Observern, Controllern oder dem Konsolenbefehl zu verstreuen, bedeutet, den Config-Pfad als Zeichenkette an mehreren Stellen zu wiederholen - ein Tippfehler fällt dann erst zur Laufzeit auf. LoyaltyConfig kapselt alle vier Pfade an einer einzigen Stelle.

app/code/Mironsoft/Loyalty/Model/Config/LoyaltyConfig.php
<?php

declare(strict_types=1);

namespace Mironsoft\Loyalty\Model\Config;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;

/**
 * Typed reader for the mironsoft_loyalty/* configuration values.
 */
class LoyaltyConfig
{
    private const XML_PATH_ENABLED = 'mironsoft_loyalty/general/enabled';
    private const XML_PATH_POINTS_PER_EURO = 'mironsoft_loyalty/general/points_per_euro';
    private const XML_PATH_POINTS_EXPIRY_MONTHS = 'mironsoft_loyalty/general/points_expiry_months';
    private const XML_PATH_TIER_THRESHOLDS = 'mironsoft_loyalty/general/tier_thresholds';

    /**
     * @param ScopeConfigInterface $scopeConfig Reads store configuration values.
     */
    public function __construct(
        private readonly ScopeConfigInterface $scopeConfig,
    ) {
    }

    /**
     * @param int|null $websiteId Website scope, null falls back to the default scope.
     * @return bool
     */
    public function isEnabled(?int $websiteId = null): bool
    {
        return $this->scopeConfig->isSetFlag(
            self::XML_PATH_ENABLED,
            ScopeInterface::SCOPE_WEBSITE,
            $websiteId
        );
    }

    /**
     * @param int|null $websiteId Website scope, null falls back to the default scope.
     * @return float
     */
    public function getPointsPerEuro(?int $websiteId = null): float
    {
        return (float) $this->scopeConfig->getValue(
            self::XML_PATH_POINTS_PER_EURO,
            ScopeInterface::SCOPE_WEBSITE,
            $websiteId
        );
    }

    /**
     * @return int
     */
    public function getPointsExpiryMonths(): int
    {
        return (int) $this->scopeConfig->getValue(self::XML_PATH_POINTS_EXPIRY_MONTHS);
    }

    /**
     * @return string
     */
    public function getTierThresholdsJson(): string
    {
        return (string) $this->scopeConfig->getValue(self::XML_PATH_TIER_THRESHOLDS);
    }
}

Tipp: getTierThresholdsJson() liefert das JSON unverändert als Zeichenkette zurück und überlässt das Decodieren PointsCalculator::determineTier() (Kapitel 5) - LoyaltyConfig kennt bewusst keine Geschäftslogik, nur Konfigurationswerte. Diese Trennung macht beide Klassen unabhängig voneinander testbar.

Mit Konfiguration und ACL an Ort und Stelle folgt in Kapitel 8 der letzte Infrastruktur-Baustein von Block 1: ein eigener Cache-Typ.