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; } }
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; } }
final class InMemoryCartRepository implements CartRepository { private array $repository = []; public function getCart(string $cart_id): Cart { if (isset($this->repository[$cart_id])) { /** @var Cart $cart */ $cart = $this->repository[$cart_id]; } else { $cart = new Cart($cart_id); } return $cart; } }
public function getCart(string $cart_id): Cart { if (!isset($this->repository[$cart_id])) { $this->repository[$cart_id] = new Cart($cart_id); } /** @var Cart $cart */ return $this->repository[$cart_id]; }
LLoC AoC ACC Sample project 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75
Command
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 $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->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 $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->repository[$options['cart_id']] = $cart; return $cart; } }
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) { $this->messageBus->dispatch(new AddToCart( $options['cart_id'], $options['product'], $options['type'], $options['quantity'], $options['price'] )); } elseif ('remove_from_cart' === $operation) { $this->messageBus->dispatch(new RemoveFromCart( $options['cart_id'], $options['product'], )); } else { throw new InvalidArgumentException('operation not supported'); } $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
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) { $this->messageBus->dispatch(new AddToCart( $options['cart_id'], $options['product'], $options['type'], $options['quantity'], $options['price'] )); } elseif ('remove_from_cart' === $operation) { $this->messageBus->dispatch(new RemoveFromCart( $options['cart_id'], $options['product'], )); } else { throw new InvalidArgumentException('operation not supported'); } $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
final class AddToCart { private string $cartId; private string $product; private string $type; private int $quantity; private int $price; public function __construct(...) { $this->cartId = $cartId; $this->product = $product; $this->type = $type; $this->quantity = $quantity; $this->price = $price; } }
final class AddToCartHandler implements MessageHandlerInterface { private CartRepository $cartRepository; public function __construct(CartRepository $cartRepository) { $this->cartRepository = $cartRepository; } public function __invoke(AddToCart $addToCart) { $cart = $this->cartRepository->getCart($addToCart->getCartId()); $cart->addProduct( $addToCart->getProduct(), $addToCart->getType(), $addToCart->getQuantity(), $addToCart->getPrice() ); $this->cartRepository->save($cart); } }
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) { $this->messageBus->dispatch(new AddToCart( $options['cart_id'], $options['product'], $options['type'], $options['quantity'], $options['price'] )); } elseif ('remove_from_cart' === $operation) { $this->messageBus->dispatch(new RemoveFromCart( $options['cart_id'], $options['product'], )); } else { throw new InvalidArgumentException('operation not supported'); } $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
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) { $this->messageBus->dispatch(new AddToCart( $options['cart_id'], $options['product'], $options['type'], $options['quantity'], $options['price'] )); } elseif ('remove_from_cart' === $operation) { $this->messageBus->dispatch(new RemoveFromCart( $options['cart_id'], $options['product'], )); } else { throw new InvalidArgumentException('operation not supported'); } $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
public function cartAction(CartIdAwareCommand $operation): Cart { // Fetch object from repository $cart = $this->cartRepository->getCart($operation->getCartId()); // Perform operation $this->messageBus->dispatch($operation); $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
public function cartAction(CartIdAwareCommand $operation): Cart { // Fetch object from repository $cart = $this->cartRepository->getCart($operation->getCartId()); // Perform operation $this->messageBus->dispatch($operation); $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
LLoC AoC ACC Sample project 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33
Decorator
final class AddToCartHandler implements MessageHandlerInterface { private CartRepository $cartRepository; public function __construct(CartRepository $cartRepository) { $this->cartRepository = $cartRepository; } public function __invoke(AddToCart $addToCart) { $cart = $this->cartRepository->getCart($addToCart->getCartId()); $cart->addProduct( $addToCart->getProduct(), $addToCart->getType(), $addToCart->getQuantity(), $addToCart->getPrice() ); $this->cartRepository->save($cart); } }
final class AddToCartHandler implements MessageHandlerInterface { private CartRepository $cartRepository; public function __construct(CartRepository $cartRepository) { $this->cartRepository = $cartRepository; } public function __invoke(AddToCart $addToCart) { $cart = $this->cartRepository->getCart($addToCart->getCartId()); $cart->addProduct( $addToCart->getProduct(), $addToCart->getType(), $addToCart->getQuantity(), $addToCart->getPrice() ); $this->cartRepository->save($cart); } }
final class AddToCartHandler implements MessageHandlerInterface { private CartRepository $cartRepository; public function __construct(CartRepository $cartRepository) { $this->cartRepository = $cartRepository; } public function __invoke(AddToCart $addToCart): Cart { $cart = $this->cartRepository->getCart($addToCart->getCartId()); $cart->addProduct( $addToCart->getProduct(), $addToCart->getType(), $addToCart->getQuantity(), $addToCart->getPrice() ); return $cart; } }
final class AddToCartHandler implements MessageHandlerInterface { private CartRepository $cartRepository; public function __construct(CartRepository $cartRepository) { $this->cartRepository = $cartRepository; } public function __invoke(AddToCart $addToCart): Cart { $cart = $this->cartRepository->getCart($addToCart->getCartId()); $cart->addProduct( $addToCart->getProduct(), $addToCart->getType(), $addToCart->getQuantity(), $addToCart->getPrice() ); return $cart; } }
final class TransactionDecorator { private MessageHandlerInterface $messageHandler; private CartRepository $cartRepository; public function __construct( MessageHandlerInterface $messageHandler, CartRepository $cartRepository ) { $this->messageHandler = $messageHandler; $this->cartRepository = $cartRepository; } public function __invoke(CartIdAwareCommand $cartIdAwareCommand) { // start transaction $cart = ($this->messageHandler)($cartIdAwareCommand); $this->cartRepository->save($cart); } }
Can Symfony help us with it?
SymfonyDesignPatternsDecoratorTransactionDecorator: decorates: SymfonyDesignPatternsCommandHandlerAddToCartHandler arguments: - '@.inner'
LLoC AoC ACC Sample project 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33 Decorator iteration 216 13 1.31
Take offs?
Trade offs!
LLoC AoC ACC Sample project 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33 Decorator iteration 216 13 1.31
LLoC AoC ACC Sample project 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33 Decorator iteration 216 13 1.31
LLoC AoC ACC Sample project 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33 Decorator iteration 216 13 1.31
LLoC AoC ACC Sample project 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33 Decorator iteration 216 13 1.31
Increased complexity
Increased complexity Increased testability
Increased complexity Increased testability Possibility for maintenance cost reduction
Symfony may help you a lot
@Sylius Thank you! @lukaszchrusciel

Symfony World - Symfony components and design patterns

  • 1.
  • 3.
  • 6.
  • 7.
  • 8.
  • 9.
    Communication Reduced amount ofcode in one chunk Increased cohesion and reduced coupling
  • 10.
    Communication Reduced amount ofcode in one chunk Increased cohesion and reduced coupling Extendibility
  • 11.
    Communication Reduced amount ofcode in one chunk Increased cohesion and reduced coupling Extendibility SOLID
  • 12.
  • 13.
  • 14.
  • 15.
    Avg. Logic Linesof Code LLoC
  • 16.
    Avg. Logic Linesof Code LLoC Amount of classes
  • 17.
    Avg. Logic Linesof Code LLoC Amount of classes AoC
  • 18.
    Avg. Logic Linesof Code LLoC Amount of classes AoC Avg. Cyclomatic Complexity
  • 19.
    Avg. Logic Linesof Code LLoC Amount of classes AoC Avg. Cyclomatic Complexity ACC
  • 20.
  • 21.
  • 22.
    final class CheckoutController { privatearray $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; } }
  • 23.
    final class CheckoutController { privatearray $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; }
  • 24.
    { // Fetch objectfrom 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
  • 25.
    final class CheckoutController { privatearray $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; } }
  • 26.
    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;
  • 27.
    final class CheckoutController { privatearray $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; } }
  • 28.
    final class CheckoutController { privatearray $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; } }
  • 29.
    Avg. Logic Linesof Code 40 Amount of classes 1 Avg. Cyclomatic Complexity 8
  • 30.
    LLoC AoC ACC Sampleproject 40 1 8
  • 31.
    Single responsibility principle Openclose principle Liskov substitution principle Interface segregation principle Dependency inversion principle
  • 32.
    Single responsibility principle Openclose principle Liskov substitution principle Interface segregation principle Dependency inversion principle
  • 33.
  • 34.
  • 35.
    final class CheckoutController { privatearray $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; } }
  • 36.
    $cart = newCart($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; } }
  • 37.
    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; } }
  • 38.
    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); } }
  • 39.
    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; } }
  • 40.
    final class CheckoutController { privatearray $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; } }
  • 41.
    private function processOrder(Cart$cart): void { // Apply promotion $this->applyPromotion($cart); // Apply taxation $this->applyTaxation($cart); }
  • 42.
    final class CheckoutController { privatearray $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); } }
  • 43.
    final class CheckoutController { privatearray $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; } }
  • 44.
    final class CheckoutController { privatearray $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; } }
  • 45.
  • 46.
    final class CheckoutController { privatearray $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; } }
  • 47.
    final class CompositeOrderProcessorimplements 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); } } }
  • 48.
    LLoC AoC ACC Sampleproject 40 1 8 Extracted processor 61 2 4.5
  • 50.
    final class CompositeOrderProcessorimplements 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); } } }
  • 51.
    final class CompositeOrderProcessorimplements 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); } } }
  • 52.
    final class OrderTaxationProcessorimplements 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); } } } }
  • 53.
    final class CompositeOrderProcessorimplements 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); } } }
  • 54.
    final class OrderPromotionProcessorimplements OrderProcessor { public function processOrder(Cart $cart): void { if ($cart->getPrice() >= 1000) { $cart->applyPercentageDiscount(0.1); } } }
  • 55.
    final class CompositeOrderProcessorimplements 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); } }
  • 56.
    LLoC AoC ACC Sampleproject 40 1 8 Extracted processor 61 2 4.5 Separated processors 76 4 2.75
  • 58.
    final class CompositeOrderProcessorimplements 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); } }
  • 59.
    final class CompositeOrderProcessorimplements 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); } } }
  • 60.
    final class CompositeOrderProcessorimplements 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); } } }
  • 61.
    final class CompositeOrderProcessorimplements 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); } } }
  • 62.
    final class CompositeOrderProcessorimplements 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); } } }
  • 63.
    final class CompositeOrderProcessorimplements 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); } } }
  • 64.
    final class CompositeOrderProcessorimplements 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); } } }
  • 65.
  • 66.
    Can Symfony helpus with it?
  • 67.
  • 68.
  • 69.
  • 70.
    LLoC AoC ACC Sampleproject 40 1 8 Extracted processor 61 2 4.5 Separated processors 76 4 2.75 Composite iteration 73 4 3
  • 71.
    Single responsibility principle Openclose principle Liskov substitution principle Interface segregation principle Dependency inversion principle
  • 72.
    LLoC AoC ACC Sampleproject 40 1 8 Composite iteration 73 4 3
  • 73.
  • 74.
    final class OrderTaxationProcessorimplements 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); } } } }
  • 75.
    final class OrderTaxationProcessorimplements 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); } } } }
  • 76.
    final class OrderTaxationProcessorimplements 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); } } }
  • 77.
    final class OrderTaxationProcessorimplements 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); } } }
  • 78.
    final class TaxationStrategyDelegatorimplements TaxationStrategy { public function processTaxation(CartItem $item): void { if ('shirt' === $item->getType()) { $item->setTaxationPercentage(0.23); } if ('book' === $item->getType()) { $item->setTaxationPercentage(0.08); } } }
  • 79.
    final class TaxationStrategyDelegatorimplements TaxationStrategy { public function processTaxation(CartItem $item): void { if ('shirt' === $item->getType()) { $item->setTaxationPercentage(0.23); } if ('book' === $item->getType()) { $item->setTaxationPercentage(0.08); } } }
  • 80.
    final class TaxationStrategyDelegatorimplements 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); } } }
  • 81.
    final class TaxationStrategyDelegatorimplements 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); } } }
  • 83.
    final class TaxationStrategyDelegatorimplements 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); } } }
  • 84.
    final class TaxationStrategyDelegatorimplements 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); } } }
  • 85.
    final class TaxationStrategyDelegatorimplements 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); } }
  • 86.
    final class TaxationStrategyDelegatorimplements 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); } }
  • 87.
    Can Symfony helpus with it?
  • 88.
    final class TaxationStrategyDelegatorimplements 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); } }
  • 89.
    final class TaxationStrategyDelegatorimplements 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); } }
  • 90.
    final class TaxationStrategyDelegatorimplements 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); } }
  • 91.
    final class TaxationStrategyDelegatorimplements 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); } }
  • 92.
  • 93.
  • 94.
    SymfonyDesignPatternsTaxationStrategiesBookTaxationStrategy: tags: - { name:'app.taxation_strategies', type: 'book' } SymfonyDesignPatternsTaxationStrategiesShirtTaxationStrategy: tags: - { name: 'app.taxation_strategies', type: 'shirt' }
  • 95.
    LLoC AoC ACC Sampleproject 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86
  • 96.
    Single responsibility principle Openclose principle Liskov substitution principle Interface segregation principle Dependency inversion principle
  • 97.
    Time for smallrefactoring
  • 98.
    final class CheckoutController { privatearray $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; } }
  • 99.
    final class CheckoutController { privatearray $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; } }
  • 100.
    final class CheckoutController { privateOrderProcessor $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; } }
  • 101.
    final class CheckoutController { privateOrderProcessor $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; } }
  • 102.
    final class InMemoryCartRepositoryimplements CartRepository { private array $repository = []; public function getCart(string $cart_id): Cart { if (isset($this->repository[$cart_id])) { /** @var Cart $cart */ $cart = $this->repository[$cart_id]; } else { $cart = new Cart($cart_id); } return $cart; } }
  • 103.
    public function getCart(string$cart_id): Cart { if (!isset($this->repository[$cart_id])) { $this->repository[$cart_id] = new Cart($cart_id); } /** @var Cart $cart */ return $this->repository[$cart_id]; }
  • 104.
    LLoC AoC ACC Sampleproject 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75
  • 105.
  • 106.
    final class CheckoutController { privatearray $repository = []; private OrderProcessor $orderProcessor; public function __construct(OrderProcessor $orderProcessor) { $this->orderProcessor = $orderProcessor; } 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->repository[$options['cart_id']] = $cart; return $cart; } }
  • 107.
    final class CheckoutController { privatearray $repository = []; private OrderProcessor $orderProcessor; public function __construct(OrderProcessor $orderProcessor) { $this->orderProcessor = $orderProcessor; } 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->repository[$options['cart_id']] = $cart; return $cart; } }
  • 108.
    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) { $this->messageBus->dispatch(new AddToCart( $options['cart_id'], $options['product'], $options['type'], $options['quantity'], $options['price'] )); } elseif ('remove_from_cart' === $operation) { $this->messageBus->dispatch(new RemoveFromCart( $options['cart_id'], $options['product'], )); } else { throw new InvalidArgumentException('operation not supported'); } $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
  • 109.
    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) { $this->messageBus->dispatch(new AddToCart( $options['cart_id'], $options['product'], $options['type'], $options['quantity'], $options['price'] )); } elseif ('remove_from_cart' === $operation) { $this->messageBus->dispatch(new RemoveFromCart( $options['cart_id'], $options['product'], )); } else { throw new InvalidArgumentException('operation not supported'); } $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
  • 110.
    final class AddToCart { privatestring $cartId; private string $product; private string $type; private int $quantity; private int $price; public function __construct(...) { $this->cartId = $cartId; $this->product = $product; $this->type = $type; $this->quantity = $quantity; $this->price = $price; } }
  • 111.
    final class AddToCartHandlerimplements MessageHandlerInterface { private CartRepository $cartRepository; public function __construct(CartRepository $cartRepository) { $this->cartRepository = $cartRepository; } public function __invoke(AddToCart $addToCart) { $cart = $this->cartRepository->getCart($addToCart->getCartId()); $cart->addProduct( $addToCart->getProduct(), $addToCart->getType(), $addToCart->getQuantity(), $addToCart->getPrice() ); $this->cartRepository->save($cart); } }
  • 112.
    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) { $this->messageBus->dispatch(new AddToCart( $options['cart_id'], $options['product'], $options['type'], $options['quantity'], $options['price'] )); } elseif ('remove_from_cart' === $operation) { $this->messageBus->dispatch(new RemoveFromCart( $options['cart_id'], $options['product'], )); } else { throw new InvalidArgumentException('operation not supported'); } $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
  • 113.
    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) { $this->messageBus->dispatch(new AddToCart( $options['cart_id'], $options['product'], $options['type'], $options['quantity'], $options['price'] )); } elseif ('remove_from_cart' === $operation) { $this->messageBus->dispatch(new RemoveFromCart( $options['cart_id'], $options['product'], )); } else { throw new InvalidArgumentException('operation not supported'); } $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
  • 114.
    public function cartAction(CartIdAwareCommand$operation): Cart { // Fetch object from repository $cart = $this->cartRepository->getCart($operation->getCartId()); // Perform operation $this->messageBus->dispatch($operation); $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
  • 115.
    public function cartAction(CartIdAwareCommand$operation): Cart { // Fetch object from repository $cart = $this->cartRepository->getCart($operation->getCartId()); // Perform operation $this->messageBus->dispatch($operation); $this->orderProcessor->processOrder($cart); // Save into repository $this->cartRepository->save($cart); return $cart; }
  • 116.
    LLoC AoC ACC Sampleproject 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33
  • 117.
  • 118.
    final class AddToCartHandlerimplements MessageHandlerInterface { private CartRepository $cartRepository; public function __construct(CartRepository $cartRepository) { $this->cartRepository = $cartRepository; } public function __invoke(AddToCart $addToCart) { $cart = $this->cartRepository->getCart($addToCart->getCartId()); $cart->addProduct( $addToCart->getProduct(), $addToCart->getType(), $addToCart->getQuantity(), $addToCart->getPrice() ); $this->cartRepository->save($cart); } }
  • 119.
    final class AddToCartHandlerimplements MessageHandlerInterface { private CartRepository $cartRepository; public function __construct(CartRepository $cartRepository) { $this->cartRepository = $cartRepository; } public function __invoke(AddToCart $addToCart) { $cart = $this->cartRepository->getCart($addToCart->getCartId()); $cart->addProduct( $addToCart->getProduct(), $addToCart->getType(), $addToCart->getQuantity(), $addToCart->getPrice() ); $this->cartRepository->save($cart); } }
  • 120.
    final class AddToCartHandlerimplements MessageHandlerInterface { private CartRepository $cartRepository; public function __construct(CartRepository $cartRepository) { $this->cartRepository = $cartRepository; } public function __invoke(AddToCart $addToCart): Cart { $cart = $this->cartRepository->getCart($addToCart->getCartId()); $cart->addProduct( $addToCart->getProduct(), $addToCart->getType(), $addToCart->getQuantity(), $addToCart->getPrice() ); return $cart; } }
  • 121.
    final class AddToCartHandlerimplements MessageHandlerInterface { private CartRepository $cartRepository; public function __construct(CartRepository $cartRepository) { $this->cartRepository = $cartRepository; } public function __invoke(AddToCart $addToCart): Cart { $cart = $this->cartRepository->getCart($addToCart->getCartId()); $cart->addProduct( $addToCart->getProduct(), $addToCart->getType(), $addToCart->getQuantity(), $addToCart->getPrice() ); return $cart; } }
  • 122.
    final class TransactionDecorator { privateMessageHandlerInterface $messageHandler; private CartRepository $cartRepository; public function __construct( MessageHandlerInterface $messageHandler, CartRepository $cartRepository ) { $this->messageHandler = $messageHandler; $this->cartRepository = $cartRepository; } public function __invoke(CartIdAwareCommand $cartIdAwareCommand) { // start transaction $cart = ($this->messageHandler)($cartIdAwareCommand); $this->cartRepository->save($cart); } }
  • 123.
    Can Symfony helpus with it?
  • 124.
  • 125.
    LLoC AoC ACC Sampleproject 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33 Decorator iteration 216 13 1.31
  • 126.
  • 127.
  • 128.
    LLoC AoC ACC Sampleproject 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33 Decorator iteration 216 13 1.31
  • 129.
    LLoC AoC ACC Sampleproject 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33 Decorator iteration 216 13 1.31
  • 130.
    LLoC AoC ACC Sampleproject 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33 Decorator iteration 216 13 1.31
  • 131.
    LLoC AoC ACC Sampleproject 40 1 8 Composite iteration 73 4 3 Strategy iteration 102 7 1.86 Repository extraction 125 8 1.75 Command iteration 209 12 1.33 Decorator iteration 216 13 1.31
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.