SlideShare uma empresa Scribd logo
1 de 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?

Mais conteúdo relacionado

Mais procurados

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
 

Mais procurados (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
 

Destaque

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
 

Destaque (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
 

Semelhante a 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
 

Semelhante a 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
 

Último

Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhisoniya singh
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Allon Mureinik
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGSujit Pal
 

Último (20)

Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
Google AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAGGoogle AI Hackathon: LLM based Evaluator for RAG
Google AI Hackathon: LLM based Evaluator for RAG
 

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?

Notas do Editor

  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.