PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Strategy w PHP – i jak Magento 2 używa go w cenach

by Henryk Tews / wtorek, 09 października 2018 / Opublikowano w Magento 2, Wzorce projektowe

Strategy to jeden z tych wzorców, które po pierwszym kontakcie wydają się zbędną komplikacją. Po co opakowywać jeden algorytm w osobną klasę? Sens widać dopiero przy drugim, trzecim algorytmie – gdy zamiast rosnącego if-elsa masz czysty, wymienialny kod. Pokazuję jak to działa w PHP i gdzie Magento 2 stosuje ten wzorzec bez Twojej wiedzy.

Problem bez wzorca

Wyobraź sobie klasę kalkulatora kosztów wysyłki. Zaczynało się niewinnie:

<?php

class ShippingCalculator
{
    public function calculate(string $method, float $weight): float
    {
        if ($method === 'flat') {
            return 9.99;
        } elseif ($method === 'weight') {
            return $weight * 2.5;
        } elseif ($method === 'free') {
            return 0.0;
        }

        throw new \InvalidArgumentException('Unknown method: ' . $method);
    }
}

Każda nowa metoda wysyłki to kolejny elseif. Testy jednostkowe tej klasy testują wszystkie algorytmy naraz. Nie da się podmienić jednego algorytmu bez ryzyka zepsucia pozostałych. Tu wchodzi Strategy.

Rozwiązanie – wzorzec Strategy

Wzorzec definiuje interfejs (kontrakt) i przenosi każdy algorytm do osobnej klasy:

<?php

// Wspólny interfejs dla wszystkich strategii
interface ShippingStrategyInterface
{
    public function calculate(float $weight): float;
}

// Konkretne strategie
class FlatRateStrategy implements ShippingStrategyInterface
{
    public function calculate(float $weight): float
    {
        return 9.99;
    }
}

class WeightBasedStrategy implements ShippingStrategyInterface
{
    private float $ratePerKg;

    public function __construct(float $ratePerKg = 2.5)
    {
        $this->ratePerKg = $ratePerKg;
    }

    public function calculate(float $weight): float
    {
        return $weight * $this->ratePerKg;
    }
}

class FreeShippingStrategy implements ShippingStrategyInterface
{
    public function calculate(float $weight): float
    {
        return 0.0;
    }
}

Klasa kontekstu (Context) przyjmuje strategię z zewnątrz i nie wie nic o jej implementacji:

<?php

class ShippingCalculator
{
    private ShippingStrategyInterface $strategy;

    public function __construct(ShippingStrategyInterface $strategy)
    {
        $this->strategy = $strategy;
    }

    public function setStrategy(ShippingStrategyInterface $strategy): void
    {
        $this->strategy = $strategy;
    }

    public function calculate(float $weight): float
    {
        return $this->strategy->calculate($weight);
    }
}

// Użycie
$calculator = new ShippingCalculator(new WeightBasedStrategy(3.0));
echo $calculator->calculate(5.0); // 15.0

$calculator->setStrategy(new FreeShippingStrategy());
echo $calculator->calculate(5.0); // 0.0

Korzyści w praktyce

  • Każda strategia to osobna klasa – testujesz ją w izolacji
  • Nowa metoda wysyłki to nowa klasa, zero zmian w istniejącym kodzie (zasada Open/Closed)
  • Strategię możesz wstrzyknąć przez DI – łatwa podmiana w testach i konfiguracji

Strategy w Magento 2

Magento 2 używa tego wzorca m.in. w kalkulacji cen. Interfejs Magento\Framework\Pricing\Price\PriceInterface definiuje kontrakt, a konkretne klasy jak RegularPrice, SpecialPrice, TierPrice implementują go niezależnie. Możesz dorzucić własną implementację przez DI bez dotykania istniejących klas.

Podobny mechanizm znajdziesz w sortowaniu wyników wyszukiwania (Magento\Catalog\Model\Config) czy w resolverach URL – każdy typ encji ma swoją strategię rozwiązywania adresów.

Podsumowanie

Strategy sprawdza się zawsze gdy masz zestaw wymiennych algorytmów robiących „to samo, ale inaczej”. Zamiast if-elsa rosnącego z każdym wymaganiem, dostajesz zestaw małych, testowalnych klas z jasno zdefiniowanym kontraktem. W przyszłych wpisach pokażę kolejne wzorce GoF – następny będzie Observer, który w Magento 2 ma wyjątkowo ciekawą implementację przez system zdarzeń.

About Henryk Tews

Co możesz przeczytać następne

AI-assisted optymalizacja SQL – LLM + EXPLAIN + Blackfire, 5x przyspieszenie
Wzorzec Factory Method – Simple Factory, GoF, auto-generowane fabryki Magento 2
GraphQL batch loading – BatchServiceContractResolverInterface, cache z tagami, N+1
  • 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}