Skip to main content
Symfony Components


and Design Patterns
Design patterns?
Why should you care?
Communication
Communication


Reduced amount of code in one chunk
Communication


Reduced amount of code in one chunk


Increased cohesion and reduced coupling
Communication


Reduced amount of code in one chunk


Increased cohesion and reduced coupling


Extendibility
Communication


Reduced amount of code in one chunk


Increased cohesion and reduced coupling


Extendibility


SOLID
Increase code quality
Can we measure it?
Avg. Logic Lines of Code
Avg. Logic Lines of Code LLoC
Avg. Logic Lines of Code LLoC
Amount of classes
Avg. Logic Lines of Code LLoC
Amount of classes AoC
Avg. Logic Lines of Code LLoC
Amount of classes AoC
Avg. Cyclomatic Complexity
Avg. Logic Lines of Code LLoC
Amount of classes AoC
Avg. Cyclomatic Complexity ACC
Let’s see the code
Sample Project
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
// Apply taxation
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
// Apply taxation
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct(
$options[‘product'],
$options[‘type'],
$options[‘quantity'],
$options[‘price']
);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
// Apply taxation
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
// Save into repository
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
// Apply taxation
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
// Apply taxation
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
// Save into repository
$this->repository[$options['cart_id']] = $cart;
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
// Apply taxation
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
// Apply taxation
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
Avg. Logic Lines of Code 40
Amount of classes 1
Avg. Cyclomatic Complexity 8
LLoC AoC ACC
Sample project 40 1 8
Single responsibility principle


Open close principle


Liskov substitution principle


Interface segregation principle


Dependency inversion principle
Single responsibility principle


Open close principle


Liskov substitution principle


Interface segregation principle


Dependency inversion principle
Composition
Preparation
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
// Apply taxation
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
// Apply taxation
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
$this->applyPromotion($cart);
// Apply taxation
$this->applyTaxation($cart);
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
private function applyTaxation(Cart $cart): void
{
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
}
private function applyPromotion(Cart $cart): void
{
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
}
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
// Apply promotion
$this->applyPromotion($cart);
// Apply taxation
$this->applyTaxation($cart);
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
$this->processOrder($cart);
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
private function processOrder(Cart $cart): void
{
// Apply promotion
$this->applyPromotion($cart);
// Apply taxation
$this->applyTaxation($cart);
}
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
$this->processOrder($cart);
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
private function applyTaxation(Cart $cart): void
{
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
}
private function applyPromotion(Cart $cart): void
{
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
}
/**
* @param Cart $cart
*/
private function processOrder(Cart $cart): void
{
// Apply promotion
$this->applyPromotion($cart);
// Apply taxation
$this->applyTaxation($cart);
}
}
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
$this->processOrder($cart);
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
$this->processOrder($cart);
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
Extraction
final class CheckoutController
{
private array $repository = [];
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
$this->orderProcessor->processOrder($cart);
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
final class CompositeOrderProcessor implements OrderProcessor
{
public function processOrder(Cart $cart): void
{
// Apply promotion
$this->applyPromotion($cart);
// Apply taxation
$this->applyTaxation($cart);
}
private function applyTaxation(Cart $cart): void
{
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
}
private function applyPromotion(Cart $cart): void
{
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
}
}
LLoC AoC ACC
Sample project 40 1 8
Extracted processor 61 2 4.5
final class CompositeOrderProcessor implements OrderProcessor
{
public function processOrder(Cart $cart): void
{
// Apply promotion
$this->applyPromotion($cart);
// Apply taxation
$this->applyTaxation($cart);
}
private function applyTaxation(Cart $cart): void
{
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
}
private function applyPromotion(Cart $cart): void
{
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
}
}
final class CompositeOrderProcessor implements OrderProcessor
{
public function processOrder(Cart $cart): void
{
// Apply promotion
$this->applyPromotion($cart);
// Apply taxation
$this->applyTaxation($cart);
}
private function applyTaxation(Cart $cart): void
{
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
}
private function applyPromotion(Cart $cart): void
{
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
}
}
final class OrderTaxationProcessor implements OrderProcessor
{
public function processOrder(Cart $cart): void
{
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
}
}
final class CompositeOrderProcessor implements OrderProcessor
{
public function processOrder(Cart $cart): void
{
// Apply promotion
$this->applyPromotion($cart);
// Apply taxation
$this->applyTaxation($cart);
}
private function applyPromotion(Cart $cart): void
{
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
}
}
final class OrderPromotionProcessor implements OrderProcessor
{
public function processOrder(Cart $cart): void
{
if ($cart->getPrice() >= 1000) {
$cart->applyPercentageDiscount(0.1);
}
}
}
final class CompositeOrderProcessor implements OrderProcessor
{
private OrderPromotionProcessor $orderPromotionProcessor;
private OrderTaxationProcessor $orderTaxationProcessor;
public function __construct(
OrderPromotionProcessor $orderPromotionProcessor,
OrderTaxationProcessor $orderTaxationProcessor
) {
$this->orderPromotionProcessor = $orderPromotionProcessor;
$this->orderTaxationProcessor = $orderTaxationProcessor;
}
public function processOrder(Cart $cart): void
{
// Apply promotion
$this->orderPromotionProcessor->processOrder($cart);
// Apply taxation
$this->orderTaxationProcessor->processOrder($cart);
}
}
LLoC AoC ACC
Sample project 40 1 8
Extracted processor 61 2 4.5


Separated processors 76 4 2.75
final class CompositeOrderProcessor implements OrderProcessor
{
private OrderPromotionProcessor $orderPromotionProcessor;
private OrderTaxationProcessor $orderTaxationProcessor;
public function __construct(
OrderPromotionProcessor $orderPromotionProcessor,
OrderTaxationProcessor $orderTaxationProcessor
) {
$this->orderPromotionProcessor = $orderPromotionProcessor;
$this->orderTaxationProcessor = $orderTaxationProcessor;
}
public function processOrder(Cart $cart): void
{
// Apply promotion
$this->orderPromotionProcessor->processOrder($cart);
// Apply taxation
$this->orderTaxationProcessor->processOrder($cart);
}
}
final class CompositeOrderProcessor implements OrderProcessor
{
private iterable $orderProcessors;
public function __construct(
OrderPromotionProcessor $orderPromotionProcessor,
OrderTaxationProcessor $orderTaxationProcessor
) {
$this->orderProcessors = [
$orderPromotionProcessor,
$orderTaxationProcessor
];
}
public function processOrder(Cart $cart): void
{
foreach ($this->orderProcessors as $orderProcessor) {
$orderProcessor->processOrder($cart);
}
}
}
final class CompositeOrderProcessor implements OrderProcessor
{
private iterable $orderProcessors;
public function __construct(
OrderPromotionProcessor $orderPromotionProcessor,
OrderTaxationProcessor $orderTaxationProcessor
) {
$this->orderProcessors = [
$orderPromotionProcessor,
$orderTaxationProcessor
];
}
public function processOrder(Cart $cart): void
{
foreach ($this->orderProcessors as $orderProcessor) {
$orderProcessor->processOrder($cart);
}
}
}
final class CompositeOrderProcessor implements OrderProcessor
{
private iterable $orderProcessors;
public function __construct(
OrderPromotionProcessor $orderPromotionProcessor,
OrderTaxationProcessor $orderTaxationProcessor
) {
$this->orderProcessors = [
$orderPromotionProcessor,
$orderTaxationProcessor
];
}
public function processOrder(Cart $cart): void
{
foreach ($this->orderProcessors as $orderProcessor) {
$orderProcessor->processOrder($cart);
}
}
}
final class CompositeOrderProcessor implements OrderProcessor
{
private iterable $orderProcessors;
public function __construct(
OrderPromotionProcessor $orderPromotionProcessor,
OrderTaxationProcessor $orderTaxationProcessor
) {
$this->orderProcessors = [
$orderPromotionProcessor,
$orderTaxationProcessor
];
}
public function processOrder(Cart $cart): void
{
foreach ($this->orderProcessors as $orderProcessor) {
$orderProcessor->processOrder($cart);
}
}
}
final class CompositeOrderProcessor implements OrderProcessor
{
private iterable $orderProcessors;
public function __construct(iterable $orderProcessors)
{
$this->orderProcessors = $orderProcessors;
}
public function processOrder(Cart $cart): void
{
foreach ($this->orderProcessors as $orderProcessor) {
$orderProcessor->processOrder($cart);
}
}
}
final class CompositeOrderProcessor implements OrderProcessor
{
private iterable $orderProcessors;
public function __construct(iterable $orderProcessors)
{
$this->orderProcessors = $orderProcessors;
}
public function processOrder(Cart $cart): void
{
foreach ($this->orderProcessors as $orderProcessor) {
$orderProcessor->processOrder($cart);
}
}
}
SymfonyDesignPatternsProcessorOrderProcessor:
class: 'SymfonyDesignPatternsProcessorCompositeOrderProcessor'
arguments:
- [
'@SymfonyDesignPatternsProcessorOrderPromotionProcessor',
'@SymfonyDesignPatternsProcessorOrderTaxationProcessor'
]
Can Symfony help us with it?
SymfonyDesignPatternsProcessorOrderProcessor:
class: 'SymfonyDesignPatternsProcessorCompositeOrderProcessor'
arguments:
- !tagged_iterator app.order_processor
SymfonyDesignPatternsProcessorOrderProcessor:
class: 'SymfonyDesignPatternsProcessorCompositeOrderProcessor'
arguments:
- !tagged_iterator app.order_processor
SymfonyDesignPatternsProcessorOrderPromotionProcessor:
tags:
- app.order_processor
SymfonyDesignPatternsProcessorOrderTaxationProcessor:
tags:
- app.order_processor
LLoC AoC ACC
Sample project 40 1 8
Extracted processor 61 2 4.5


Separated processors 76 4 2.75
Composite iteration 73 4 3
Single responsibility principle


Open close principle


Liskov substitution principle


Interface segregation principle


Dependency inversion principle
LLoC AoC ACC
Sample project 40 1 8
Composite iteration 73 4 3
Strategy
final class OrderTaxationProcessor implements OrderProcessor
{
public function processOrder(Cart $cart): void
{
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
}
}
final class OrderTaxationProcessor implements OrderProcessor
{
public function processOrder(Cart $cart): void
{
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
}
}
final class OrderTaxationProcessor implements OrderProcessor
{
private TaxationStrategyDelegator $taxationStrategy;
public function __construct(TaxationStrategyDelegator $taxationStrategy)
{
$this->taxationStrategy = $taxationStrategy;
}
public function processOrder(Cart $cart): void
{
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
$this->taxationStrategy->processTaxation($item);
}
}
}
final class OrderTaxationProcessor implements OrderProcessor
{
private TaxationStrategyDelegator $taxationStrategy;
public function __construct(TaxationStrategyDelegator $taxationStrategy)
{
$this->taxationStrategy = $taxationStrategy;
}
public function processOrder(Cart $cart): void
{
/** @var CartItem $item */
foreach ($cart->getItems() as $item) {
$this->taxationStrategy->processTaxation($item);
}
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
public function processTaxation(CartItem $item): void
{
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
public function processTaxation(CartItem $item): void
{
if ('shirt' === $item->getType()) {
$item->setTaxationPercentage(0.23);
}
if ('book' === $item->getType()) {
$item->setTaxationPercentage(0.08);
}
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
private ShirtTaxationStrategy $shirtTaxationStrategy;
private BookTaxationStrategy $bookTaxationStrategy;
public function __construct(
ShirtTaxationStrategy $shirtTaxationStrategy,
BookTaxationStrategy $bookTaxationStrategy
) {
$this->shirtTaxationStrategy = $shirtTaxationStrategy;
$this->bookTaxationStrategy = $bookTaxationStrategy;
}
public function processTaxation(CartItem $item): void
{
if ('shirt' === $item->getType()) {
$this->shirtTaxationStrategy->processTaxation($item);
}
if ('book' === $item->getType()) {
$this->bookTaxationStrategy->processTaxation($item);
}
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
private ShirtTaxationStrategy $shirtTaxationStrategy;
private BookTaxationStrategy $bookTaxationStrategy;
public function __construct(
ShirtTaxationStrategy $shirtTaxationStrategy,
BookTaxationStrategy $bookTaxationStrategy
) {
$this->shirtTaxationStrategy = $shirtTaxationStrategy;
$this->bookTaxationStrategy = $bookTaxationStrategy;
}
public function processTaxation(CartItem $item): void
{
if ('shirt' === $item->getType()) {
$this->shirtTaxationStrategy->processTaxation($item);
}
if ('book' === $item->getType()) {
$this->bookTaxationStrategy->processTaxation($item);
}
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
private ShirtTaxationStrategy $shirtTaxationStrategy;
private BookTaxationStrategy $bookTaxationStrategy;
public function __construct(
ShirtTaxationStrategy $shirtTaxationStrategy,
BookTaxationStrategy $bookTaxationStrategy
) {
$this->shirtTaxationStrategy = $shirtTaxationStrategy;
$this->bookTaxationStrategy = $bookTaxationStrategy;
}
public function processTaxation(CartItem $item): void
{
if ('shirt' === $item->getType()) {
$this->shirtTaxationStrategy->processTaxation($item);
}
if ('book' === $item->getType()) {
$this->bookTaxationStrategy->processTaxation($item);
}
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
private ShirtTaxationStrategy $shirtTaxationStrategy;
private BookTaxationStrategy $bookTaxationStrategy;
public function __construct(
ShirtTaxationStrategy $shirtTaxationStrategy,
BookTaxationStrategy $bookTaxationStrategy
) {
$this->shirtTaxationStrategy = $shirtTaxationStrategy;
$this->bookTaxationStrategy = $bookTaxationStrategy;
}
public function processTaxation(CartItem $item): void
{
if ('shirt' === $item->getType()) {
$this->shirtTaxationStrategy->processTaxation($item);
}
if ('book' === $item->getType()) {
$this->bookTaxationStrategy->processTaxation($item);
}
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
private array $indexedTaxationStrategies;
public function __construct(
ShirtTaxationStrategy $shirtTaxationStrategy,
BookTaxationStrategy $bookTaxationStrategy
) {
$this->indexedTaxationStrategies = [
'shirt' => $shirtTaxationStrategy,
'book' => $bookTaxationStrategy,
];
}
public function processTaxation(CartItem $item): void
{
$this->indexedTaxationStrategies[$item->getType()]->processTaxation($item);
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
private array $indexedTaxationStrategies;
public function __construct(
ShirtTaxationStrategy $shirtTaxationStrategy,
BookTaxationStrategy $bookTaxationStrategy
) {
$this->indexedTaxationStrategies = [
'shirt' => $shirtTaxationStrategy,
'book' => $bookTaxationStrategy,
];
}
public function processTaxation(CartItem $item): void
{
$this->indexedTaxationStrategies[$item->getType()]->processTaxation($item);
}
}
Can Symfony help us with it?
final class TaxationStrategyDelegator implements TaxationStrategy
{
private array $indexedTaxationStrategies;
public function __construct(
ShirtTaxationStrategy $shirtTaxationStrategy,
BookTaxationStrategy $bookTaxationStrategy
) {
$this->indexedTaxationStrategies = [
'shirt' => $shirtTaxationStrategy,
'book' => $bookTaxationStrategy,
];
}
public function processTaxation(CartItem $item): void
{
$this->indexedTaxationStrategies[$item->getType()]->processTaxation($item);
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
private array $indexedTaxationStrategies;
public function __construct(
ShirtTaxationStrategy $shirtTaxationStrategy,
BookTaxationStrategy $bookTaxationStrategy
) {
$this->indexedTaxationStrategies = [
'shirt' => $shirtTaxationStrategy,
'book' => $bookTaxationStrategy,
];
}
public function processTaxation(CartItem $item): void
{
$this->indexedTaxationStrategies[$item->getType()]->processTaxation($item);
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
private ServiceLocator $taxationStrategies;
public function __construct(ServiceLocator $taxationStrategies)
{
$this->taxationStrategies = $taxationStrategies;
}
public function processTaxation(CartItem $item): void
{
$this->taxationStrategies->get($item->getType())->processTaxation($item);
}
}
final class TaxationStrategyDelegator implements TaxationStrategy
{
private ServiceLocator $taxationStrategies;
public function __construct(ServiceLocator $taxationStrategies)
{
$this->taxationStrategies = $taxationStrategies;
}
public function processTaxation(CartItem $item): void
{
$this->taxationStrategies->get($item->getType())->processTaxation($item);
}
}
SymfonyDesignPatternsTaxationStrategiesTaxationStrategy:
class: 'SymfonyDesignPatternsTaxationStrategiesTaxationStrategyDelegator'
arguments:
- !tagged_locator { tag: 'app.taxation_strategies', index_by: 'type' }
SymfonyDesignPatternsTaxationStrategiesTaxationStrategy:
class: 'SymfonyDesignPatternsTaxationStrategiesTaxationStrategyDelegator'
arguments:
- !tagged_locator { tag: 'app.taxation_strategies', index_by: 'type' }
SymfonyDesignPatternsTaxationStrategiesBookTaxationStrategy:
tags:
- { name: 'app.taxation_strategies', type: 'book' }
SymfonyDesignPatternsTaxationStrategiesShirtTaxationStrategy:
tags:
- { name: 'app.taxation_strategies', type: 'shirt' }
LLoC AoC ACC
Sample project 40 1 8
Composite iteration 73 4 3
Strategy iteration 102 7 1.86
Single responsibility principle


Open close principle


Liskov substitution principle


Interface segregation principle


Dependency inversion principle
Time for small refactoring
final class CheckoutController
{
private array $repository = [];
private OrderProcessor $orderProcessor;
public function __construct(OrderProcessor $orderProcessor)
{
$this->orderProcessor = $orderProcessor;
}
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
$this->orderProcessor->processOrder($cart);
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
final class CheckoutController
{
private array $repository = [];
private OrderProcessor $orderProcessor;
public function __construct(OrderProcessor $orderProcessor)
{
$this->orderProcessor = $orderProcessor;
}
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
if (isset($this->repository[$options['cart_id']])) {
/** @var Cart $cart */
$cart = $this->repository[$options['cart_id']];
} else {
$cart = new Cart($options['cart_id']);
}
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
$this->orderProcessor->processOrder($cart);
// Save into repository
$this->repository[$options['cart_id']] = $cart;
return $cart;
}
}
final class CheckoutController
{
private OrderProcessor $orderProcessor;
private CartRepository $cartRepository;
public function __construct(OrderProcessor $orderProcessor, CartRepository $cartRepository)
{
$this->orderProcessor = $orderProcessor;
$this->cartRepository = $cartRepository;
}
public function cartAction(string $operation, array $options): Cart
{
// Fetch object from repository
$cart = $this->cartRepository->getCart($options['cart_id']);
// Perform operation
if ('add_to_cart' === $operation) {
$cart->addProduct($options['product'], $options['type'], $options['quantity'], $options['price']);
} elseif ('remove_from_cart' === $operation) {
$cart->removeProduct($options['product']);
} else {
throw new InvalidArgumentException('operation not supported');
}
$this->orderProcessor->processOrder($cart);
// Save into repository
$this->cartRepository->save($cart);
return $cart;
}
}