zero to solid
TRANSCRIPT
Zero to SOLIDin 45 Minutes
by Vic Metcalfe@v_metcalfe
I made an e-Commercesite in PHP!
Children or those feint of heart are warned to
leave the room…
<?phpsession_start();$connection = new \PDO('mysql:host=localhost;dbname=solid', 'root', '');if (!isset($_SESSION['cart'])) { $connection->exec("INSERT INTO cart () VALUES ()"); $_SESSION['cart'] = $connection->lastInsertId();}
if (isset($_POST['addproduct'])) { $sql = "INSERT INTO cartitem (cart, product, quantity) VALUES (:cart, :product, :quantity) ON DUPLICATE KEY UPDATE quantity = quantity + :quantity"; $parameters = [ 'cart' => $_SESSION['cart'], 'product' => $_POST['addproduct'], 'quantity' => $_POST['quantity'], ]; $statement = $connection->prepare($sql); $statement->execute($parameters);}
if (isset($_POST['update'])) { $sql = "UPDATE cartitem SET quantity=:quantity WHERE cart=:cart and product=:product"; $parameters = [ 'cart' => $_SESSION['cart'], 'product' => $_POST['update'], 'quantity' => $_POST['quantity'], ]; $statement = $connection->prepare($sql); $statement->execute($parameters);}
https://github.com/zymsys/solid/blob/00/cart.php
$statement = $connection->prepare("SELECT * FROM cartitem WHERE cart=:cart AND quantity <> 0");$statement->execute(['cart' => $_SESSION['cart']]);$cartItems = $statement->fetchAll();?><!doctype html><html lang="en"><head> <meta charset="UTF-8"> <title>GTA-PHP Gift Shop</title> <link rel="stylesheet" href="site.css"></head><body><div class="container"> <h1>GTA-PHP Gift Shop</h1> <p>Buy our junk to keep our organizers up to date with the latest gadgets.</p> <table class="table"> <tr> <th>Product Name</th> <th>You Pay</th> <th>Group Gets</th> <th><!-- Column for add to cart button --></th> </tr> <?php $products = []; $result = $connection->query("SELECT * FROM product"); foreach ($result as $product) { $products[$product['id']] = $product; ?> <tr> <td><?php echo $product['name']; ?></td>
https://github.com/zymsys/solid/blob/00/cart.php
<td><?php $price = $product['price']; echo number_format($price / 100, 2); ?></td> <td> <?php echo number_format( ($product['price'] - $product['cost']) / 100, 2 ); ?> </td> <td> <form method="post"> <input type="number" name="quantity" value="1" style="width: 3em"> <input type="hidden" name="addproduct" value="<?php echo $product['id']; ?>"> <input class="btn btn-default btn-xs" type="submit" value="Add to Cart"> </form> </td> </tr> <?php } ?> </table> <?php if (count($cartItems) > 0): ?> <?php $total = 0; $taxable = 0;
$provinceCode = isset($_GET['province']) ? $_GET['province'] : 'ON'; //Default to GTA-PHP's home $provinces = [];
https://github.com/zymsys/solid/blob/00/cart.php
$result = $connection->query("SELECT * FROM province ORDER BY name"); foreach ($result as $row) { $provinces[$row['code']] = $row; if ($row['code'] === $provinceCode) { $province = $row; } } ?> <h2>Your Cart:</h2> <table class="table"> <?php foreach ($cartItems as $cartItem): ?> <?php $product = $products[$cartItem['product']]; ?> <tr> <td> <?php echo $product['name']; ?> </td> <td> <form method="post"> Quantity: <input type="hidden" name="update" value="<?php echo $product['id']; ?>"> <input type="number" name="quantity" style="width: 3em" value="<?php echo $cartItem['quantity']; ?>"> <button type="submit">Update</button> </form> </td> <td> <?php echo number_format( $cartItem['quantity'] * $product['price'] / 100, 2 ); $itemTotal = $cartItem['quantity'] * $product['price'];
https://github.com/zymsys/solid/blob/00/cart.php
$total += $itemTotal; $taxable += $product['taxes'] ? $itemTotal : 0; ?> </td> </tr> <?php endforeach; ?> <tr> <td><!-- Name --></td> <td style="text-align: right">Subtotal:</td> <td><?php echo number_format($total / 100, 2); ?></td> </tr> <tr> <td><!-- Name --></td> <td style="text-align: right"> <?php echo $province['name']; ?> taxes at <?php echo $province['taxrate'] ?>%:</td> <td> <?php $taxes = $taxable * $province['taxrate'] / 100; $total += $taxes; echo number_format($taxes / 100, 2); ?> </td> </tr> <tr> <td><!-- Name --></td> <td style="text-align: right">Total:</td> <td><?php echo number_format($total / 100, 2); ?></td> </tr> </table> <form method="get"> Calculate taxes for purchase from: <select name="province">
https://github.com/zymsys/solid/blob/00/cart.php
<?php foreach ($provinces as $province): ?> <?php $selected = $provinceCode === $province['code'] ? 'selected' : ''; ?> <option value="<?php echo $province['code']; ?>" <?php echo $selected; ?>> <?php echo $province['name']; ?> </option> <?php endforeach; ?> </select> <button type="submit" class="btn btn-default btn-xs"> Recalculate</button> </form> <form action="checkout.php" method="post"> <?php foreach ($cartItems as $itemNumber => $cartItem): ?> <?php $product = $products[$cartItem['product']]; ?> <input type="hidden" name="item<?php echo $itemNumber; ?>" value="<?php echo $product['name'] . '|' . number_format($product['price'] / 100, 2); ?>"> <?php endforeach; ?> <input type="hidden" name="item<?php echo count($cartItems); ?>" value="<?php echo 'Tax|' . number_format($taxes / 100, 2); ?>"> <button type="submit" class="btn btn-primary" style="float: right"> Checkout </button> </form> <?php endif; ?></div></body></html>
https://github.com/zymsys/solid/blob/00/cart.php
Is there anything wrong with this?
Step 1:Separate PHP from
HTML
<?phpfunction initialize(){ global $connection;
session_start(); $connection = new \PDO('mysql:host=localhost;dbname=solid', 'root', ''); if (!isset($_SESSION['cart'])) { $connection->exec("INSERT INTO cart () VALUES ()"); $_SESSION['cart'] = $connection->lastInsertId(); }}
function handleAdd(){ global $connection;
if (!isset($_POST['addproduct'])) { return; } $sql = "INSERT INTO cartitem (cart, product, quantity) VALUES (:cart, :product, :quantity) ON DUPLICATE KEY UPDATE quantity = quantity + :quantity"; $parameters = [ 'cart' => $_SESSION['cart'], 'product' => $_POST['addproduct'], 'quantity' => $_POST['quantity'], ]; $statement = $connection->prepare($sql); $statement->execute($parameters);}
https://github.com/zymsys/solid/blob/01/cart.php
function handleUpdate(){ global $connection;
if (!isset($_POST['update'])) { return; } $sql = "UPDATE cartitem SET quantity=:quantity WHERE cart=:cart and product=:product"; $parameters = [ 'cart' => $_SESSION['cart'], 'product' => $_POST['update'], 'quantity' => $_POST['quantity'], ]; $statement = $connection->prepare($sql); $statement->execute($parameters);}
function loadCartItems(){ global $connection, $viewData;
$viewData = []; $statement = $connection->prepare("SELECT * FROM cartitem WHERE cart=:cart AND quantity <> 0"); $statement->execute(['cart' => $_SESSION['cart']]); return $statement->fetchAll();}
https://github.com/zymsys/solid/blob/01/cart.php
function loadProducts(){ global $connection;
$products = []; $result = $connection->query("SELECT * FROM product"); foreach ($result as $product) { $products[$product['id']] = $product; } return $products;}
function loadProvinces(){ global $connection;
$provinces = []; $result = $connection->query("SELECT * FROM province ORDER BY name"); foreach ($result as $row) { $provinces[$row['code']] = $row; } return $provinces;}
https://github.com/zymsys/solid/blob/01/cart.php
function calculateCartSubtotal($cartItems, $products){ $subtotal = 0;
foreach ($cartItems as $cartItem) { $product = $products[$cartItem['product']]; $subtotal += $cartItem['quantity'] * $product['price']; }
return $subtotal;}
function calculateCartTaxes($cartItems, $products, $taxrate){ $taxable = 0;
foreach ($cartItems as $cartItem) { $product = $products[$cartItem['product']]; $taxable += $product['taxes'] ? $cartItem['quantity'] * $product['price'] : 0; } return $taxable * $taxrate / 100;}
https://github.com/zymsys/solid/blob/01/cart.php
function buildViewData(){ $viewData = [ 'cartItems' => loadCartItems(), 'products' => loadProducts(), 'provinces' => loadProvinces(), 'provinceCode' => isset($_GET['province']) ? $_GET['province'] : 'ON', //Default to GTA-PHP's home ];
foreach ($viewData['provinces'] as $province) { if ($province['code'] === $viewData['provinceCode']) { $viewData['province'] = $province; } }
$viewData['subtotal'] = calculateCartSubtotal($viewData['cartItems'], $viewData['products']); $viewData['taxes'] = calculateCartTaxes($viewData['cartItems'], $viewData['products'], $viewData['province']['taxrate']); $viewData['total'] = $viewData['subtotal'] + $viewData['taxes'];
return $viewData;}initialize();handleAdd();handleUpdate();
$viewData = buildViewData();?>
https://github.com/zymsys/solid/blob/01/cart.php
<!doctype html><html lang="en"><head> <meta charset="UTF-8"> <title>GTA-PHP Gift Shop</title> <link rel="stylesheet" href="site.css"></head><body><div class="container"> <h1>GTA-PHP Gift Shop</h1> <p>Buy our junk to keep our organizers up to date with the latest gadgets.</p> <table class="table"> <tr> <th>Product Name</th> <th>You Pay</th> <th>Group Gets</th> <th><!-- Column for add to cart button --></th> </tr> <?php foreach ($viewData['products'] as $product): ?> <tr> <td><?php echo $product['name']; ?></td> <td><?php $price = $product['price']; echo number_format($price / 100, 2); ?></td> <td><?php echo number_format( ($product['price'] - $product['cost']) / 100, 2 ); ?></td>
https://github.com/zymsys/solid/blob/01/cart.php
<td> <form method="post"> <input type="number" name="quantity" value="1" style="width: 3em"> <input type="hidden" name="addproduct" value="<?php echo $product['id']; ?>"> <input class="btn btn-default btn-xs" type="submit" value="Add to Cart"> </form> </td> </tr> <?php endforeach; ?> </table> <?php if (count($viewData['cartItems']) > 0): ?> <h2>Your Cart:</h2> <table class="table"> <?php foreach ($viewData['cartItems'] as $cartItem): ?> <?php $product = $viewData['products'][$cartItem['product']]; ?> <tr> <td> <?php echo $product['name']; ?> </td> <td> <form method="post"> Quantity: <input type="hidden" name="update" value="<?php echo $product['id']; ?>"> <input type="number" name="quantity" style="width: 3em" value="<?php echo $cartItem['quantity']; ?>"> <button type="submit">Update</button> </form> </td>
https://github.com/zymsys/solid/blob/01/cart.php
<td> <?php echo number_format( $cartItem['quantity'] * $product['price'] / 100, 2 ); ?> </td> </tr> <?php endforeach; ?> <tr> <td><!-- Name --></td> <td style="text-align: right">Subtotal:</td> <td><?php echo number_format($viewData['subtotal'] / 100, 2); ?></td> </tr> <tr> <td><!-- Name --></td> <td style="text-align: right"> <?php echo $viewData['province']['name']; ?> taxes at <?php echo $viewData['province']['taxrate'] ?>%:</td> <td> <?php echo number_format($viewData['taxes'] / 100, 2); ?> </td> </tr> <tr> <td><!-- Name --></td> <td style="text-align: right">Total:</td> <td><?php echo number_format($viewData['total'] / 100, 2); ?></td> </tr> </table>
https://github.com/zymsys/solid/blob/01/cart.php
<form method="get"> Calculate taxes for purchase from: <select name="province"> <?php foreach ($viewData['provinces'] as $province): ?> <?php $selected = $viewData['provinceCode'] === $province['code'] ? 'selected' : ''; ?> <option value="<?php echo $province['code']; ?>" <?php echo $selected; ?>> <?php echo $province['name']; ?> </option> <?php endforeach; ?> </select> <button type="submit" class="btn btn-default btn-xs">Recalculate</button> </form> <form action="checkout.php" method="post"> <?php foreach ($viewData['cartItems'] as $itemNumber => $cartItem): ?> <?php $product = $viewData['products'][$cartItem['product']]; ?> <input type="hidden" name="item<?php echo $itemNumber; ?>" value="<?php echo $product['name'] . '|' . number_format($product['price'] / 100, 2); ?>"> <?php endforeach; ?>
https://github.com/zymsys/solid/blob/01/cart.php
<input type="hidden" name="item<?php echo count($viewData['cartItems']); ?>" value="<?php echo 'Tax|' . number_format($viewData['taxes'] / 100, 2); ?>"> <button type="submit" class="btn btn-primary" style="float: right"> Checkout</button> </form> <?php endif; ?></div></body></html>
https://github.com/zymsys/solid/blob/01/cart.php
Any room for improvement now?
ObjectsA very brief introduction
Objects help us to organize our code
function initialize()function handleAdd()function handleUpdate()function loadCartItems()function loadProducts()function loadProvinces()
function calculateCartSubtotal($cartItems, $products)function calculateCartTaxes($cartItems, $products, $taxrate)
function buildViewData()
How might we group these functions?
function initialize()function handleAdd()function handleUpdate()
function loadCartItems()function loadProducts()function loadProvinces()
function calculateCartSubtotal($cartItems, $products)function calculateCartTaxes($cartItems, $products, $taxrate)
function buildViewData()
Invisible stuff that happens on page load
Loads stuff into our HTML
(view)
Objects help us to encapsulate code
function initialize()function handleAdd()function handleUpdate()
function loadCartItems()function loadProducts()function loadProvinces()
function calculateCartSubtotal($cartItems, $products)function calculateCartTaxes($cartItems, $products, $taxrate)
function buildViewData()
Invisible stuff that happens on page load
Loads stuff into our HTML
(view)
function initialize()
function handleAdd()function handleUpdate()
function loadCartItems()function loadProducts()function loadProvinces()
function calculateCartSubtotal($cartItems, $products)function calculateCartTaxes($cartItems, $products, $taxrate)
function buildViewData()
private private
public
private private
public
private private private
class Initializer{ private $connection;
public function __construct(\PDO $connection) { $this->connection = $connection; }
public function initialize() { session_start(); if (!isset($_SESSION['cart'])) { $this->connection->exec("INSERT INTO cart () VALUES ()"); $_SESSION['cart'] = $this->connection->lastInsertId(); } $this->handleAdd(); $this->handleUpdate(); }
private function handleAdd() { … } private function handleUpdate() { … }}
https://github.com/zymsys/solid/blob/02/cart.php
class ViewData { private $connection;
public function __construct(\PDO $connection) { $this->connection = $connection; }
private function loadCartItems() { … } private function loadProducts(){ … } private function loadProvinces(){ … } private function calculateCartSubtotal($cartItems, $products) { … } private function calculateCartTaxes($cartItems, $products, $taxrate) { … } public function buildViewData() { … }}
https://github.com/zymsys/solid/blob/02/cart.php
public function buildViewData(){ $viewData = [ 'cartItems' => $this->loadCartItems(), 'products' => $this->loadProducts(), 'provinces' => $this->loadProvinces(), 'provinceCode' => isset($_GET['province']) ? $_GET['province'] : 'ON', //Default to GTA-PHP's home ];
foreach ($viewData['provinces'] as $province) { if ($province['code'] === $viewData['provinceCode']) { $viewData['province'] = $province; } }
$viewData['subtotal'] = $this->calculateCartSubtotal($viewData['cartItems'], $viewData['products']); $viewData['taxes'] = $this->calculateCartTaxes($viewData['cartItems'], $viewData['products'], $viewData['province']['taxrate']); $viewData['total'] = $viewData['subtotal'] + $viewData['taxes'];
return $viewData;}
https://github.com/zymsys/solid/blob/02/cart.php
SOLIDFewer Gremlins in your Code
SRPSingle Responsibility Principal
Responsibility?
Business Responsibility
not code
function initialize()function handleAdd()function handleUpdate()
function loadCartItems()function loadProducts()function loadProvinces()
function calculateCartSubtotal($cartItems, $products)function calculateCartTaxes($cartItems, $products, $taxrate)
function buildViewData()
Invisible stuff that happens on page load
Loads stuff into our HTML
(view)
function initialize()
function handleAdd()function handleUpdate()function loadCartItems()
function loadProducts()
function loadProvinces()
function calculateCartSubtotal($cartItems, $products)function calculateCartTaxes($cartItems, $products, $taxrate)
function buildViewData()
Sales
Accounting
Inventory
Application / IT
class Application{ private $connection;
public function __construct() { $this->connection = new \PDO('mysql:host=localhost;dbname=solid', 'root', ''); $this->inventory = new Inventory($this->connection); $this->sales = new Sales($this->connection); $this->accounting = new Accounting($this->connection); }
public function initialize() { session_start(); if (!isset($_SESSION['cart'])) { $this->connection->exec("INSERT INTO cart VALUES ()"); $_SESSION['cart'] = $this->connection->lastInsertId(); } $this->handlePost(); }
https://github.com/zymsys/solid/blob/03/cart.php
public function buildViewData() { $viewData = [ 'cartItems' => $this->sales->loadCartItems(), 'products' => $this->inventory->loadProducts(), 'provinces' => $this->accounting->loadProvinces(), 'provinceCode' => isset($_GET['province']) ? $_GET['province'] : 'ON', //Default to GTA-PHP's home ];
foreach ($viewData['provinces'] as $province) { if ($province['code'] === $viewData['provinceCode']) { $viewData['province'] = $province; } }
$viewData['subtotal'] = $this->accounting-> calculateCartSubtotal($viewData['cartItems'], $viewData['products']); $viewData['taxes'] = $this->accounting-> calculateCartTaxes($viewData['cartItems'], $viewData['products'], $viewData['province']['taxrate']); $viewData['total'] = $viewData['subtotal'] + $viewData['taxes'];
return $viewData; }
//Class Application Continued…
https://github.com/zymsys/solid/blob/03/cart.php
private function handlePost() { if (isset($_POST['addproduct'])) { $this->sales->addProductToCart( $_SESSION['cart'], $_POST['addproduct'], $_POST['quantity'] ); } if (isset($_POST['update'])) { $this->sales->modifyProductQuantityInCart( $_SESSION['cart'], $_POST['update'], $_POST['quantity'] ); } }}
//Class Application Continued…
https://github.com/zymsys/solid/blob/03/cart.php
class Inventory { private $connection;
public function __construct(\PDO $connection) { $this->connection = $connection; }
public function loadProducts() { $products = []; $result = $this->connection->query("SELECT * FROM product"); foreach ($result as $product) { $products[$product['id']] = $product; } return $products; }}
https://github.com/zymsys/solid/blob/03/cart.php
class Sales { private $connection;
public function __construct(\PDO $connection) { $this->connection = $connection; }
public function addProductToCart($cartId, $productId, $quantity) { $sql = "INSERT INTO cartitem (cart, product, quantity) VALUES (:cart, :product, :quantity) ON DUPLICATE KEY UPDATE quantity = quantity + :quantity"; $parameters = [ 'cart' => $cartId, 'product' => $productId, 'quantity' => $quantity, ]; $statement = $this->connection->prepare($sql); $statement->execute($parameters); }
https://github.com/zymsys/solid/blob/03/cart.php
public function modifyProductQuantityInCart($cartId, $productId, $quantity) { $sql = "UPDATE cartitem SET quantity=:quantity WHERE cart=:cart and product=:product"; $parameters = [ 'cart' => $cartId, 'product' => $productId, 'quantity' => $quantity, ]; $statement = $this->connection->prepare($sql); $statement->execute($parameters); }
public function loadCartItems() { $statement = $this->connection->prepare("SELECT * FROM cartitem WHERE cart=:cart AND quantity <> 0"); $statement->execute(['cart' => $_SESSION['cart']]); return $statement->fetchAll(); }}
//Class Sales Continued…
https://github.com/zymsys/solid/blob/03/cart.php
class Accounting { private $connection;
public function __construct(\PDO $connection) { $this->connection = $connection; }
public function loadProvinces() { $provinces = []; $result = $this->connection->query("SELECT * FROM province ORDER BY name"); foreach ($result as $row) { $provinces[$row['code']] = $row; } return $provinces; }
public function calculateCartSubtotal($cartItems, $products) { $subtotal = 0;
foreach ($cartItems as $cartItem) { $product = $products[$cartItem['product']]; $subtotal += $cartItem['quantity'] * $product['price']; }
return $subtotal; }
https://github.com/zymsys/solid/blob/03/cart.php
public function calculateCartTaxes($cartItems, $products, $taxrate) { $taxable = 0;
foreach ($cartItems as $cartItem) { $product = $products[$cartItem['product']]; $taxable += $product['taxes'] ? $cartItem['quantity'] * $product['price'] : 0; } return $taxable * $taxrate / 100; }
}
//Class Accounting Continued…
https://github.com/zymsys/solid/blob/03/cart.php
OCPOpen/Closed Principle
Open and Closed?
Open to Extension
Closed to Modification
New requirement:10% off orders over
$100
Where does the code go?
First we need to understand
inheritance and polymorphism
Classes can extend other classes
class AccountingStrategy { private $description;
public function __construct($description) { $this->description = $description; }
public function getAdjustment($cartItems) { return false; }
public function getDescription() { return $this->description; }}
https://github.com/zymsys/solid/blob/04/cart.php
class TaxAccountingStrategy extends AccountingStrategy { private $products; private $taxRate;
public function __construct($products, $province) { parent::__construct($province['name'] . ' taxes at ' . $province['taxrate'] . '%:'); $this->products = $products; $this->taxRate = $province['taxrate']; }
public function getAdjustment($cartItems) { $taxable = 0;
foreach ($cartItems as $cartItem) { $product = $this->products[$cartItem['product']]; $taxable += $product['taxes'] ? $cartItem['quantity'] * $product['price'] : 0; } return $taxable * $this->taxRate / 100; }}
https://github.com/zymsys/solid/blob/04/cart.php
TaxAccountingStrategy is an
AccountingStrategy.
public function initialize(){ session_start(); if (!isset($_SESSION['cart'])) { $this->connection->exec("INSERT INTO cart VALUES ()"); $_SESSION['cart'] = $this->connection->lastInsertId(); } $this->handlePost();
$this->products = $this->inventory->loadProducts(); $provinceRepository = new ProvinceRepository($this->connection, isset($_GET['province']) ? $_GET['province'] : 'ON'); $this->provinces = $provinceRepository->loadProvinces(); $this->selectedProvince = $provinceRepository->getSelectedProvince();
$this->accounting->addStrategy( new TaxAccountingStrategy( $this->products, $provinceRepository->getSelectedProvince() ) );}
public function initialize(){ session_start(); if (!isset($_SESSION['cart'])) { $this->connection->exec("INSERT INTO cart VALUES ()"); $_SESSION['cart'] = $this->connection->lastInsertId(); } $this->handlePost();}
https://github.com/zymsys/solid/blob/04/cart.php
public function buildViewData(){ $viewData = [ 'cartItems' => $this->sales->loadCartItems(), 'products' => $this->inventory->loadProducts(), 'provinces' => $this->accounting->loadProvinces(), 'provinceCode' => isset($_GET['province']) ? $_GET['province'] : 'ON', //Default to GTA-PHP's home ];
public function buildViewData(){ $cartItems = $this->sales->loadCartItems(); $viewData = [ 'cartItems' => $cartItems, 'products' => $this->products, 'provinces' => $this->provinces, 'adjustments' => $this->accounting->applyAdjustments($cartItems), 'provinceCode' => $this->selectedProvince['code'], ];
Done in initialize()
now
Used Twice
New!
Start of buildViewData()
https://github.com/zymsys/solid/blob/04/cart.php
End of buildViewData()
$viewData['subtotal'] = $this->accounting-> calculateCartSubtotal($viewData['cartItems'], $viewData['products']); $viewData['taxes'] = $this->accounting-> calculateCartTaxes($viewData['cartItems'], $viewData['products'], $viewData['province']['taxrate']); $viewData['total'] = $viewData['subtotal'] + $viewData['taxes'];
return $viewData;}
$viewData['subtotal'] = $this->accounting-> calculateCartSubtotal($viewData['cartItems'], $viewData['products']); $viewData['total'] = $viewData['subtotal'] + $this->accounting->getAppliedAdjustmentsTotal();
return $viewData;}
Taxes are handled by adjustmentsand removed as a specific item in
the view’s data.
https://github.com/zymsys/solid/blob/04/cart.php
// loadProvinces used to live in the Accounting classclass ProvinceRepository{ private $connection; private $provinces = null; private $selectedProvince; private $selectedProvinceCode;
public function __construct(\PDO $connection, $selectedProvinceCode) { $this->connection = $connection; $this->selectedProvinceCode = $selectedProvinceCode; }
public function loadProvinces() { … } // Now sets $selectedProvince
public function getProvinces() { return is_null($this->provinces) ? $this->loadProvinces() : $this->provinces; }
public function getSelectedProvince() { return $this->selectedProvince; }}
https://github.com/zymsys/solid/blob/04/cart.php
Remove calculateCartTaxes and add AccountingStrategy
class Accounting { private $connection; private $strategies = []; private $appliedAdjustments = 0;
public function __construct(\PDO $connection) { $this->connection = $connection; }
public function calculateCartSubtotal($cartItems, $products) { … } // No change
public function addStrategy(AccountingStrategy $strategy) { $this->strategies[] = $strategy; }
https://github.com/zymsys/solid/blob/04/cart.php
public function applyAdjustments($cartItems) { $adjustments = []; foreach ($this->strategies as $strategy) { $adjustment = $strategy->getAdjustment($cartItems); if ($adjustment) { $this->appliedAdjustments += $adjustment; $adjustments[] = [ 'description' => $strategy->getDescription(), 'adjustment' => $adjustment, ]; } } return $adjustments; }
public function getAppliedAdjustmentsTotal() { return $this->appliedAdjustments; }}
//Class Accounting Continued…
https://github.com/zymsys/solid/blob/04/cart.php
<?php foreach ($viewData['adjustments'] as $adjustment): ?><tr> <td><!-- Name --></td> <td style="text-align: right"> <?php echo $adjustment['description']; ?> </td> <td> <?php echo number_format($adjustment['adjustment'] / 100, 2); ?> </td></tr><?php endforeach; ?>
<?phpfor ($adjustmentIndex = 0; $adjustmentIndex < count($viewData['adjustments']); $adjustmentIndex += 1): $adjustment = $viewData['adjustments'][$adjustmentIndex];?><input type="hidden" name="item<?php echo $adjustmentIndex; ?>" value="<?php echo $adjustment['description'] . '|' . number_format($adjustment['adjustment'] / 100, 2); ?>"><?php endfor; ?>
Now we can add DiscountAccountingStrategy
without modifying Accounting or the view
class DiscountAccountingStrategy extends AccountingStrategy { private $products;
public function __construct($products) { parent::__construct("Discount for orders over $100"); $this->products = $products; }
public function getAdjustment($cartItems) { $total = array_reduce($cartItems, function ($carry, $item) { $product = $this->products[$item['product']]; return $carry + $item['quantity'] * $product['price']; }, 0); return $total > 10000 ? ($total / -10) : false; }}
https://github.com/zymsys/solid/blob/04/cart.php
In Application::initialize(
)$this->accounting->addStrategy( new DiscountAccountingStrategy($this->products));
https://github.com/zymsys/solid/blob/04/cart.php
LSPLiskov Substitution Principal
If a class is a thing, it should act like that
thing. must
Square is a Rectangle?
Ways to break LSP• Throw a new exception• Add requirements to parameters
• requiring a more specific type• Return an unexpected type
• returning a less specific type• Do anything that would be unexpected if
used as a stand-in for an ancestor
DIPDependency Inversion Principle
Yeah smarty-pants, the D does come
before the I
Classes can implement interfaces,
and can depend on interfaces
Our Dependencies
Dependency Inversion
We can scrap the AccountingStrategy class and
add…interface AccountingStrategyInterface{ public function getDescription(); public function getAdjustment($cartItems);}
https://github.com/zymsys/solid/blob/06/cart.php
Because Application creates concrete classes there’s little to be gained by
adding other interfacespublic function __construct(){ $this->connection = new \PDO('mysql:host=localhost;dbname=solid', 'root', ''); $this->inventory = new Inventory($this->connection); $this->sales = new Sales($this->connection); $this->accounting = new Accounting($this->connection);}
Dependency Injection Containers would solve this, but are a topic for
another talk.
https://github.com/zymsys/solid/blob/06/cart.php
ISPInterface Segregation Principal
If we follow SRP and interfaces describe
classes, what’s left to segregate?
Code Responsibilities
Imagine an interface to our Sales class
interface SalesInterface{
public function addProductToCart($cartId, $productId, $quantity); public function modifyProductQuantityInCart($cartId, $productId, $quantity);
public function loadCartItems();}
Imagine an interface to our Sales class
interface SalesWriterInterface{
public function addProductToCart($cartId, $productId, $quantity); public function modifyProductQuantityInCart($cartId, $productId, $quantity);
public function loadCartItems();
}
interface SalesReaderInterface{
}
Last words
Resist OverengineeringSimplify don’t complicate
Pragmatic not Dogmatic
Questions?