Strategy Pattern in Magento 2
AI generated
Magento 2 · Design Patterns

Strategy Pattern
in Magento 2

The Strategy pattern defines a family of algorithms, encapsulates each one and makes them interchangeable. In Magento 2 it shows up everywhere: tax calculation, shipping methods, price rules, payment methods, all of them rely on Strategy.

⏱ 11 min read PHP 8.4 Magento 2.4.8

1. The Strategy Pattern

The Strategy pattern (GoF) defines a family of algorithms behind a shared interface. The context knows the interface, not the concrete implementation. Algorithms can be swapped at runtime:


// Strategy interface
interface PriceCalculatorInterface
{
    public function calculate(float $basePrice, array $context): float;
}

// Concrete strategy 1
class StandardPriceCalculator implements PriceCalculatorInterface
{
    public function calculate(float $basePrice, array $context): float
    {
        return $basePrice;
    }
}

// Concrete strategy 2
class DiscountPriceCalculator implements PriceCalculatorInterface
{
    public function calculate(float $basePrice, array $context): float
    {
        $discount = $context['discount_percent'] ?? 0;
        return $basePrice * (1 - $discount / 100);
    }
}

// Context: uses the strategy without knowing the concrete implementation
class PriceContext
{
    public function __construct(
        private PriceCalculatorInterface $calculator
    ) {}

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

2. Strategy in Magento 2: Where It Shows Up

The Strategy pattern is one of the most frequently used patterns in Magento 2:

  • Tax Calculators - TaxCalculatorInterface: different tax calculation algorithms (row-based, unit-based, total-based)
  • Shipping Methods - AbstractCarrier: every shipping method is its own strategy
  • Payment Methods - MethodInterface: credit card, PayPal, bank transfer as interchangeable strategies
  • Price Renderers - different price presentations per product type
  • Search Adapters - MySQL vs Elasticsearch as interchangeable search strategies

// Magento tax calculator strategy (simplified)
interface TaxCalculatorInterface
{
    /**
     * Calculate tax for the given item details.
     */
    public function calculateWithTaxInPrice(
        QuoteDetailsItemInterface $item,
        float $quantity,
        bool $round
    ): AppliedTaxRateInterface;
}

// Row-based calculator, one of the strategies
class RowBaseCalculator implements TaxCalculatorInterface
{
    public function calculateWithTaxInPrice(
        QuoteDetailsItemInterface $item,
        float $quantity,
        bool $round
    ): AppliedTaxRateInterface {
        // Tax calculation per row
        $price = $item->getUnitPrice() * $quantity;
        return $this->applyRates($price);
    }
}

3. Shipping Methods as Strategy

Shipping methods are the clearest example of Strategy in Magento 2. Every carrier implements AbstractCarrier with the collectRates() method as its core strategy:


<?php
declare(strict_types=1);

namespace Mironsoft\Shipping\Model\Carrier;

use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory;
use Magento\Quote\Model\Quote\Address\RateResult\MethodFactory;
use Magento\Shipping\Model\Carrier\AbstractCarrier;
use Magento\Shipping\Model\Carrier\CarrierInterface;
use Magento\Shipping\Model\Rate\ResultFactory;

/**
 * Flat rate carrier strategy, returns fixed shipping rate.
 */
class FlatRate extends AbstractCarrier implements CarrierInterface
{
    protected string $_code = 'mironsoft_flatrate';

    public function __construct(
        private readonly ResultFactory $rateResultFactory,
        private readonly MethodFactory $rateMethodFactory,
        // Parent constructor parameters...
    ) {}

    /**
     * Collect shipping rates for this carrier strategy.
     */
    public function collectRates(RateRequest $request): ?\Magento\Shipping\Model\Rate\Result
    {
        if (!$this->getConfigFlag('active')) {
            return null;
        }

        $result = $this->rateResultFactory->create();
        $method = $this->rateMethodFactory->create();

        $method->setCarrier($this->_code);
        $method->setCarrierTitle($this->getConfigData('title'));
        $method->setMethod('standard');
        $method->setMethodTitle('Standard Shipping');
        $method->setPrice((float) $this->getConfigData('price'));

        $result->append($method);
        return $result;
    }

    public function getAllowedMethods(): array
    {
        return ['standard' => 'Standard Shipping'];
    }
}

4. Implementing Your Own Strategy

A practical example: different export formats (CSV, XML, JSON) as interchangeable strategies:


<?php
declare(strict_types=1);

namespace Mironsoft\Export\Api;

/**
 * Export formatter strategy interface.
 */
interface FormatterInterface
{
    /**
     * Format data array into export string.
     *
     * @param array<string, mixed> $data
     */
    public function format(array $data): string;

    public function getMimeType(): string;

    public function getFileExtension(): string;
}

<?php
declare(strict_types=1);

namespace Mironsoft\Export\Model\Formatter;

use Mironsoft\Export\Api\FormatterInterface;

/**
 * CSV export formatter strategy.
 */
class CsvFormatter implements FormatterInterface
{
    public function format(array $data): string
    {
        $output = '';
        foreach ($data as $row) {
            $output .= implode(',', array_map(
                fn($value) => '"' . str_replace('"', '""', (string) $value) . '"',
                $row
            )) . "\n";
        }
        return $output;
    }

    public function getMimeType(): string { return 'text/csv'; }
    public function getFileExtension(): string { return 'csv'; }
}

5. Strategy with DI and Pools

Magento 2 often combines a pool (array of strategies) with DI to make strategies configurable:


<?php
declare(strict_types=1);

namespace Mironsoft\Export\Model;

use Mironsoft\Export\Api\FormatterInterface;

/**
 * Export service, uses a strategy pool for format selection.
 */
class ExportService
{
    /** @param FormatterInterface[] $formatters */
    public function __construct(
        private readonly array $formatters
    ) {}

    public function export(array $data, string $format): string
    {
        $formatter = $this->formatters[$format]
            ?? throw new \InvalidArgumentException("Unknown format: $format");

        return $formatter->format($data);
    }

    /** @return string[] */
    public function getSupportedFormats(): array
    {
        return array_keys($this->formatters);
    }
}

<!-- di.xml: configuring the strategy pool -->
<type name="Mironsoft\Export\Model\ExportService">
    <arguments>
        <argument name="formatters" xsi:type="array">
            <item name="csv" xsi:type="object">Mironsoft\Export\Model\Formatter\CsvFormatter</item>
            <item name="xml" xsi:type="object">Mironsoft\Export\Model\Formatter\XmlFormatter</item>
            <item name="json" xsi:type="object">Mironsoft\Export\Model\Formatter\JsonFormatter</item>
        </argument>
    </arguments>
</type>

Mironsoft

Magento 2 Module Development & Architecture

Strategy Pattern for Your Magento 2 Project?

We build extensible Magento 2 modules with strategy pool architecture: configurable via di.xml, testable and without core modifications. Shipping, payment, export and more.

Custom Carrier

Custom shipping methods with AbstractCarrier and strategy architecture

Strategy Pools

Extensible algorithm pools via di.xml, no core hacking

Interface Design

Service contracts and strategy interfaces for upgrade-safe extensibility

6. Summary

The Strategy pattern is one of the most widely used GoF patterns in Magento 2. It makes algorithms interchangeable without changing the context code. In Magento it becomes configurable through DI pools in di.xml: new strategies are added through new classes and di.xml entries, not through core changes.

Strategy Pattern, the Key Points at a Glance

Strategy Interface

A shared interface for all algorithms. The context only knows the interface. Concrete strategies implement it, interchangeable without changing the context.

Magento Examples

Tax calculators, shipping carriers, payment methods, search adapters. All follow the same pattern: interface plus concrete implementations.

Strategy Pool via di.xml

argument name="strategies" xsi:type="array", extensible without code changes. New strategy: new class plus di.xml entry.

Strategy vs. Plugin

Plugins extend or override existing logic. Strategy replaces the entire algorithm. For completely different calculation logic: Strategy. For adjustments: Plugin.

7. FAQ: Strategy Pattern in Magento 2

1 What is the Strategy pattern?
A family of algorithms behind a shared interface. The context only knows the interface, algorithms are interchangeable at runtime. New behavior: new class, no context code changes.
2 Where does Magento 2 use Strategy?
Tax calculators, shipping carriers, payment methods, search adapters, price renderers, import/export handlers. The most used GoF pattern in Magento 2.
3 How do I create my own shipping method?
AbstractCarrier extends + CarrierInterface implements. collectRates() is the strategy method. Register it in etc/config.xml. Provide admin configuration in system.xml.
4 How do you implement a strategy pool?
Define an interface, implement concrete classes. The service receives $strategies as an array via constructor injection. Configure it as an array argument in di.xml. New strategy: new class plus di.xml entry.
5 When Strategy instead of Plugin?
Strategy = replace the algorithm entirely. Plugin = modify or extend a method. For completely different calculation logic: Strategy. For adjusting existing logic: Plugin.
6 How can I override a Magento strategy?
Full replacement: preference in di.xml. Modification: a plugin on the strategy class. Registering your own new implementation is often more stable than overriding an existing one.
7 How do you test strategy classes?
Very easily: instantiate concrete strategies directly and test the algorithm method with known inputs. Mock the interface for context tests. The Strategy pattern makes code very testable through this separation.
8 Strategy vs. Template Method pattern?
Template Method: an algorithm skeleton in the base class, hooks via inheritance. Strategy: a completely interchangeable object via composition. Magento uses both: AbstractCarrier is Template Method, the carrier pool is Strategy.
9 How does Magento choose the right tax strategy?
Via admin configuration: Stores > Tax > Tax Calculation Method Based On. The TaxCalculationService gets the chosen calculator strategy injected via DI.
10 Is Strategy the same as Dependency Injection?
Related, but different. DI is the mechanism. Strategy is the pattern. In Magento 2, DI is used to inject strategy objects, DI pools (di.xml arrays) make switching strategies possible without code changes.