SlideShare ist ein Scribd-Unternehmen logo
1 von 34
Downloaden Sie, um offline zu lesen
SPECIFICATION
A Software Design Pattern
Problem solver,

Architect, Leader
Robert Šorn
SPECIFICATION
A Software Design Pattern
— DISCLAIMER —
— WHAT IS A SPECIFICATION? —
Design pattern
used to codify business rules
that state something about an object
Cheap to
WRITE

MAINTAIN
Complex
LOGIC
Easy to
TEST
To encapsulate a business rule which does not belong inside entities or value, but is applied to it.
— WHY TO USE A SPECIFICATION? —
To make a business rule explicit in the codebase.
To make a business rule a first-class citizen of your codebase - domain.
Assertion
Build
to order
Selection
— HOW TO USE A SPECIFICATION? —
— ASSERTION —
interface CustomerSpecification
{
public function isSatisfiedBy(Customer $customer) : bool;
}
final class CustomerIsPremium implements CustomerSpecification
{
private $orderRepository;
public function __construct(OrderRepositoryInterface $orderRepository)
{
$this->orderRepository = $orderRepository;
}
public function isSatisfiedBy(Customer $customer) : bool
{
return $this->orderRepository->countFor($customer) > 3;
}
}
$customer = $customerRepository->findById(42);
$spec = new CustomerIsPremium($orderRepository);
$spec->isSatisfiedBy($customer);
— ASSERTION - COMPOSITE - AND —
final class AndSpecification extends AbstractSpecification implements SpecificationInterface
{
/** @var AbstractSpecification */
private $one;
/** @var AbstractSpecification */
private $two;
/**
* @param AbstractSpecification $one
* @param AbstractSpecification $two
*/
public function __construct(AbstractSpecification $one, AbstractSpecification $two)
{
$this->one = $one;
$this->two = $two;
}
/**
* @param mixed $object
*/
public function isSatisfiedBy($object) : bool
{
return $this->one->isSatisfiedBy($object) && $this->two->isSatisfiedBy($object);
}
}
— ASSERTION - COMPOSITE - OR —
final class OrSpecification extends AbstractSpecification implements SpecificationInterface
{
/** @var AbstractSpecification */
private $one;
/** @var AbstractSpecification */
private $two;
/**
* @param AbstractSpecification $one
* @param AbstractSpecification $two
*/
public function __construct(AbstractSpecification $one, AbstractSpecification $two)
{
$this->one = $one;
$this->two = $two;
}
/**
* @param mixed $object
*/
public function isSatisfiedBy($object) : bool
{
return $this->one->isSatisfiedBy($object) || $this->two->isSatisfiedBy($object);
}
}
— ASSERTION - COMPOSITE - NOT —
final class NotSpecification extends AbstractSpecification implements SpecificationInterface
{
/** @var AbstractSpecification */
private $spec;
/**
* @param AbstractSpecification $spec
*/
public function __construct(AbstractSpecification $spec)
{
$this->spec = $spec;
}
/**
* @param mixed $object
*/
public function isSatisfiedBy($object) : bool

{
return !$this->spec->isSatisfiedBy($object);
}
}
— ASSERTION - COMPOSITE —
$specAnd = new AndSpecification(
new CustomerIsPremium($orderRepository),
new CustomerHasOverdueInvoices($invoiceRepository)
);
$specAnd->isSatisfiedBy($customer);
$specOr = new OrSpecification(
new CustomerIsPremium($orderRepository),
new CustomerHasOverdueInvoices($invoiceRepository)
);
$specOr->isSatisfiedBy($customer);
$CustomerIsNotPremium = new NotSpecification(
new CustomerIsPremium($orderRepository)
);
$specNot->isSatisfiedBy($customer);
— ASSERTION - COMPOSITE - PIPE IT BABY —
abstract class AbstractSpecification
{
/**
* @param AbstractSpecification $other
* @return SpecificationInterface
*/
public function andSpecification(AbstractSpecification $other)
{
return new AndSpecification($this, $other);
}
/**
* @param AbstractSpecification $other
* @return SpecificationInterface
*/
public function orSpecification(AbstractSpecification $other)
{
return new OrSpecification($this, $other);
}
/**
* @return SpecificationInterface
*/
public function notSpecification()
{
return new NotSpecification($this);
}
}
Build
to order
Assertion Selection
— SELECTION —
— SELECTION —
interface SqlSpecification
{
/** @return string */
public function asSql();
}
final class CustomerIsPremium implements CustomerSpecification, SqlSpecification
{
// ...
/** @return string */
public function asSql()
{
return "SELECT * FROM customers LEFT JOIN orders ON ...";
}
}
— SELECTION —
interface RepositoryCompatibleSpecification
{
public function applyToRepository(SpecificationReceivableRepository $repo);
}
interface RepositoryCompatibleSpecification
{
public function getRecordsMatching(RepositoryCompatibleSpecification $spec) : array;
}
— SELECTION —
EXAMPLE OF SPECIFICATION USED FOR SELECTION WITH DOCTRINE
https://github.com/Happyr/Doctrine-Specification
— SELECTION - COMPOSITE - PIPE —
Build
to order
Assertion Selection
— BUILD TO ORDER —
— BUILD TO ORDER —
API BasketEvents
— BUILD TO ORDER —
Ad TransactionBasket
— BUILD TO ORDER —
Entity vs Value
— BUILD TO ORDER —
final class Basket
{
private $products = array();
private $amount = 0.0;
public function __construct(array $products = array())
{
foreach ($products as $product) {
$this->products[$product->getProductId()] = $product;
$this->amount += $product->getPrice();
}
}
public function hasProduct(Product $product)
{
return isset($this->products[$product->getProductId()]);
}
public function getAmount()
{
return $this->amount;
}
public function getProducts()
{
return $this->products;
}
}
— BUILD TO ORDER —
class BasketOperationService
{
public function addProductToBasket(Basket $basket, Product $product)
{
$products = $basket->getProducts();
$products[$product->getProductId()] = $product;
$newBasket = new Basket($products);
return $newBasket;
}
public function removeProductFromBasket(Basket $basket, Product $product)
{
$products = $basket->getProducts();
unset($products[$product->getProductId()]);
$newBasket = new Basket($products);
return $newBasket;
}
}
— BUILD TO ORDER —
class ProductIsInBasketSpecification
{
public function isSatisfiedBy(Basket $basket, Product $product)
{
return $basket->hasProduct($product);
}
}
— BUILD TO ORDER —
class BasketCanBeBoughtSpecification
{
public function isSatisfiedBy(Basket $basket, User $user)
{
if ($basket->getAmount() <= $user->getCurrentBalanceAmount()) {
return true;
}
return false;
}
}
— BUILD TO ORDER —
final class TriedToAddProductToBasketEvent
{
private $basket;
private $product;
private $user;
public function __construct(Basket $basket, Product $product, User $user)
{
$this->basket = $basket;
$this->product = $product;
$this->user = $user;
}
public function getProduct()
{
return $this->product;
}
public function getBasket()
{
return $this->basket;
}
public function getUser()
{
return $this->user;
}
}
— BUILD TO ORDER —
— BUILD TO ORDER —
class TriedToAddProductToBasketEventSubscriber
{
public function onTriedToAddProductToBasketEvent(TriedToAddProductToBasketEvent $event, EventDispatcher $dispatcher)
{
$basket = $event->getBasket();
$product = $event->getProduct();
$user = $event->getUser();
$communicatorService = $dispatcher->getFrontendCommunicatorService();
$productIsInBasketSpecification = new ProductIsInBasketSpecification();
if ($productIsInBasketSpecification->isSatisfiedBy($basket, $product)) {
$communicatorService->showError('Product already in basket!', $product);
} else {
$basketOperationService = new BasketOperationService();
$newBasket = $basketOperationService->addProductToBasket($basket, $product);
$basketCanBeBoughtSpecification = new BasketCanBeBoughtSpecification();
if ($basketCanBeBoughtSpecification->isSatisfiedBy($newBasket, $user)) {
/* ... Replace $basket with $newBasket in DIC for further events to use it ... */
$communicatorService->showProductInBasket($product);
$communicatorService->setTotalPrice($newBasket->getAmount());
} else {
$communicatorService->showError('Not enough funds!', $product);
}
}
}
}
— BUILD TO ORDER —
— BUILD TO ORDER —
— FURTHER READING —
“THE BLUE BOOK”
https://www.amazon.com/gp/product/B00794TAUG/
EXAMPLE OF SPECIFICATION USED FOR SELECTION WITH DOCTRINE
https://github.com/Happyr/Doctrine-Specification
BLOG POST 4 QUICK PEEK
http://marcaube.ca/2015/05/specifications
MARTIN FOWLER
https://www.martinfowler.com/apsupp/spec.pdf
“THE YELLOW BOOK”
https://leanpub.com/ddd-in-php?a=Ug88MJbcykCAu8AEAWNjDA
Q&A
Robert Šorn
COO @ TRIKODER
robert.sorn@trikoder.net
ks, ks, BTW, Trikoder is hiring! ;)
Head of PM, PHP developers, Java developers, UX/UI specialist, …
work@trikoder.net
THANK YOU
Robert Šorn
COO @ TRIKODER
robert.sorn@trikoder.net

Weitere ähnliche Inhalte

Was ist angesagt?

Gravity Forms Hooks & Filters
Gravity Forms Hooks & FiltersGravity Forms Hooks & Filters
Gravity Forms Hooks & Filters
iamdangavin
 
Instant Dynamic Forms with #states
Instant Dynamic Forms with #statesInstant Dynamic Forms with #states
Instant Dynamic Forms with #states
Konstantin Käfer
 

Was ist angesagt? (20)

Leveraging Symfony2 Forms
Leveraging Symfony2 FormsLeveraging Symfony2 Forms
Leveraging Symfony2 Forms
 
Gravity Forms Hooks & Filters
Gravity Forms Hooks & FiltersGravity Forms Hooks & Filters
Gravity Forms Hooks & Filters
 
WordPress overloading Gravityforms using hooks, filters and extending classes
WordPress overloading Gravityforms using hooks, filters and extending classes WordPress overloading Gravityforms using hooks, filters and extending classes
WordPress overloading Gravityforms using hooks, filters and extending classes
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 
Oop php 5
Oop php 5Oop php 5
Oop php 5
 
Dutch php a short tale about state machine
Dutch php   a short tale about state machineDutch php   a short tale about state machine
Dutch php a short tale about state machine
 
11. CodeIgniter vederea unei singure inregistrari
11. CodeIgniter vederea unei singure inregistrari11. CodeIgniter vederea unei singure inregistrari
11. CodeIgniter vederea unei singure inregistrari
 
D8 Form api
D8 Form apiD8 Form api
D8 Form api
 
Rails <form> Chronicle
Rails <form> ChronicleRails <form> Chronicle
Rails <form> Chronicle
 
WordCamp Denver 2012 - Custom Meta Boxes
WordCamp Denver 2012 - Custom Meta BoxesWordCamp Denver 2012 - Custom Meta Boxes
WordCamp Denver 2012 - Custom Meta Boxes
 
Unbreakable Domain Models - DPC13
Unbreakable Domain Models - DPC13Unbreakable Domain Models - DPC13
Unbreakable Domain Models - DPC13
 
The Beautiful Simplicity of ES2015
The Beautiful Simplicity of ES2015The Beautiful Simplicity of ES2015
The Beautiful Simplicity of ES2015
 
Writing Sensible Code
Writing Sensible CodeWriting Sensible Code
Writing Sensible Code
 
Zend framework 04 - forms
Zend framework 04 - formsZend framework 04 - forms
Zend framework 04 - forms
 
Coding for Scale and Sanity
Coding for Scale and SanityCoding for Scale and Sanity
Coding for Scale and Sanity
 
Taming forms with React
Taming forms with ReactTaming forms with React
Taming forms with React
 
Instant Dynamic Forms with #states
Instant Dynamic Forms with #statesInstant Dynamic Forms with #states
Instant Dynamic Forms with #states
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
 
Form demoinplaywithmysql
Form demoinplaywithmysqlForm demoinplaywithmysql
Form demoinplaywithmysql
 
Learning the basics of the Drupal API
Learning the basics of the Drupal APILearning the basics of the Drupal API
Learning the basics of the Drupal API
 

Ähnlich wie ZG PHP - Specification

Ähnlich wie ZG PHP - Specification (20)

Zend Framework and the Doctrine2 MongoDB ODM (ZF1)
Zend Framework and the Doctrine2 MongoDB ODM (ZF1)Zend Framework and the Doctrine2 MongoDB ODM (ZF1)
Zend Framework and the Doctrine2 MongoDB ODM (ZF1)
 
Migrating to dependency injection
Migrating to dependency injectionMigrating to dependency injection
Migrating to dependency injection
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
[PHPCon 2023] “Kto to pisał?!... a, to ja.”, czyli sposoby żeby znienawidzić ...
[PHPCon 2023] “Kto to pisał?!... a, to ja.”, czyli sposoby żeby znienawidzić ...[PHPCon 2023] “Kto to pisał?!... a, to ja.”, czyli sposoby żeby znienawidzić ...
[PHPCon 2023] “Kto to pisał?!... a, to ja.”, czyli sposoby żeby znienawidzić ...
 
PHP: 4 Design Patterns to Make Better Code
PHP: 4 Design Patterns to Make Better CodePHP: 4 Design Patterns to Make Better Code
PHP: 4 Design Patterns to Make Better Code
 
Lessons learned from functional programming
Lessons learned from functional programmingLessons learned from functional programming
Lessons learned from functional programming
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Patterns in PHP
Patterns in PHPPatterns in PHP
Patterns in PHP
 
Revisiting SOLID Principles
Revisiting  SOLID Principles Revisiting  SOLID Principles
Revisiting SOLID Principles
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for PerformanceMeet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
 
Android Design Patterns
Android Design PatternsAndroid Design Patterns
Android Design Patterns
 
Laravel Design Patterns
Laravel Design PatternsLaravel Design Patterns
Laravel Design Patterns
 
Practical Event Sourcing
Practical Event SourcingPractical Event Sourcing
Practical Event Sourcing
 
Doctrine and NoSQL
Doctrine and NoSQLDoctrine and NoSQL
Doctrine and NoSQL
 
Key Insights into Development Design Patterns for Magento 2 - Magento Live UK
Key Insights into Development Design Patterns for Magento 2 - Magento Live UKKey Insights into Development Design Patterns for Magento 2 - Magento Live UK
Key Insights into Development Design Patterns for Magento 2 - Magento Live UK
 
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 

Kürzlich hochgeladen

Jax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined DeckJax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined Deck
Marc Lester
 

Kürzlich hochgeladen (20)

AI Hackathon.pptx
AI                        Hackathon.pptxAI                        Hackathon.pptx
AI Hackathon.pptx
 
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdfThe Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
 
What need to be mastered as AI-Powered Java Developers
What need to be mastered as AI-Powered Java DevelopersWhat need to be mastered as AI-Powered Java Developers
What need to be mastered as AI-Powered Java Developers
 
Lessons Learned from Building a Serverless Notifications System.pdf
Lessons Learned from Building a Serverless Notifications System.pdfLessons Learned from Building a Serverless Notifications System.pdf
Lessons Learned from Building a Serverless Notifications System.pdf
 
SQL Injection Introduction and Prevention
SQL Injection Introduction and PreventionSQL Injection Introduction and Prevention
SQL Injection Introduction and Prevention
 
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
 
Jax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined DeckJax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined Deck
 
A Guideline to Zendesk to Re:amaze Data Migration
A Guideline to Zendesk to Re:amaze Data MigrationA Guideline to Zendesk to Re:amaze Data Migration
A Guideline to Zendesk to Re:amaze Data Migration
 
Automate your OpenSIPS config tests - OpenSIPS Summit 2024
Automate your OpenSIPS config tests - OpenSIPS Summit 2024Automate your OpenSIPS config tests - OpenSIPS Summit 2024
Automate your OpenSIPS config tests - OpenSIPS Summit 2024
 
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
 
Crafting the Perfect Measurement Sheet with PLM Integration
Crafting the Perfect Measurement Sheet with PLM IntegrationCrafting the Perfect Measurement Sheet with PLM Integration
Crafting the Perfect Measurement Sheet with PLM Integration
 
GraphSummit Stockholm - Neo4j - Knowledge Graphs and Product Updates
GraphSummit Stockholm - Neo4j - Knowledge Graphs and Product UpdatesGraphSummit Stockholm - Neo4j - Knowledge Graphs and Product Updates
GraphSummit Stockholm - Neo4j - Knowledge Graphs and Product Updates
 
Weeding your micro service landscape.pdf
Weeding your micro service landscape.pdfWeeding your micro service landscape.pdf
Weeding your micro service landscape.pdf
 
Secure Software Ecosystem Teqnation 2024
Secure Software Ecosystem Teqnation 2024Secure Software Ecosystem Teqnation 2024
Secure Software Ecosystem Teqnation 2024
 
KLARNA - Language Models and Knowledge Graphs: A Systems Approach
KLARNA -  Language Models and Knowledge Graphs: A Systems ApproachKLARNA -  Language Models and Knowledge Graphs: A Systems Approach
KLARNA - Language Models and Knowledge Graphs: A Systems Approach
 
Modern binary build systems - PyCon 2024
Modern binary build systems - PyCon 2024Modern binary build systems - PyCon 2024
Modern binary build systems - PyCon 2024
 
Anypoint Code Builder - Munich MuleSoft Meetup - 16th May 2024
Anypoint Code Builder - Munich MuleSoft Meetup - 16th May 2024Anypoint Code Builder - Munich MuleSoft Meetup - 16th May 2024
Anypoint Code Builder - Munich MuleSoft Meetup - 16th May 2024
 
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdf
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdfImplementing KPIs and Right Metrics for Agile Delivery Teams.pdf
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdf
 
How to install and activate eGrabber JobGrabber
How to install and activate eGrabber JobGrabberHow to install and activate eGrabber JobGrabber
How to install and activate eGrabber JobGrabber
 
OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024
 

ZG PHP - Specification

  • 5. — WHAT IS A SPECIFICATION? — Design pattern used to codify business rules that state something about an object
  • 6. Cheap to WRITE
 MAINTAIN Complex LOGIC Easy to TEST To encapsulate a business rule which does not belong inside entities or value, but is applied to it. — WHY TO USE A SPECIFICATION? — To make a business rule explicit in the codebase. To make a business rule a first-class citizen of your codebase - domain.
  • 7. Assertion Build to order Selection — HOW TO USE A SPECIFICATION? —
  • 8. — ASSERTION — interface CustomerSpecification { public function isSatisfiedBy(Customer $customer) : bool; } final class CustomerIsPremium implements CustomerSpecification { private $orderRepository; public function __construct(OrderRepositoryInterface $orderRepository) { $this->orderRepository = $orderRepository; } public function isSatisfiedBy(Customer $customer) : bool { return $this->orderRepository->countFor($customer) > 3; } } $customer = $customerRepository->findById(42); $spec = new CustomerIsPremium($orderRepository); $spec->isSatisfiedBy($customer);
  • 9. — ASSERTION - COMPOSITE - AND — final class AndSpecification extends AbstractSpecification implements SpecificationInterface { /** @var AbstractSpecification */ private $one; /** @var AbstractSpecification */ private $two; /** * @param AbstractSpecification $one * @param AbstractSpecification $two */ public function __construct(AbstractSpecification $one, AbstractSpecification $two) { $this->one = $one; $this->two = $two; } /** * @param mixed $object */ public function isSatisfiedBy($object) : bool { return $this->one->isSatisfiedBy($object) && $this->two->isSatisfiedBy($object); } }
  • 10. — ASSERTION - COMPOSITE - OR — final class OrSpecification extends AbstractSpecification implements SpecificationInterface { /** @var AbstractSpecification */ private $one; /** @var AbstractSpecification */ private $two; /** * @param AbstractSpecification $one * @param AbstractSpecification $two */ public function __construct(AbstractSpecification $one, AbstractSpecification $two) { $this->one = $one; $this->two = $two; } /** * @param mixed $object */ public function isSatisfiedBy($object) : bool { return $this->one->isSatisfiedBy($object) || $this->two->isSatisfiedBy($object); } }
  • 11. — ASSERTION - COMPOSITE - NOT — final class NotSpecification extends AbstractSpecification implements SpecificationInterface { /** @var AbstractSpecification */ private $spec; /** * @param AbstractSpecification $spec */ public function __construct(AbstractSpecification $spec) { $this->spec = $spec; } /** * @param mixed $object */ public function isSatisfiedBy($object) : bool
 { return !$this->spec->isSatisfiedBy($object); } }
  • 12. — ASSERTION - COMPOSITE — $specAnd = new AndSpecification( new CustomerIsPremium($orderRepository), new CustomerHasOverdueInvoices($invoiceRepository) ); $specAnd->isSatisfiedBy($customer); $specOr = new OrSpecification( new CustomerIsPremium($orderRepository), new CustomerHasOverdueInvoices($invoiceRepository) ); $specOr->isSatisfiedBy($customer); $CustomerIsNotPremium = new NotSpecification( new CustomerIsPremium($orderRepository) ); $specNot->isSatisfiedBy($customer);
  • 13. — ASSERTION - COMPOSITE - PIPE IT BABY — abstract class AbstractSpecification { /** * @param AbstractSpecification $other * @return SpecificationInterface */ public function andSpecification(AbstractSpecification $other) { return new AndSpecification($this, $other); } /** * @param AbstractSpecification $other * @return SpecificationInterface */ public function orSpecification(AbstractSpecification $other) { return new OrSpecification($this, $other); } /** * @return SpecificationInterface */ public function notSpecification() { return new NotSpecification($this); } }
  • 15. — SELECTION — interface SqlSpecification { /** @return string */ public function asSql(); } final class CustomerIsPremium implements CustomerSpecification, SqlSpecification { // ... /** @return string */ public function asSql() { return "SELECT * FROM customers LEFT JOIN orders ON ..."; } }
  • 16. — SELECTION — interface RepositoryCompatibleSpecification { public function applyToRepository(SpecificationReceivableRepository $repo); } interface RepositoryCompatibleSpecification { public function getRecordsMatching(RepositoryCompatibleSpecification $spec) : array; }
  • 17. — SELECTION — EXAMPLE OF SPECIFICATION USED FOR SELECTION WITH DOCTRINE https://github.com/Happyr/Doctrine-Specification
  • 18. — SELECTION - COMPOSITE - PIPE —
  • 20. — BUILD TO ORDER —
  • 22. Ad TransactionBasket — BUILD TO ORDER — Entity vs Value
  • 23. — BUILD TO ORDER — final class Basket { private $products = array(); private $amount = 0.0; public function __construct(array $products = array()) { foreach ($products as $product) { $this->products[$product->getProductId()] = $product; $this->amount += $product->getPrice(); } } public function hasProduct(Product $product) { return isset($this->products[$product->getProductId()]); } public function getAmount() { return $this->amount; } public function getProducts() { return $this->products; } }
  • 24. — BUILD TO ORDER — class BasketOperationService { public function addProductToBasket(Basket $basket, Product $product) { $products = $basket->getProducts(); $products[$product->getProductId()] = $product; $newBasket = new Basket($products); return $newBasket; } public function removeProductFromBasket(Basket $basket, Product $product) { $products = $basket->getProducts(); unset($products[$product->getProductId()]); $newBasket = new Basket($products); return $newBasket; } }
  • 25. — BUILD TO ORDER — class ProductIsInBasketSpecification { public function isSatisfiedBy(Basket $basket, Product $product) { return $basket->hasProduct($product); } }
  • 26. — BUILD TO ORDER — class BasketCanBeBoughtSpecification { public function isSatisfiedBy(Basket $basket, User $user) { if ($basket->getAmount() <= $user->getCurrentBalanceAmount()) { return true; } return false; } }
  • 27. — BUILD TO ORDER — final class TriedToAddProductToBasketEvent { private $basket; private $product; private $user; public function __construct(Basket $basket, Product $product, User $user) { $this->basket = $basket; $this->product = $product; $this->user = $user; } public function getProduct() { return $this->product; } public function getBasket() { return $this->basket; } public function getUser() { return $this->user; } }
  • 28. — BUILD TO ORDER —
  • 29. — BUILD TO ORDER — class TriedToAddProductToBasketEventSubscriber { public function onTriedToAddProductToBasketEvent(TriedToAddProductToBasketEvent $event, EventDispatcher $dispatcher) { $basket = $event->getBasket(); $product = $event->getProduct(); $user = $event->getUser(); $communicatorService = $dispatcher->getFrontendCommunicatorService(); $productIsInBasketSpecification = new ProductIsInBasketSpecification(); if ($productIsInBasketSpecification->isSatisfiedBy($basket, $product)) { $communicatorService->showError('Product already in basket!', $product); } else { $basketOperationService = new BasketOperationService(); $newBasket = $basketOperationService->addProductToBasket($basket, $product); $basketCanBeBoughtSpecification = new BasketCanBeBoughtSpecification(); if ($basketCanBeBoughtSpecification->isSatisfiedBy($newBasket, $user)) { /* ... Replace $basket with $newBasket in DIC for further events to use it ... */ $communicatorService->showProductInBasket($product); $communicatorService->setTotalPrice($newBasket->getAmount()); } else { $communicatorService->showError('Not enough funds!', $product); } } } }
  • 30. — BUILD TO ORDER —
  • 31. — BUILD TO ORDER —
  • 32. — FURTHER READING — “THE BLUE BOOK” https://www.amazon.com/gp/product/B00794TAUG/ EXAMPLE OF SPECIFICATION USED FOR SELECTION WITH DOCTRINE https://github.com/Happyr/Doctrine-Specification BLOG POST 4 QUICK PEEK http://marcaube.ca/2015/05/specifications MARTIN FOWLER https://www.martinfowler.com/apsupp/spec.pdf “THE YELLOW BOOK” https://leanpub.com/ddd-in-php?a=Ug88MJbcykCAu8AEAWNjDA
  • 33. Q&A Robert Šorn COO @ TRIKODER robert.sorn@trikoder.net ks, ks, BTW, Trikoder is hiring! ;) Head of PM, PHP developers, Java developers, UX/UI specialist, … work@trikoder.net
  • 34. THANK YOU Robert Šorn COO @ TRIKODER robert.sorn@trikoder.net