Helper-Klassen: wann sie trotz ViewModel-Präferenz noch sinnvoll sind
Helper-Klassen: wann sie trotz ViewModel-Präferenz noch sinnvoll sind
~6 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026
CLAUDE.md ist eindeutig: ViewModels (ArgumentInterface) statt Block-Klassen für alles, was ein Template betrifft. Kein Kapitel dieser Serie hat dieser Regel bisher widersprochen. Dieses letzte Kapitel von Block 5 klärt trotzdem ehrlich, wo die klassische Magento-Helper-Klasse (extends \Magento\Framework\App\Helper\AbstractHelper) noch ihren Platz hat - nicht als Rückfall in alte Gewohnheiten, sondern für zwei eng begrenzte Kontexte, die ein ViewModel schlicht nicht abdeckt.
Was ein Helper historisch war
AbstractHelper stammt aus der Luma-/Block-Ära: in einem .phtml-Template war $this->helper(SomeHelper::class)->method() der Standardweg, Logik aus dem Template auszulagern. Hyvä nutzt dieses Muster in Storefront-Templates bewusst nicht - es gibt in diesem Theme kein blockbasiertes $this->helper(), und CLAUDE.md schließt Luma, Knockout.js und UI-Components ohnehin aus. Für alles, was ein Hyvä-Storefront-Template braucht, bleibt das ViewModel der richtige, einzige Baustein.
Die zwei legitimen Ausnahmen
1. Kontexte außerhalb des Hyvä-Storefronts
Transaktions-E-Mails (Magento\Email\Model\Template) werden auch in einem vollständigen Hyvä-Shop über eine eigene, von Hyvä unabhängige Template-Engine gerendert - dasselbe gilt für viele Admin-Bereiche. Eine Helper-Klasse, die ausschließlich für die Bestellbestätigungs-Mail eine formatierte Punkte-Zusammenfassung liefert, konkurriert nicht mit der ViewModel-Regel - sie ist schlicht das richtige Werkzeug für einen Kontext, den ViewModels gar nicht adressieren.
<?php
declare(strict_types=1);
namespace Mironsoft\Loyalty\Helper;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Mironsoft\Loyalty\Model\Config\LoyaltyConfig;
use Mironsoft\Loyalty\Model\Util\PointsFormatter;
/**
* Formats loyalty points text for transactional email templates, which
* render outside the Hyva storefront and therefore outside ViewModel scope.
*/
class EmailPointsHelper extends AbstractHelper
{
/**
* @param Context $context Framework helper context, required by AbstractHelper.
* @param LoyaltyConfig $loyaltyConfig Reads the points-per-euro rate for display context.
*/
public function __construct(
Context $context,
private readonly LoyaltyConfig $loyaltyConfig,
) {
parent::__construct($context);
}
/**
* Builds the "you earned N points" sentence used in the order confirmation email.
*
* @param int $pointsEarned Points earned on this order.
* @return string
*/
public function getEarnedPointsText(int $pointsEarned): string
{
if ($pointsEarned <= 0 || !$this->loyaltyConfig->isEnabled()) {
return '';
}
return (string) __('You earned %1 points with this order.', PointsFormatter::formatPoints($pointsEarned));
}
}2. Zustandslose Utility-Funktionen ohne Magento-Objektgraph
Eine reine Formatierungsfunktion ohne Konfigurationszugriff, ohne Datenbank, ohne jede Abhängigkeit braucht keine Dependency Injection - eine schlichte, final markierte Klasse mit statischen Methoden ist hier legitim und spart unnötigen Objektaufbau, etwa im Konsolenbefehl aus Kapitel 9, in der E-Mail-Helper-Klasse oben und später in PDF-Ausgaben.
<?php
declare(strict_types=1);
namespace Mironsoft\Loyalty\Model\Util;
/**
* Stateless number-formatting utility for loyalty points. Deliberately final -
* a static utility class has no legitimate reason to be subclassed (see the
* final-method discussion in chapters 40-41 for the mirror-image argument).
*/
final class PointsFormatter
{
/**
* Formats a point count with a thousands separator, e.g. "1.250".
*
* @param int $points Point count to format.
* @return string
*/
public static function formatPoints(int $points): string
{
return \number_format($points, 0, ',', '.');
}
}Faustregel
- Logik für ein Hyvä-Storefront-Template? → ViewModel (
ArgumentInterface), immer. - Domänenlogik mit Zustand und/oder mehreren Abhängigkeiten? → Service-Klasse nach dem Muster von
PointsCalculator(Kapitel 5). - Reine, zustandslose Funktion ganz ohne Magento-Objektgraph? → statische Utility-Klasse zulässig, aber bewusst klein und
finalgehalten. - E-Mail-Templates, Admin-Legacy-Kontext oder eine von einem Drittanbieter-Modul erwartete
AbstractHelper-Instanz? → klassischer Helper, dokumentiert als bewusste Ausnahme außerhalb des Storefronts.
Tipp: Block 5 ist damit abgeschlossen: zwei Plugins (Kapitel 38/39), eine sauber begründete Preference (Kapitel 41) samt ihrer Risiken (Kapitel 42), Plugin-Reihenfolge und -Konflikte (Kapitel 43) und die letzte offene Frage zu Helper-Klassen. Block 6 wendet sich ab Kapitel 45 dem Frontend zu: eigene Controller, ein eigener Router und die erste sichtbare Seite dieses Moduls.
Achtung: Eine Helper- oder Utility-Klasse, die über die Zeit unbemerkt Konfigurationszugriffe, Datenbankaufrufe oder sonstigen Zustand ansammelt, verwandelt sich zurück in genau das globale Mage_Core_Helper_Data-Antimuster, das Magento 1 berüchtigt gemacht hat - und das die Trennung von ViewModel und Service-Klasse in dieser Serie von Anfang an verhindern soll. Sobald eine "Utility"-Klasse eine Abhängigkeit braucht, ist sie keine Utility-Klasse mehr, sondern ein Kandidat für eine reguläre, injizierte Service-Klasse.