Observer and Strategy are two of the most widely used behavioural patterns. Observer defines a publish-subscribe mechanism where subjects notify observers of state changes. Strategy defines a family of interchangeable algorithms. Both appear throughout Magento 2 – I show GoF implementations and the platform’s concrete use of each.
Observer – event-driven notification
<?php
declare(strict_types=1);
interface EventObserverInterface
{
public function onEvent(string $eventName, array $eventData): void;
}
class EventEmitter
{
private array $listeners = [];
public function on(string $event, EventObserverInterface $observer): void
{
$this->listeners[$event][] = $observer;
}
public function off(string $event, EventObserverInterface $observer): void
{
$this->listeners[$event] = array_filter(
$this->listeners[$event] ?? [],
fn($o) => $o !== $observer
);
}
public function emit(string $event, array $data = []): void
{
foreach ($this->listeners[$event] ?? [] as $observer) {
$observer->onEvent($event, $data);
}
}
}
// Concrete observers
class EmailObserver implements EventObserverInterface
{
public function onEvent(string $eventName, array $data): void
{
if ($eventName === 'order.placed') {
echo "Sending confirmation email to: {$data['email']}\n";
}
}
}
class InventoryObserver implements EventObserverInterface
{
public function onEvent(string $eventName, array $data): void
{
if ($eventName === 'order.placed') {
foreach ($data['items'] as $item) {
echo "Reserving {$item['qty']} of SKU {$item['sku']}\n";
}
}
}
}
class AuditLogObserver implements EventObserverInterface
{
public function onEvent(string $eventName, array $data): void
{
file_put_contents('/var/log/audit.log',
date('Y-m-d H:i:s') . " {$eventName}: " . json_encode($data) . "\n",
FILE_APPEND
);
}
}
// Wire it up
$emitter = new EventEmitter();
$emitter->on('order.placed', new EmailObserver());
$emitter->on('order.placed', new InventoryObserver());
$emitter->on('order.placed', new AuditLogObserver());
// Emit the event - all three observers react automatically
$emitter->emit('order.placed', [
'order_id' => 42,
'email' => 'customer@example.com',
'items' => [['sku' => 'MG-001', 'qty' => 2]],
]);
Observer in Magento 2
<!-- etc/events.xml -->
<config>
<event name="checkout_submit_all_after">
<observer name="vendor_send_confirmation"
instance="Vendor\Module\Observer\SendOrderConfirmation"/>
</event>
<event name="catalog_product_save_after">
<observer name="vendor_sync_to_pim"
instance="Vendor\Module\Observer\SyncProductToPim"/>
</event>
</config>
<?php
namespace Vendor\Module\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
class SyncProductToPim implements ObserverInterface
{
public function __construct(
private \Vendor\Module\Model\PimSyncService $pimSync,
private \Psr\Log\LoggerInterface $logger
) {}
public function execute(Observer $observer): void
{
$product = $observer->getData('product');
try {
$this->pimSync->sync((int) $product->getId());
} catch (\Exception $e) {
$this->logger->error('PIM sync failed', ['product_id' => $product->getId()]);
}
}
}
Strategy – interchangeable algorithms
<?php
declare(strict_types=1);
interface SortStrategyInterface
{
public function sort(array $items): array;
}
class BubbleSortStrategy implements SortStrategyInterface
{
public function sort(array $items): array
{
$n = count($items);
for ($i = 0; $i < $n - 1; $i++) {
for ($j = 0; $j < $n - $i - 1; $j++) {
if ($items[$j] > $items[$j + 1]) {
[$items[$j], $items[$j + 1]] = [$items[$j + 1], $items[$j]];
}
}
}
return $items;
}
}
class QuickSortStrategy implements SortStrategyInterface
{
public function sort(array $items): array
{
if (count($items) <= 1) return $items;
$pivot = $items[0];
$left = array_filter(array_slice($items, 1), fn($x) => $x <= $pivot);
$right = array_filter(array_slice($items, 1), fn($x) => $x > $pivot);
return [...$this->sort(array_values($left)), $pivot, ...$this->sort(array_values($right))];
}
}
class NativeSortStrategy implements SortStrategyInterface
{
public function sort(array $items): array
{
sort($items);
return $items;
}
}
// Context uses strategy without knowing the algorithm
class DataSorter
{
private SortStrategyInterface $strategy;
public function __construct(SortStrategyInterface $strategy)
{
$this->strategy = $strategy;
}
public function setStrategy(SortStrategyInterface $strategy): void
{
$this->strategy = $strategy;
}
public function sort(array $data): array
{
return $this->strategy->sort($data);
}
}
$sorter = new DataSorter(new NativeSortStrategy());
print_r($sorter->sort([5, 3, 8, 1, 9, 2])); // [1, 2, 3, 5, 8, 9]
$sorter->setStrategy(new QuickSortStrategy()); // swap algorithm at runtime
Strategy in Magento 2 – shipping calculations
<?php
// Each shipping carrier is a Strategy implementing AbstractCarrier
// Magento's context (ShippingModel) uses whichever is configured
namespace Vendor\Module\Model\Carrier;
use Magento\Shipping\Model\Carrier\AbstractCarrier;
use Magento\Shipping\Model\Carrier\CarrierInterface;
class FlatRateCarrier extends AbstractCarrier implements CarrierInterface
{
protected $_code = 'vendor_flatrate';
public function collectRates(
\Magento\Quote\Model\Quote\Address\RateRequest $request
): \Magento\Shipping\Model\Rate\Result {
$result = $this->_rateResultFactory->create();
$method = $this->_rateMethodFactory->create();
$method->setCarrier($this->_code);
$method->setMethod('standard');
$method->setMethodTitle('Standard Delivery');
$method->setPrice((float) $this->getConfigData('price'));
$result->append($method);
return $result;
}
public function getAllowedMethods(): array
{
return ['standard' => 'Standard Delivery'];
}
}
Observer vs Strategy – when to use which
| Criterion | Observer | Strategy |
|---|---|---|
| Relationship | One-to-many (subject to observers) | One-to-one (context to algorithm) |
| Coupling | Very loose – subject does not know observers | Context knows it has a strategy |
| When to use | Reacting to events across modules | Varying algorithms or business rules |
| Magento | events.xml observers | Shipping carriers, payment methods |
Summary
Observer and Strategy both reduce coupling between components but in different ways. Observer separates event producers from consumers completely – the subject never knows who is listening. Strategy separates a context from its algorithm – the context knows it has a strategy but not its concrete type. Master both and you have the right tool for the two most common extensibility needs in Magento 2: reacting to platform events and plugging in custom business logic.
