Magento 2 · Composite Pattern

Composite Pattern
in Magento 2

Das Composite Pattern macht einzelne Objekte und ganze Baumstrukturen über dieselbe Schnittstelle nutzbar. In Magento 2 sieht man das besonders bei Preisregeln, Sales Rule Conditions, Layout-Containern und Konfigurationsbäumen.

12 Min. Lesezeit PHP 8.4 Magento 2.4.8

1. Was ist das Composite Pattern?

Das Composite Pattern Magento 2 beschreibt ein Strukturmuster, bei dem einzelne Objekte und Gruppen von Objekten über dieselbe Schnittstelle behandelt werden. Ein einzelnes Element wird oft „Leaf“ genannt. Eine Gruppe, die weitere Elemente enthalten kann, ist das „Composite“. Der aufrufende Code muss nicht wissen, ob er gerade ein einzelnes Objekt oder einen ganzen Objektbaum verarbeitet.

Der Nutzen wird klar, sobald du Baumstrukturen modellierst. Eine Preisregel kann aus einer einzelnen Bedingung bestehen: „Kategorie ist 12“. Sie kann aber auch eine Gruppe enthalten: „Kategorie ist 12 UND Kundengruppe ist B2B UND Warenkorbwert ist größer als 100“. Diese Gruppe kann wiederum Untergruppen enthalten. Genau solche Strukturen sind ideal für das Composite Pattern Magento 2, weil jede Bedingung und jede Gruppe dieselbe Methode anbieten kann, zum Beispiel validate().

Das Pattern reduziert Sonderfälle. Ohne Composite müsste dein Code ständig fragen: „Ist das ein einzelner Knoten oder eine Gruppe?“ Mit Composite verarbeitet der Code einfach ein Interface. Eine Gruppe ruft intern dieselbe Methode bei ihren Kindern auf. Ein einzelnes Element prüft nur sich selbst. Das macht Regeln, Layouts, Menüs, Kategorien, Berechtigungsbäume und andere verschachtelte Modelle deutlich leichter wartbar.


<?php
declare(strict_types=1);

/**
 * Common contract for a rule condition node.
 */
interface ConditionInterface
{
    /**
     * Validates the given subject against this condition node.
     */
    public function validate(array $subject): bool;
}

/**
 * Leaf condition that checks one field.
 */
final readonly class FieldCondition implements ConditionInterface
{
    public function __construct(
        private string $field,
        private mixed $expectedValue
    ) {}

    public function validate(array $subject): bool
    {
        return ($subject[$this->field] ?? null) === $this->expectedValue;
    }
}

/**
 * Composite condition that validates all child conditions.
 */
final class AllConditions implements ConditionInterface
{
    /**
     * @param ConditionInterface[] $conditions
     */
    public function __construct(
        private readonly array $conditions
    ) {}

    public function validate(array $subject): bool
    {
        foreach ($this->conditions as $condition) {
            if (!$condition->validate($subject)) {
                return false;
            }
        }

        return true;
    }
}

Dieses vereinfachte Beispiel zeigt den Kern: FieldCondition und AllConditions implementieren dasselbe Interface. Die Gruppe kennt ihre Kinder, aber der Client kennt nur ConditionInterface. Das Composite Pattern Magento 2 ist dadurch nicht nur ein akademisches Muster, sondern eine sehr praktische Lösung für Shop-Regeln.

2. Composite Pattern in Magento 2

In Magento 2 taucht das Composite Pattern an mehreren Stellen auf. Am sichtbarsten ist es bei Sales Rules und Catalog Price Rules. Dort entstehen Bedingungsbäume aus einzelnen Conditions und Gruppen. Eine Regel kann zum Beispiel prüfen, ob ein Produkt zu einer Kategorie gehört, ob ein Warenkorbwert erreicht wurde oder ob ein Kunde zu einer bestimmten Kundengruppe gehört. Diese Bedingungen werden nicht als flache Liste verarbeitet, sondern als Baum.

Auch das Layout-System ist ein gutes Beispiel. Ein einzelner Block kann HTML rendern. Ein Container kann mehrere Blöcke oder Container enthalten. Für den Template-Code ist das oft egal: Er ruft getChildHtml() auf, und Magento verarbeitet die darunterliegende Struktur. Genau diese Einheitlichkeit ist typisch für das Composite Pattern Magento 2.

Weitere Beispiele sind Konfigurationsbäume, Menüstrukturen, Kategoriehierarchien, UI-Component-Strukturen und teilweise Preisberechnungen. Nicht jede dieser Stellen ist eine perfekte Lehrbuch-Implementierung. Magento ist historisch gewachsen. Aber das Architekturprinzip ist klar: Verschachtelte Strukturen werden einheitlich durchlaufen, validiert oder gerendert.


<?php
declare(strict_types=1);

namespace Mironsoft\Rule\Service;

use Magento\SalesRule\Api\Data\RuleInterface;
use Magento\Quote\Api\Data\CartInterface;

/**
 * Demonstrates how a rule service can stay independent from concrete condition trees.
 */
final class CartRuleValidator
{
    /**
     * Validates the cart by delegating to the rule condition tree.
     */
    public function isRuleApplicable(RuleInterface $rule, CartInterface $cart): bool
    {
        $conditions = $rule->getCondition();

        if ($conditions === null) {
            return true;
        }

        return (bool) $conditions->validate($cart);
    }
}

Der entscheidende Punkt ist nicht die einzelne Klasse, sondern die Form der Zusammenarbeit. Der Validator muss nicht jede mögliche Bedingung kennen. Er delegiert an den Condition-Baum. Neue Conditions können ergänzt werden, ohne dass der zentrale Ablauf alle Details kennen muss. Damit unterstützt das Composite Pattern Magento 2 das Open/Closed Principle: Erweiterungen kommen über neue Knoten, nicht über immer größere If-Else-Blöcke.

3. Preisregeln und Sales Rule Conditions

Preisregeln sind der praktischste Einstieg in das Composite Pattern Magento 2. In Adminhtml baut der Händler Bedingungen visuell zusammen: „Wenn ALLE Bedingungen erfüllt sind“ oder „Wenn EINE Bedingung erfüllt ist“. Darunter liegen einzelne Produkt-, Warenkorb- oder Kundenbedingungen. Magento speichert diese Struktur und rekonstruiert sie später als Bedingungsbaum.

Eine Bedingungsgruppe ist selbst wieder eine Bedingung. Sie hat nur eine andere Aufgabe: Sie entscheidet nicht anhand eines Feldes, sondern anhand ihrer Kinder. Eine „ALL“-Gruppe gibt nur dann true zurück, wenn jedes Kind gültig ist. Eine „ANY“-Gruppe gibt true zurück, sobald ein Kind gültig ist. Einzelbedingungen prüfen dagegen konkrete Werte. Genau dieses gemeinsame Interface macht das Composite Pattern Magento 2 so passend für Preisregeln.

In Magento-Projekten entstehen Fehler häufig, wenn Entwickler die Bedingungsstruktur umgehen und eigene Sonderlogik neben die Regelengine schreiben. Das funktioniert kurzfristig, wird aber schwer wartbar. Besser ist es, eigene Conditions sauber in die vorhandene Struktur einzubinden. Dann kann der Händler sie im Admin nutzen, und die Regelengine verarbeitet sie wie alle anderen Conditions.


<?php
declare(strict_types=1);

namespace Mironsoft\SalesRule\Model\Rule\Condition;

use Magento\Rule\Model\Condition\AbstractCondition;

/**
 * Custom condition that checks whether the cart contains a configured product attribute value.
 */
class HasProductAttributeValue extends AbstractCondition
{
    /**
     * Defines the condition metadata for the admin condition tree.
     */
    public function loadAttributeOptions(): self
    {
        $this->setAttributeOption([
            'mironsoft_product_attribute_value' => __('Product has configured attribute value')
        ]);

        return $this;
    }

    /**
     * Validates the quote item or quote context against the configured condition.
     */
    public function validate(\Magento\Framework\Model\AbstractModel $model): bool
    {
        $attributeCode = (string) $this->getAttribute();
        $expectedValue = (string) $this->getValue();
        $product = $model->getProduct();

        if (!$product) {
            return false;
        }

        return (string) $product->getData($attributeCode) === $expectedValue;
    }
}

Dieses Beispiel ist bewusst kompakt. In einem echten Modul würde man die Condition über Dependency Injection, Konfiguration und Admin-Optionen sauberer aufbauen. Trotzdem sieht man die Idee: Eine eigene Condition wird Teil des Baums. Sie verhält sich wie ein Leaf-Knoten und kann von Gruppen kombiniert werden. Das Composite Pattern Magento 2 sorgt dafür, dass die Regelengine nicht zwischen Core-Condition und Custom-Condition unterscheiden muss.

4. Layout-Bäume und Container

Neben Preisregeln ist das Layout-System eine zweite wichtige Composite-Struktur. Magento-Layouts bestehen aus Blöcken, Containern und verschachtelten Kindern. Ein Block rendert konkrete Ausgabe. Ein Container organisiert Kinder und Positionierung. Templates arbeiten häufig mit $block->getChildHtml(). Der aufrufende Code muss nicht immer wissen, wie tief die Struktur darunter ist.

Für Hyvä-Projekte ist das besonders relevant. Der Hyvä-Block-Ansatz sollte nicht ersetzt werden. Wenn ein Template über $block->getChildNames() iteriert oder getChildHtml() nutzt, bleibt die Layout-Struktur erweiterbar. Module können neue Blöcke per Layout-XML ergänzen, ohne das Template hart zu ändern. Das ist Composite-Denken in der täglichen Magento-Frontend-Arbeit.

Wer stattdessen direkte HTML-Fragmente hart in Templates einbaut, verliert diese Erweiterbarkeit. Ein Composite-System lebt davon, dass Elternknoten Kinder aufnehmen können und der Render-Prozess einheitlich bleibt. Deshalb sollten Änderungen sauber über Layout-XML gesteuert werden.


<?php
declare(strict_types=1);

/**
 * Simplified template example for rendering child blocks.
 *
 * @var \Magento\Framework\View\Element\Template $block
 */
?>

<div class="footer-links">
    <?php foreach ($block->getChildNames() as $childName): ?>
        <?= $block->getChildHtml($childName) ?>
    <?php endforeach; ?>
</div>

Das Template behandelt jedes Kind gleich. Ob dahinter ein einfacher Link-Block, ein Container mit mehreren Kindern oder eine komplexere Komponente liegt, entscheidet das Layout. Genau deshalb passt das Composite Pattern Magento 2 auch zur Frontend-Architektur. Die Struktur bleibt erweiterbar, ohne dass alle Aufrufer die Details kennen müssen.

5. Eigene Composite-Implementierung

Eigene Composite-Strukturen sind sinnvoll, wenn dein Modul verschachtelte Regeln, Validierungen oder Berechnungen braucht. Beispiele sind B2B-Freigaben, Produktfeed-Filter, dynamische Preisaufschläge, Kundensegment-Regeln oder Versandlogik. Wichtig ist, zuerst ein klares Interface zu definieren. Leaf-Knoten und Composite-Knoten implementieren dieses Interface gemeinsam.

Das finale Design sollte einfach bleiben. Wenn ein Knoten validiert, nennt man die Methode validate(). Wenn ein Knoten rendert, nennt man sie render(). Wenn ein Knoten einen Betrag berechnet, nennt man sie calculate(). Das Composite Pattern Magento 2 funktioniert dann gut, wenn der gemeinsame Vertrag wirklich natürlich ist.


<?php
declare(strict_types=1);

namespace Mironsoft\Approval\Api;

/**
 * Defines a common approval rule node.
 */
interface ApprovalRuleInterface
{
    /**
     * Checks whether the approval request satisfies this rule node.
     */
    public function isSatisfiedBy(ApprovalContextInterface $context): bool;
}

<?php
declare(strict_types=1);

namespace Mironsoft\Approval\Model\Rule;

use Mironsoft\Approval\Api\ApprovalContextInterface;
use Mironsoft\Approval\Api\ApprovalRuleInterface;

/**
 * Leaf rule that checks the order total.
 */
final readonly class MinimumOrderTotalRule implements ApprovalRuleInterface
{
    public function __construct(
        private float $minimumTotal
    ) {}

    public function isSatisfiedBy(ApprovalContextInterface $context): bool
    {
        return $context->getOrderTotal() >= $this->minimumTotal;
    }
}

<?php
declare(strict_types=1);

namespace Mironsoft\Approval\Model\Rule;

use Mironsoft\Approval\Api\ApprovalContextInterface;
use Mironsoft\Approval\Api\ApprovalRuleInterface;

/**
 * Composite rule that requires every child rule to be satisfied.
 */
final readonly class AllApprovalRules implements ApprovalRuleInterface
{
    /**
     * @param ApprovalRuleInterface[] $rules
     */
    public function __construct(
        private array $rules
    ) {}

    public function isSatisfiedBy(ApprovalContextInterface $context): bool
    {
        foreach ($this->rules as $rule) {
            if (!$rule->isSatisfiedBy($context)) {
                return false;
            }
        }

        return true;
    }
}

Diese Struktur ist testbar. Du kannst Leaf-Regeln isoliert testen und Composite-Regeln mit Mocks oder echten kleinen Regeln prüfen. Gleichzeitig ist sie erweiterbar. Neue Regeln brauchen keine Änderung am Composite selbst. Das ist der wichtigste Vorteil gegenüber einer zentralen Klasse mit vielen Bedingungen.

6. Vergleich: Composite vs. Strategy vs. Decorator

Das Composite Pattern Magento 2 wird leicht mit anderen Patterns verwechselt. Strategy kapselt austauschbare Algorithmen. Decorator erweitert ein Objekt dynamisch um Verhalten. Composite modelliert dagegen Teil-Ganzes-Strukturen. Wenn du einen Baum hast, in dem einzelne Knoten und Gruppen gleich behandelt werden sollen, ist Composite meist der richtige Kandidat.

Pattern Zweck Magento-Beispiel
Composite Einzelobjekte und Gruppen einheitlich behandeln Sales Rule Conditions, Layout-Bäume, Menüstrukturen
Strategy Austauschbare Algorithmen kapseln Shipping Carrier, Payment Methods, Preisberechnung
Decorator Verhalten eines Objekts erweitern Service-Erweiterung durch Wrapper oder Plugins
Chain of Responsibility Anfrage durch mehrere Handler schicken Router, Validator-Ketten, Import-Verarbeitung

Der Test ist einfach: Wenn du verschachtelte Knoten hast und jeder Knoten dieselbe Operation ausführen soll, spricht viel für Composite. Wenn du nur eine von mehreren Berechnungsarten auswählen willst, ist Strategy besser. Wenn du bestehendes Verhalten um zusätzliche Logik erweitern willst, ist Decorator oder in Magento oft ein Plugin passender.

Mironsoft

Magento 2 Architektur, Preisregeln und Hyvä Frontends

Komplexe Magento-Regeln sauber modellieren?

Wir entwickeln Magento 2 Module mit klaren Service Contracts, testbaren Regelstrukturen, sauberen Layout-Erweiterungen und Hyvä-kompatiblen Frontends ohne Luma-Abhängigkeiten.

Preisregeln

Sales Rule Conditions und Catalog Rule Logik wartbar erweitern

Architektur

Composite, Strategy, Repository und Plugins korrekt trennen

Hyvä

Layout-Bäume und Block-System sauber über XML erweitern

8. Zusammenfassung

Das Composite Pattern Magento 2 ist besonders wertvoll für Baumstrukturen. Preisregeln, Sales Rule Conditions, Layout-Container und Menüstrukturen profitieren davon, dass einzelne Knoten und Gruppen über dasselbe Interface verarbeitet werden. Dadurch bleiben zentrale Services schlank und müssen nicht jeden Sonderfall kennen.

In eigenen Modulen solltest du Composite nutzen, wenn du verschachtelte Regeln oder Teil-Ganzes-Strukturen modellierst. Definiere ein klares Interface, halte Leaf-Knoten klein und mache Composite-Knoten für die Kombination ihrer Kinder verantwortlich. Business-Entscheidungen bleiben in Services, die Struktur selbst bleibt im Composite.

Composite Pattern Magento 2 — Das Wichtigste auf einen Blick

Hauptzweck

Einzelne Objekte und Gruppen von Objekten über dieselbe Schnittstelle behandeln.

Magento-Beispiele

Sales Rule Conditions, Catalog Price Rules, Layout-Bäume, Container und Menüstrukturen.

Guter Einsatz

Verschachtelte Regeln, Validierungsbäume, Render-Bäume und hierarchische Konfigurationen.

Vorsicht

Composite nicht für flache Algorithmen erzwingen. Für austauschbare Berechnungen ist Strategy oft passender.

9. FAQ: Composite Pattern in Magento 2

1 Was ist das Composite Pattern in Magento 2?
Ein Muster, bei dem einzelne Objekte und Gruppen über dieselbe Schnittstelle behandelt werden. In Magento 2 sieht man das bei Preisregeln, Layout-Bäumen und Containern.
2 Warum passt Composite zu Preisregeln?
Preisregeln bestehen aus einzelnen Conditions und Condition-Gruppen. Gruppen enthalten weitere Conditions und werden selbst wie eine Condition validiert.
3 Was ist ein Leaf?
Ein Leaf ist ein einzelner Knoten ohne Kinder, zum Beispiel eine Bedingung für Produktattribut, Kundengruppe oder Warenkorbwert.
4 Was ist ein Composite-Knoten?
Ein Composite-Knoten enthält weitere Knoten. Bei Regeln ist das eine Gruppe, die Kindbedingungen mit ALL- oder ANY-Logik kombiniert.
5 Ist das Layout-System ein Composite?
Konzeptionell ja. Blöcke und Container bilden verschachtelte Render-Strukturen, die über Layout-XML erweitert und über getChildHtml() ausgegeben werden.
6 Wie erweitert man Sales Rule Conditions?
Über eine eigene Condition-Klasse, die in die Condition-Auswahl eingebunden wird. Die Regelengine verarbeitet sie danach wie andere Knoten im Baum.
7 Composite oder Strategy?
Composite passt für verschachtelte Knoten und Teil-Ganzes-Strukturen. Strategy passt für austauschbare Algorithmen ohne Baumstruktur.
8 Sollte man Composite immer nutzen?
Nein. Es lohnt sich bei echten Baumstrukturen. Für einfache flache Abläufe wäre Composite unnötige Komplexität.
9 Wie testet man Composite-Strukturen?
Leaf-Knoten isoliert testen, Composite-Knoten mit kleinen Regeln oder Mocks testen. Besonders wichtig sind AND-, OR- und Abbruchlogik.
10 Passt Composite zu Hyvä?
Ja. Hyvä-Templates sollten die Magento-Layout-Struktur respektieren und Kinder über getChildNames() oder getChildHtml() rendern, statt sie hart zu ersetzen.