PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Wzorzec Decorator – dynamiczne zachowanie, PHP, system pluginów Magento 2

by Henryk Tews / piątek, 21 sierpnia 2026 / Opublikowano w Wzorce projektowe

Wzorzec Decorator pozwala dynamicznie dodawać zachowanie do obiektu bez modyfikacji jego klasy i bez dziedziczenia. Każdy dekorator opakowuje oryginalny obiekt i rozszerza lub modyfikuje jego działanie. W PHP i Magento 2 to jeden z najczęściej stosowanych wzorców – system pluginów Magento to de facto Decorator zaimplementowany przez framework DI.

Implementacja podstawowa

<?php

declare(strict_types=1);

// Interfejs komponentu
interface PriceCalculatorInterface
{
    public function calculate(float $basePrice): float;
    public function getDescription(): string;
}

// Konkretny komponent
class BasePriceCalculator implements PriceCalculatorInterface
{
    public function calculate(float $basePrice): float
    {
        return $basePrice;
    }

    public function getDescription(): string
    {
        return "Cena bazowa";
    }
}

// Abstrakcyjny dekorator
abstract class PriceDecorator implements PriceCalculatorInterface
{
    public function __construct(
        protected PriceCalculatorInterface $calculator
    ) {}

    public function calculate(float $basePrice): float
    {
        return $this->calculator->calculate($basePrice);
    }

    public function getDescription(): string
    {
        return $this->calculator->getDescription();
    }
}

// Konkretne dekoratory
class TaxDecorator extends PriceDecorator
{
    public function __construct(
        PriceCalculatorInterface $calculator,
        private float $taxRate = 0.23
    ) {
        parent::__construct($calculator);
    }

    public function calculate(float $basePrice): float
    {
        return parent::calculate($basePrice) * (1 + $this->taxRate);
    }

    public function getDescription(): string
    {
        return parent::getDescription() . " + VAT " . ($this->taxRate * 100) . "%";
    }
}

class DiscountDecorator extends PriceDecorator
{
    public function __construct(
        PriceCalculatorInterface $calculator,
        private float $discountPercent
    ) {
        parent::__construct($calculator);
    }

    public function calculate(float $basePrice): float
    {
        $price = parent::calculate($basePrice);
        return $price * (1 - $this->discountPercent / 100);
    }

    public function getDescription(): string
    {
        return parent::getDescription() . " - {$this->discountPercent}% rabat";
    }
}

class ShippingDecorator extends PriceDecorator
{
    public function __construct(
        PriceCalculatorInterface $calculator,
        private float $shippingCost
    ) {
        parent::__construct($calculator);
    }

    public function calculate(float $basePrice): float
    {
        return parent::calculate($basePrice) + $this->shippingCost;
    }

    public function getDescription(): string
    {
        return parent::getDescription() . " + dostawa {$this->shippingCost} PLN";
    }
}

// Kompozycja dekoratorów
$calculator = new BasePriceCalculator();
$calculator = new DiscountDecorator($calculator, 10.0);   // -10%
$calculator = new TaxDecorator($calculator, 0.23);         // +23% VAT
$calculator = new ShippingDecorator($calculator, 19.99);   // +dostawa

echo $calculator->calculate(100.0);     // 100 * 0.9 * 1.23 + 19.99 = 130.69
echo $calculator->getDescription();
// "Cena bazowa - 10% rabat + VAT 23% + dostawa 19.99 PLN"

Decorator w Magento 2 – system pluginów

<?php
// Magento 2 around plugin = Decorator pattern przez DI framework

// Oryginalny serwis
class ProductRepository implements ProductRepositoryInterface
{
    public function getById(int $id): ProductInterface
    {
        return $this->resource->load($id);
    }
}

// Plugin (Decorator) - dodaje cache
class CachingProductRepositoryPlugin
{
    public function __construct(
        private \Magento\Framework\Cache\FrontendInterface $cache
    ) {}

    public function aroundGetById(
        ProductRepositoryInterface $subject,
        callable $proceed,
        int $id
    ): ProductInterface {
        $cacheKey = "product_{$id}";

        $cached = $this->cache->load($cacheKey);
        if ($cached) {
            return unserialize($cached);
        }

        $product = $proceed($id); // wywołaj oryginalną metodę

        $this->cache->save(serialize($product), $cacheKey, [], 3600);
        return $product;
    }
}

// di.xml - rejestracja pluginu (DI robi resztę)
// <type name="Magento\Catalog\Model\ProductRepository">
//   <plugin name="caching" type="Vendor\Module\Plugin\CachingProductRepositoryPlugin"/>
// </type>

Kiedy Decorator, kiedy dziedziczenie

Kryterium Decorator Dziedziczenie
Kombinowanie zachowań Tak – dowolna kompozycja Nie – jeden łańcuch
Runtime decyzja Tak Nie – compile time
Modyfikacja klasy bazowej Nie potrzeba Może wymagać
Złożoność Więcej klas Prostsza hierarchia

Podsumowanie

Decorator to otoczenie obiektu w kolejne warstwy zachowania bez modyfikacji oryginału. PHP i Magento 2 stosują go powszechnie – każdy around plugin to Decorator. Kluczowa zaleta: dekoratory można komponować w dowolnej kolejności i kombinacji w runtime. Wada: wiele małych klas – trudniejsze debugowanie gdy łańcuch jest długi.

About Henryk Tews

Co możesz przeczytać następne

Wzorzec Specification – enkapsulacja reguł biznesowych, AND/OR/NOT kompozycja, testowanie
Chain of Responsibility – łańcuch walidatorów, konfiguracja przez di.xml z sortOrder
Wzorzec Memento – undo/redo, historia zmian cen, persystencja do bazy jako audit log
  • Publikacje
  • O autorze
  • Kontakt

© 2026 Created by

GÓRA
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Zawsze aktywne
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Zarządzaj opcjami
  • Zarządzaj serwisami
  • Zarządzaj {vendor_count} dostawcami
  • Przeczytaj więcej o tych celach
Zobacz preferencje
  • {title}
  • {title}
  • {title}