SlideShare a Scribd company logo
1 of 84
Zero to SOLID
in 45 Minutes
by Vic Metcalfe
@v_metcalfe
I made an
e-Commerce
site in PHP!
Children or those feint of
heart are warned to leave
the room…
<?php
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();
}
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
<?php
function 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?
Objects
A 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
SOLID
Fewer Gremlins in your Code
SRP
Single 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
OCP
Open/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 adjustments
and 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 class
class 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; ?>
<?php
for ($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
LSP
Liskov 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
DIP
Dependency 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 interfaces
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);
}
Dependency Injection Containers would
solve this, but are a topic for another talk.
https://github.com/zymsys/solid/blob/06/cart.php
ISP
Interface 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 Overengineering
Simplify don’t complicate
Pragmatic not Dogmatic
Questions?

More Related Content

What's hot

Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP GeneratorsMark Baker
 
News of the Symfony2 World
News of the Symfony2 WorldNews of the Symfony2 World
News of the Symfony2 WorldFabien Potencier
 
Rich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 ApplicationRich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 ApplicationKirill Chebunin
 
The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010Fabien Potencier
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016Kacper Gunia
 
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)James Titcumb
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICKonstantin Kudryashov
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)Javier Eguiluz
 
Webrtc mojo
Webrtc mojoWebrtc mojo
Webrtc mojobpmedley
 
Design how your objects talk through mocking
Design how your objects talk through mockingDesign how your objects talk through mocking
Design how your objects talk through mockingKonstantin Kudryashov
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2Hugo Hamon
 
Looping the Loop with SPL Iterators
Looping the Loop with SPL IteratorsLooping the Loop with SPL Iterators
Looping the Loop with SPL IteratorsMark Baker
 
Perl web frameworks
Perl web frameworksPerl web frameworks
Perl web frameworksdiego_k
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Leonardo Proietti
 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownpartsBastian Feder
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteLeonardo Proietti
 

What's hot (20)

Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
 
News of the Symfony2 World
News of the Symfony2 WorldNews of the Symfony2 World
News of the Symfony2 World
 
Rich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 ApplicationRich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 Application
 
The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010
 
Symfony 2.0 on PHP 5.3
Symfony 2.0 on PHP 5.3Symfony 2.0 on PHP 5.3
Symfony 2.0 on PHP 5.3
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016
 
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
 
The IoC Hydra
The IoC HydraThe IoC Hydra
The IoC Hydra
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
 
Symfony2 - WebExpo 2010
Symfony2 - WebExpo 2010Symfony2 - WebExpo 2010
Symfony2 - WebExpo 2010
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
 
Webrtc mojo
Webrtc mojoWebrtc mojo
Webrtc mojo
 
Design how your objects talk through mocking
Design how your objects talk through mockingDesign how your objects talk through mocking
Design how your objects talk through mocking
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
 
Looping the Loop with SPL Iterators
Looping the Loop with SPL IteratorsLooping the Loop with SPL Iterators
Looping the Loop with SPL Iterators
 
Perl web frameworks
Perl web frameworksPerl web frameworks
Perl web frameworks
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownparts
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
 

Viewers also liked

Slim RedBeanPHP and Knockout
Slim RedBeanPHP and KnockoutSlim RedBeanPHP and Knockout
Slim RedBeanPHP and KnockoutVic Metcalfe
 
An Elephant of a Different Colour: Hack
An Elephant of a Different Colour: HackAn Elephant of a Different Colour: Hack
An Elephant of a Different Colour: HackVic Metcalfe
 
Organizing Your PHP Projects (2010 ConFoo)
Organizing Your PHP Projects (2010 ConFoo)Organizing Your PHP Projects (2010 ConFoo)
Organizing Your PHP Projects (2010 ConFoo)Paul Jones
 
Namespaces and Autoloading
Namespaces and AutoloadingNamespaces and Autoloading
Namespaces and AutoloadingVic Metcalfe
 
Software_Architectures_from_SOA_to_MSA
Software_Architectures_from_SOA_to_MSASoftware_Architectures_from_SOA_to_MSA
Software_Architectures_from_SOA_to_MSAPeter Denev
 
Don't be STUPID, Grasp SOLID - North East PHP
Don't be STUPID, Grasp SOLID - North East PHPDon't be STUPID, Grasp SOLID - North East PHP
Don't be STUPID, Grasp SOLID - North East PHPAnthony Ferrara
 
Keeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro frameworkKeeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro frameworkJeremy Kendall
 

Viewers also liked (10)

Slim RedBeanPHP and Knockout
Slim RedBeanPHP and KnockoutSlim RedBeanPHP and Knockout
Slim RedBeanPHP and Knockout
 
An Elephant of a Different Colour: Hack
An Elephant of a Different Colour: HackAn Elephant of a Different Colour: Hack
An Elephant of a Different Colour: Hack
 
Organizing Your PHP Projects (2010 ConFoo)
Organizing Your PHP Projects (2010 ConFoo)Organizing Your PHP Projects (2010 ConFoo)
Organizing Your PHP Projects (2010 ConFoo)
 
SOLID design
SOLID designSOLID design
SOLID design
 
Namespaces and Autoloading
Namespaces and AutoloadingNamespaces and Autoloading
Namespaces and Autoloading
 
Software_Architectures_from_SOA_to_MSA
Software_Architectures_from_SOA_to_MSASoftware_Architectures_from_SOA_to_MSA
Software_Architectures_from_SOA_to_MSA
 
Getting hands dirty with php7
Getting hands dirty with php7Getting hands dirty with php7
Getting hands dirty with php7
 
Don't be STUPID, Grasp SOLID - North East PHP
Don't be STUPID, Grasp SOLID - North East PHPDon't be STUPID, Grasp SOLID - North East PHP
Don't be STUPID, Grasp SOLID - North East PHP
 
SOLID Principles
SOLID PrinciplesSOLID Principles
SOLID Principles
 
Keeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro frameworkKeeping it small: Getting to know the Slim micro framework
Keeping it small: Getting to know the Slim micro framework
 

Similar to Zero to SOLID

Drupal Development (Part 2)
Drupal Development (Part 2)Drupal Development (Part 2)
Drupal Development (Part 2)Jeff Eaton
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your CodeAbbas Ali
 
Introduction à CoffeeScript pour ParisRB
Introduction à CoffeeScript pour ParisRB Introduction à CoffeeScript pour ParisRB
Introduction à CoffeeScript pour ParisRB jhchabran
 
SULTHAN's - PHP MySQL programs
SULTHAN's - PHP MySQL programsSULTHAN's - PHP MySQL programs
SULTHAN's - PHP MySQL programsSULTHAN BASHA
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutesBarang CK
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 MinutesAzim Kurt
 
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 needKacper Gunia
 
10...ish things i learned from programming an e-shop for a year
10...ish things i learned from programming an e-shop for a year10...ish things i learned from programming an e-shop for a year
10...ish things i learned from programming an e-shop for a yearInes Panker
 
R57shell
R57shellR57shell
R57shellady36
 
Practical PHP by example Jan Leth-Kjaer
Practical PHP by example   Jan Leth-KjaerPractical PHP by example   Jan Leth-Kjaer
Practical PHP by example Jan Leth-KjaerCOMMON Europe
 
BDD revolution - or how we came back from hell
BDD revolution - or how we came back from hellBDD revolution - or how we came back from hell
BDD revolution - or how we came back from hellMateusz Zalewski
 
[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ć ...Mateusz Zalewski
 

Similar to Zero to SOLID (20)

Php
PhpPhp
Php
 
Add loop shortcode
Add loop shortcodeAdd loop shortcode
Add loop shortcode
 
Daily notes
Daily notesDaily notes
Daily notes
 
Drupal Development (Part 2)
Drupal Development (Part 2)Drupal Development (Part 2)
Drupal Development (Part 2)
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
 
Introduction à CoffeeScript pour ParisRB
Introduction à CoffeeScript pour ParisRB Introduction à CoffeeScript pour ParisRB
Introduction à CoffeeScript pour ParisRB
 
SULTHAN's - PHP MySQL programs
SULTHAN's - PHP MySQL programsSULTHAN's - PHP MySQL programs
SULTHAN's - PHP MySQL programs
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutes
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
 
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
 
logic321
logic321logic321
logic321
 
Smarty
SmartySmarty
Smarty
 
10...ish things i learned from programming an e-shop for a year
10...ish things i learned from programming an e-shop for a year10...ish things i learned from programming an e-shop for a year
10...ish things i learned from programming an e-shop for a year
 
R57shell
R57shellR57shell
R57shell
 
CakePHP workshop
CakePHP workshopCakePHP workshop
CakePHP workshop
 
Practical PHP by example Jan Leth-Kjaer
Practical PHP by example   Jan Leth-KjaerPractical PHP by example   Jan Leth-Kjaer
Practical PHP by example Jan Leth-Kjaer
 
BDD revolution - or how we came back from hell
BDD revolution - or how we came back from hellBDD revolution - or how we came back from hell
BDD revolution - or how we came back from hell
 
[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ć ...
 
Bacbkone js
Bacbkone jsBacbkone js
Bacbkone js
 
Separation of concerns - DPC12
Separation of concerns - DPC12Separation of concerns - DPC12
Separation of concerns - DPC12
 

Recently uploaded

Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024TopCSSGallery
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Alkin Tezuysal
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical InfrastructureVarsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructureitnewsafrica
 
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...Wes McKinney
 
QCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architecturesQCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architecturesBernd Ruecker
 
Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Hiroshi SHIBATA
 
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...Nikki Chapple
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality AssuranceInflectra
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfLoriGlavin3
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Farhan Tariq
 
Glenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security ObservabilityGlenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security Observabilityitnewsafrica
 
Generative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxGenerative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxfnnc6jmgwh
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesMuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesManik S Magar
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
UiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPathCommunity
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 

Recently uploaded (20)

Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024Top 10 Hubspot Development Companies in 2024
Top 10 Hubspot Development Companies in 2024
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical InfrastructureVarsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
 
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
 
QCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architecturesQCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architectures
 
Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024
 
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...
Microsoft 365 Copilot: How to boost your productivity with AI – Part one: Ado...
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdf
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...
 
Glenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security ObservabilityGlenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security Observability
 
Generative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxGenerative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptx
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesMuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
UiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to Hero
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 

Zero to SOLID

  • 1. Zero to SOLID in 45 Minutes by Vic Metcalfe @v_metcalfe
  • 3. Children or those feint of heart are warned to leave the room…
  • 4. <?php 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(); } 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
  • 5. $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
  • 6. <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
  • 7. $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
  • 8. $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
  • 9. <?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
  • 11. Step 1: Separate PHP from HTML
  • 12. <?php function 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
  • 13. 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
  • 14. 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
  • 15. 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
  • 16. 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
  • 17. <!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
  • 18. <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
  • 19. <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
  • 20. <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
  • 21. <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
  • 23. Objects A very brief introduction
  • 24. Objects help us to organize our code
  • 25. 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?
  • 26. 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)
  • 27. Objects help us to encapsulate code
  • 28. 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)
  • 29. 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
  • 30. 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
  • 31. 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
  • 32. 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
  • 37. 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)
  • 38. 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
  • 39. 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
  • 40. 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
  • 41. 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
  • 42. 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
  • 43. 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
  • 44. 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
  • 45. 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
  • 46. 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
  • 49. Open to Extension Closed to Modification
  • 50. New requirement: 10% off orders over $100
  • 51. Where does the code go?
  • 52. First we need to understand inheritance and polymorphism
  • 54. 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
  • 55. 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
  • 57. 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
  • 58. 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
  • 59. 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 adjustments and removed as a specific item in the view’s data. https://github.com/zymsys/solid/blob/04/cart.php
  • 60. // loadProvinces used to live in the Accounting class class 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
  • 61. 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
  • 62. 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
  • 63. <?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; ?>
  • 64. <?php for ($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; ?>
  • 65. Now we can add DiscountAccountingStrategy without modifying Accounting or the view
  • 66. 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
  • 69. If a class is a thing, it should act like that thing. must
  • 71. 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
  • 73. Yeah smarty-pants, the D does come before the I
  • 74. Classes can implement interfaces, and can depend on interfaces
  • 77. 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
  • 78. Because Application creates concrete classes there’s little to be gained by adding other interfaces 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); } Dependency Injection Containers would solve this, but are a topic for another talk. https://github.com/zymsys/solid/blob/06/cart.php
  • 80. If we follow SRP and interfaces describe classes, what’s left to segregate?
  • 82. Imagine an interface to our Sales class interface SalesInterface { public function addProductToCart($cartId, $productId, $quantity); public function modifyProductQuantityInCart($cartId, $productId, $quantity); public function loadCartItems(); }
  • 83. 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 { }
  • 84. Last words Resist Overengineering Simplify don’t complicate Pragmatic not Dogmatic Questions?

Editor's Notes

  1. Why would I build handcuffs into my code that let me do less with it? Talk about the contract we have with code outside our class and how private methods make refactoring easier.
  2. Loads more into ivars instead of directly into view Strategies are also added at initialize.
  3. Avoid these terms, but if they come up: Parameters should be contravariant Return types should be covariant
  4. Most ISP definitions go so far as to say that no class should be forced to depend on methods they do not use. Personally I think this goes too far.