After covering all 23 GoF patterns individually, time to see where they actually live inside Magento 2. The platform is a textbook example of design patterns applied at scale – DI, plugins, observers, repositories, factories all map directly to specific GoF patterns. This post is a guided tour of patterns hiding in plain sight in Magento’s architecture.
Creational patterns in Magento 2
Factory Method – *Factory classes
<?php
// Every *Factory class is a Factory Method
// Magento auto-generates them from model class names
use Magento\Catalog\Model\ProductFactory;
use Magento\Sales\Model\Order\ItemFactory;
class MyService
{
public function __construct(
private ProductFactory $productFactory,
private ItemFactory $itemFactory
) {}
public function createProduct(): \Magento\Catalog\Model\Product
{
// create() is the factory method - always returns a new instance
return $this->productFactory->create(['data' => ['sku' => 'NEW-001']]);
}
}
Builder – SearchCriteriaBuilder
<?php
// SearchCriteriaBuilder is a textbook Builder
$criteria = $this->searchCriteriaBuilder
->addFilter('status', 1)
->addFilter('price', 100, 'gt')
->addSortOrder($this->sortOrderBuilder->setField('name')->setAscendingDirection()->create())
->setPageSize(20)
->setCurrentPage(1)
->create(); // builds the immutable SearchCriteria object
Prototype – Object Manager cloneObject
<?php // Magento uses Prototype for quote item duplication // When splitting an order, quote items are cloned $clonedItem = clone $originalItem; // PHP's built-in clone = Prototype
Structural patterns in Magento 2
Decorator – Plugins (Interceptors)
<?php
// Every Magento plugin IS a Decorator
// It wraps a method call and adds behaviour before/after/around
namespace Vendor\Module\Plugin;
class ProductPricePlugin
{
// "around" plugin = classic Decorator pattern
public function aroundGetPrice(
\Magento\Catalog\Model\Product $subject,
callable $proceed
): float {
$price = $proceed(); // call the real method (wrapped object)
return $price * 0.9; // add behaviour (10% discount)
}
}
// Magento compiles this into an Interceptor class - the generated Decorator
Proxy – generated \Proxy classes
<!-- In di.xml - declare Proxy for lazy-loading an expensive service -->
<type name="Vendor\Module\Console\HeavyCommand">
<arguments>
<argument name="heavyService" xsi:type="object">
Vendor\Module\Model\HeavyService\Proxy
</argument>
</arguments>
</type>
<!-- HeavyService is not instantiated until the first method call -->
Composite – Layout blocks and category tree
<?php
// Layout rendering uses Composite:
// AbstractBlock can contain child blocks
// Rendering the parent renders all children recursively
$block = $layout->getBlock('product.info.main');
$block->append($layout->createBlock('Magento\Catalog\Block\Product\View\Description'));
$block->append($layout->createBlock('Magento\Catalog\Block\Product\View\Attributes'));
// toHtml() renders self + all children - Composite pattern
echo $block->toHtml();
Behavioural patterns in Magento 2
Observer – events.xml system
<?php
// Magento's EventManager is the Subject (Publisher)
$this->_eventManager->dispatch('catalog_product_save_after', ['product' => $product]);
// Any module can register an Observer in events.xml
// The EventManager notifies all of them - classic Observer/Pub-Sub
Strategy – shipping carriers and payment methods
<?php
// Each shipping carrier implements CarrierInterface - the Strategy interface
// ShippingModel selects and uses the active carrier without knowing its type
$rates = $this->shippingModel->collectCarrierRates(
$carrier, // Strategy: FlatRateCarrier, DHLCarrier, UPSCarrier...
$request
);
Template Method – AbstractModel and AbstractBlock
<?php
// AbstractModel defines _beforeSave() and _afterSave() hooks
// Subclasses override them to add behaviour
class MyModel extends \Magento\Framework\Model\AbstractModel
{
protected function _beforeSave(): static
{
$this->setUpdatedAt(date('Y-m-d H:i:s'));
return parent::_beforeSave(); // call template method
}
protected function _afterSave(): static
{
// additional logic after save
return parent::_afterSave();
}
}
State – Order state machine
<?php
// Order transitions through states: new -> processing -> complete/closed/cancelled
// Each state permits or blocks certain actions
$order->getState(); // 'processing', 'complete', 'cancelled', etc.
$order->setState(\Magento\Sales\Model\Order::STATE_PROCESSING);
$order->setStatus('processing');
// Magento validates state transitions internally
// You cannot complete an order that is in 'new' state without going through payment
Pattern density in a single request
A typical Magento product page request uses all of the following patterns simultaneously:
- Factory Method – creating product and collection instances
- Builder – SearchCriteria for related products
- Decorator – Plugins modifying product data
- Proxy – lazy-loading optional services
- Observer – layout generation events
- Strategy – tax calculation strategy
- Composite – block tree rendering
- Template Method – block _toHtml() lifecycle
Summary
Magento 2’s architecture is a reference implementation of the GoF patterns in a large PHP application. Understanding the patterns makes the platform less mysterious – when you see a *Factory class, a plugin, an events.xml observer, or a \Proxy suffix, you immediately know the intent and the constraints. Pattern knowledge is not academic overhead; it is the vocabulary that makes working with Magento predictable.
