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

Custom-Renderer und Custom-Column-Klassen schreiben

Custom-Renderer und Custom-Column-Klassen schreiben

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

Kapitel 17 hat bereits eine eigene Column-Klasse für die Actions-Spalte gebaut. Dieses Kapitel verallgemeinert das Muster: Wie entscheidet man, ob eine PHP-Column-Klasse reicht oder ob eine eigene JavaScript-Komponente nötig ist - und wie baut man beides sauber?

Zwei Ebenen von Custom Rendering

  • PHP-Column-Klasse (prepareDataSource()) - formt die Rohdaten pro Zeile um, bevor sie an den Browser gehen. Reicht für alles, was sich als reiner Text/HTML-Wert oder als zusätzliches Datenfeld ausdrücken lässt.
  • Eigene JavaScript-Komponente (KnockoutJS-Template + uiComponent) - nötig, sobald im Browser selbst Interaktivität nötig ist (Klick-Handler, dynamisches Nachladen), die über einen einfachen Link mit confirm hinausgeht.

Für die Kundenstimmen reicht die erste Ebene: eine Rating-Spalte, die die Zahl 1-5 als Sternchen-Text darstellt, statt einer nackten Zahl.

Eigene Rating-Spalte

app/code/Mironsoft/Testimonial/Ui/Component/Listing/Column/Rating.php
<?php

declare(strict_types=1);

namespace Mironsoft\Testimonial\Ui\Component\Listing\Column;

use Magento\Framework\Escaper;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;

/**
 * Renders the numeric rating as a star string, e.g. "★★★★☆".
 */
class Rating extends Column
{
    private const MAX_RATING = 5;

    /**
     * @param ContextInterface $context Rendering context.
     * @param UiComponentFactory $uiComponentFactory Factory for nested UI components.
     * @param Escaper $escaper Escapes the rendered star string for safe HTML output.
     * @param array<string, mixed> $components Nested UI components.
     * @param array<string, mixed> $data Additional UI Component data configuration.
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        private readonly Escaper $escaper,
        array $components = [],
        array $data = [],
    ) {
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Replaces the raw rating integer with a rendered star string.
     *
     * @param array<string, mixed> $dataSource Raw grid data source.
     * @return array<string, mixed>
     */
    public function prepareDataSource(array $dataSource): array
    {
        if (!isset($dataSource['data']['items'])) {
            return $dataSource;
        }

        $fieldName = $this->getData('name');

        foreach ($dataSource['data']['items'] as &$item) {
            $rating = max(0, min(self::MAX_RATING, (int) $item[$fieldName]));
            $stars = str_repeat('★', $rating) . str_repeat('☆', self::MAX_RATING - $rating);
            $item[$fieldName] = $this->escaper->escapeHtml($stars);
        }

        return $dataSource;
    }
}
<column name="rating" class="Mironsoft\Testimonial\Ui\Component\Listing\Column\Rating">
    <settings>
        <filter>textRange</filter>
        <label translate="true">Rating</label>
    </settings>
</column>

Achtung: $this->escaper->escapeHtml() ist hier keine Formalität: Die Grid-JavaScript-Templates rendern Spaltenwerte teilweise ungebunden als HTML. Ein Column-Renderer, der Benutzereingaben (nicht bloß berechnete Werte wie hier) unescaped zurückgibt, öffnet eine Stored-XSS-Lücke direkt im Admin-Grid.

Wann reicht PHP nicht mehr?

Ein Beispiel, das eine eigene JavaScript-Komponente bräuchte: ein Klick auf die Sterne-Spalte, der die Bewertung direkt im Grid ohne Reload ändert. Dafür wäre ein eigenes RequireJS-Modul (Erweiterung von Magento_Ui/js/grid/columns/column) samt eigenem KnockoutJS-Template nötig - deutlich mehr Aufwand als der PHP-Renderer oben, und außerhalb des Rahmens dieser Serie. Kapitel 20 zeigt mit Inline-Edit einen fertigen, bereits eingebauten Mittelweg für genau diesen Anwendungsfall.

Die Grenze erneut benannt

Wie bereits in Kapitel 1 erwähnt: Rating hier ist ein klassisches Beispiel dafür, dass UI Components eigene Konventionen erzwingen. Die Klasse muss Column erweitern und prepareDataSource() implementieren - ein ArgumentInterface-ViewModel könnte hier nicht eingesetzt werden. Die reine Formatierungslogik (Zahl zu Sternchen-String) wäre trotzdem leicht in eine eigene, testbare Utility-Methode auslagerbar, wenn sie an mehreren Stellen im Projekt gebraucht würde.