Compare commits

...

2 Commits

Author SHA1 Message Date
Augusto Gunsch 6d40f9c2fc
Adhere to PSR-4 and PSR-12 2022-07-27 17:44:08 +02:00
Augusto Gunsch bd7f230fe1
Improve listing page 2022-07-27 12:59:59 +02:00
15 changed files with 570 additions and 327 deletions

16
add-product.php Normal file
View File

@ -0,0 +1,16 @@
<html>
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" href="index.css"/>
<title>Product Add</title>
</head>
<body>
<div id="header">
<h1 id="title">Product Add</h1>
<div id="buttons">
<a href="/"><button>Save</button></a>
<a href="/"><button>Cancel</button></a>
</div>
</div>
</body>
</html>

28
autoload.php Normal file
View File

@ -0,0 +1,28 @@
<?php
spl_autoload_register(function ($class) {
// project-specific namespace prefix
$prefix = 'ProductList\\';
// base directory for the namespace prefix
$base_dir = __DIR__ . '/src/';
// does the class use the namespace prefix?
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// no, move to the next registered autoloader
return;
}
// get the relative class name
$relative_class = substr($class, $len);
// replace the namespace prefix with the base directory, replace namespace
// separators with directory separators in the relative class name, append
// with .php
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// if the file exists, require it
if (file_exists($file)) {
require $file;
}
});

View File

@ -1,14 +1,58 @@
body {
font-family: cursive;
}
#header {
display: flex;
justify-content: space-between;
border-bottom: 2px solid gray;
margin: 0 1em 2em;
}
#buttons {
display: flex;
margin: 0 1em;
}
#buttons a {
align-self: center;
}
#buttons button {
height: 2em;
margin: 1em;
font-family: cursive;
padding: .5em 1em;
font-size: 1rem;
margin: 0 2em;
align-self: center;
background-color: white;
border: 2px solid black;
box-shadow: 2px 2px black;
}
#buttons button:hover {
background-color: #EEE;
cursor: pointer;
}
#products {
display: flex;
flex-wrap: wrap;
}
.product {
width: 150px;
height: 150px;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
border: 2px solid black;
margin: 1em;
position: relative;
}
.delete-checkbox {
border: 1px solid black;
position: absolute;
top: 0px;
}

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" href="index.css"/>
<title>Product List</title>
</head>
<body>
<div id="header">
<h1 id="title">Product List</h1>
<div id="buttons">
<a href="add-product"><button>ADD</button></a>
<button id="delete-product-btn">MASS DELETE</button>
</div>
</div>
<div id="products">
</div>
</body>
<script src="index.js"></script>
</html>

47
index.js Normal file
View File

@ -0,0 +1,47 @@
const productsList = document.getElementById('products');
const loadItems = () => {
const xhttp = new XMLHttpRequest();
xhttp.onload = function() {
console.log(this.responseText);
const products = JSON.parse(this.responseText);
const boxes = products.map(product =>
`<div class="product">
<input type="checkbox" class="delete-checkbox" value="${product.id}">
<p>
${product.sku}<br>
${product.name}<br>
${product.price} $<br>
${product.attribute}
</p>
</div>`
)
productsList.innerHTML = boxes.join('\n');
}
xhttp.open('GET', 'src/View/Product', true);
xhttp.send();
}
loadItems();
const deleteSelected = () => {
const checkboxes = document.querySelectorAll('input[class="delete-checkbox"]:checked');
let values = [];
checkboxes.forEach(checkbox => values.push(checkbox.value));
const xhttp = new XMLHttpRequest();
xhttp.onload = function() {
loadItems();
}
xhttp.open('DELETE', 'src/View/Product', true);
xhttp.send();
}
const deleteButton = document.getElementById('delete-product-btn');
deleteButton.addEventListener('click', deleteSelected);

View File

@ -1,24 +1,4 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" href="index.css"/>
<title>Product List</title>
</head>
<body>
<div id="header">
<h1 id="title">Product List</h1>
<div id="buttons">
<button>ADD</button>
<button id="delete-product-btn">MASS DELETE</button>
</div>
</div>
<?php
require 'model/product.php';
require 'autoload.php';
$products = Product::selectAll();
// TODO
echo json_encode($products);
?>
</body>
</html>
echo json_encode(ProductList\Model\Product::selectAll());

View File

@ -1,41 +0,0 @@
<?php
define("PRODUCT", "product");
define("DVD", "dvd");
define("BOOK", "book");
define("FURNITURE", "furniture");
class Database {
const SERVERNAME = "127.0.0.1";
const DATABASE = "scandiweb";
const USERNAME = "root";
const PASSWORD = "root";
public static function connect() {
$conn = new mysqli(self::SERVERNAME, self::USERNAME, self::PASSWORD);
$conn->select_db(self::DATABASE);
return $conn;
}
}
trait Model {
public abstract static function fromRow($row) : self;
public abstract function insert($conn = NULL) : int; // should return id
private abstract static function getSelectAllQuery() : string;
public static function selectAll($conn = NULL) : array {
if($conn === NULL) {
$conn = Database::connect();
}
$sql = self::getSelectAllQuery();
$rows = $conn->query($sql)->fetch_all(MYSQLI_ASSOC);
$products = array();
foreach($rows as $row) {
array_push($products, self::fromRow($row));
}
return $products;
}
}

View File

@ -1,262 +0,0 @@
<?php
require_once 'db.php';
abstract class Product implements JsonSerializable {
use Model;
private $variationId;
private $SKU;
private $name;
private $price;
private $productId;
public function __construct($SKU, $name, $price, $productId = NULL, $variationId = NULL) {
$this->productId = $productId;
$this->variationId = $variationId;
$this->SKU = $SKU;
$this->name = $name;
$this->price = $price;
}
public function getSKU() {
return $this->SKU;
}
public function getName() {
return $this->name;
}
public function getPrice() {
return $this->price;
}
public function getProductId() {
return $this->productId;
}
public function setVariationId($id) {
$this->variationId = $id;
}
public function setProductId($id) {
$this->productId = $id;
}
public abstract function getFormatedAttr();
public static function fromRow($row) : self {
if($row['size'] !== NULL) {
return DVD::fromRow($row);
} elseif($row['weight'] !== NULL) {
return Book::fromRow($row);
} elseif($row['height'] !== NULL) {
return Furniture::fromRow($row);
} else {
throw new Exception("Product without a type");
}
}
private static function getSelectAllQuery() : string {
return 'SELECT '.PRODUCT.'.id as product_id, COALESCE('.DVD.'.id, '.BOOK.'.id, '.FURNITURE.'.id) as variation_id,
name, sku, price, size, weight, width, height, length
FROM '.PRODUCT.'
LEFT JOIN '.DVD.' ON '.PRODUCT.'.id = '.DVD.'.product_id
LEFT JOIN '.BOOK.' ON '.PRODUCT.'.id = '.BOOK.'.product_id
LEFT JOIN '.FURNITURE.' ON '.PRODUCT.'.id = '.FURNITURE.'.product_id;';
}
public function insert($conn = NULL) : int {
if($conn === NULL) {
$conn = Database::connect();
}
$SKU = $this->getSKU();
$name = $this->getName();
$price = $this->getPrice();
$stmt = $conn->prepare("INSERT INTO ".PRODUCT." (sku, name, price) VALUES (?, ?, ?);");
$stmt->bind_param('ssd', $SKU, $name, $price);
if($stmt->execute() === TRUE) {
$this->setProductId($conn->insert_id);
return $conn->insert_id;
} else {
throw new Exception("Unable to insert object");
}
}
public function jsonSerialize() : mixed {
return [
'sku' => $this->getSKU(),
'name' => $this->getName(),
'price' => $this->getPrice(),
'attribute' => $this->getFormatedAttr()
];
}
}
class DVD extends Product {
private $size;
public function __construct($sku, $name, $price, $size, $productId = null, $variationId = null) {
parent::__construct($sku, $name, $price, $productId, $variationId);
$this->size = $size;
}
public static function fromRow($row) : self {
return new DVD($row['sku'], $row['name'], $row['price'], $row['size'], $row['product_id'], $row['variation_id']);
}
public function getSize() {
return $this->size;
}
public function getFormatedAttr() {
$size = $this->getSize();
return "Size: $size MB";
}
private static function getSelectAllQuery() : string {
return 'SELECT '.PRODUCT.'.*, '.DVD.'.id as variation_id, size
FROM '.PRODUCT.' LEFT JOIN '.DVD.' ON '.PRODUCT.'.id = '.DVD.'.product_id';
}
public function insert($conn = NULL) : int {
if($conn === NULL) {
$conn = Database::connect();
}
if($this->getProductId() === NULL) {
parent::insert($conn);
}
$productId = $this->getProductId();
$size = $this->getSize();
$stmt = $conn->prepare("INSERT INTO ".DVD." (product_id, size) VALUES (?, ?);");
$stmt->bind_param('ii', $productId, $size);
if($stmt->execute() === TRUE) {
$this->setVariationId($conn->insert_id);
return $conn->insert_id;
} else {
throw new Exception("Unable to insert object");
}
}
}
class Book extends Product {
private $weight;
public function __construct($sku, $name, $price, $weight, $productId = null, $variationId = null) {
parent::__construct($sku, $name, $price, $productId, $variationId);
$this->weight = $weight;
}
public static function fromRow($row) : self {
return new Book($row['sku'], $row['name'], $row['price'], $row['weight'], $row['product_id'], $row['variation_id']);
}
public function getWeight() {
return $this->weight;
}
public function getFormatedAttr() {
$weight = $this->getWeight();
return "Weight: $weight KG";
}
private static function getSelectAllQuery() : string {
return 'SELECT '.PRODUCT.'.*, '.BOOK.'.id as variation_id, size
FROM '.PRODUCT.' LEFT JOIN '.BOOK.' ON '.PRODUCT.'.id = '.BOOK.'.product_id';
}
public function insert($conn = NULL) : int {
if($conn === NULL) {
$conn = Database::connect();
}
if($this->getProductId() === NULL) {
parent::insert($conn);
}
$productId = $this->getProductId();
$weight = $this->getWeight();
$stmt = $conn->prepare("INSERT INTO ".BOOK." (product_id, weight) VALUES (?, ?);");
$stmt->bind_param('id', $productId, $weight);
if($stmt->execute() === TRUE) {
$this->setVariationId($conn->insert_id);
return $conn->insert_id;
} else {
throw new Exception("Unable to insert object");
}
}
}
class Furniture extends Product {
private $height;
private $width;
private $length;
public function __construct($SKU, $name, $price, $height, $width, $length, $productId = NULL, $variationId = NULL) {
parent::__construct($SKU, $name, $price, $productId, $variationId);
$this->height = $height;
$this->width = $width;
$this->length = $length;
}
public static function fromRow($row) : self {
return new Furniture($row['sku'], $row['name'], $row['price'], $row['height'],
$row['width'], $row['length'], $row['product_id'], $row['variation_id']);
}
public function getHeight() {
return $this->height;
}
public function getWidth() {
return $this->width;
}
public function getLength() {
return $this->length;
}
public function getFormatedAttr() {
$height = $this->getHeight();
$width = $this->getWidth();
$length = $this->getLength();
return 'Dimension: '.$height.'x'.$width.'x'.$length;
}
private static function getSelectAllQuery() : string {
return 'SELECT '.PRODUCT.'.*, '.FURNITURE.'.id as variation_id, size
FROM '.PRODUCT.' LEFT JOIN '.FURNITURE.' ON '.PRODUCT.'.id = '.FURNITURE.'.product_id';
}
public function insert($conn = NULL) : int {
if($conn === NULL) {
$conn = Database::connect();
}
if($this->getProductId() === NULL) {
parent::insert($conn);
}
$productId = $this->getProductId();
$height = $this->getHeight();
$width = $this->getWidth();
$length = $this->getLength();
$stmt = $conn->prepare("INSERT INTO ".FURNITURE." (product_id, height, width, length) VALUES (?, ?, ?, ?);");
$stmt->bind_param('iddd', $productId, $height, $width, $length);
if($stmt->execute() === TRUE) {
$this->setVariationId($conn->insert_id);
return $conn->insert_id;
} else {
throw new Exception("Unable to insert object");
}
}
}

72
src/Model/Book.php Normal file
View File

@ -0,0 +1,72 @@
<?php
namespace ProductList\Model;
class Book extends Product
{
private $weight;
public function __construct(
$sku,
$name,
$price,
$weight,
$productId = null,
$variationId = null
) {
parent::__construct($sku, $name, $price, $productId, $variationId);
$this->weight = $weight;
}
public static function fromRow($row) : self
{
return new Book(
$row['sku'],
$row['name'],
$row['price'],
$row['weight'],
$row['product_id'],
$row['variation_id']
);
}
public function getWeight()
{
return $this->weight;
}
public function getFormatedAttr()
{
$weight = $this->getWeight();
return "Weight: $weight KG";
}
private static function getSelectAllQuery() : string
{
return 'SELECT '.PRODUCT.'.*, '.BOOK.'.id as variation_id, size
FROM '.PRODUCT.' LEFT JOIN '.BOOK.' ON '.PRODUCT.'.id = '.BOOK.'.product_id';
}
public function insert($conn = null) : int
{
if ($conn === null) {
$conn = Database::connect();
}
if ($this->getProductId() === null) {
parent::insert($conn);
}
$productId = $this->getProductId();
$weight = $this->getWeight();
$stmt = $conn->prepare("INSERT INTO ".BOOK." (product_id, weight) VALUES (?, ?);");
$stmt->bind_param('id', $productId, $weight);
if ($stmt->execute() === true) {
$this->setVariationId($conn->insert_id);
return $conn->insert_id;
} else {
throw new Exception("Unable to insert object");
}
}
}

72
src/Model/DVD.php Normal file
View File

@ -0,0 +1,72 @@
<?php
namespace ProductList\Model;
class DVD extends Product
{
private $size;
public function __construct(
$sku,
$name,
$price,
$size,
$productId = null,
$variationId = null
) {
parent::__construct($sku, $name, $price, $productId, $variationId);
$this->size = $size;
}
public static function fromRow($row) : self
{
return new DVD(
$row['sku'],
$row['name'],
$row['price'],
$row['size'],
$row['product_id'],
$row['variation_id']
);
}
public function getSize()
{
return $this->size;
}
public function getFormatedAttr()
{
$size = $this->getSize();
return "Size: $size MB";
}
private static function getSelectAllQuery() : string
{
return 'SELECT '.PRODUCT.'.*, '.DVD.'.id as variation_id, size
FROM '.PRODUCT.' LEFT JOIN '.DVD.' ON '.PRODUCT.'.id = '.DVD.'.product_id';
}
public function insert($conn = null) : int
{
if ($conn === null) {
$conn = Database::connect();
}
if ($this->getProductId() === null) {
parent::insert($conn);
}
$productId = $this->getProductId();
$size = $this->getSize();
$stmt = $conn->prepare("INSERT INTO ".DVD." (product_id, size) VALUES (?, ?);");
$stmt->bind_param('ii', $productId, $size);
if ($stmt->execute() === true) {
$this->setVariationId($conn->insert_id);
return $conn->insert_id;
} else {
throw new Exception("Unable to insert object");
}
}
}

22
src/Model/Database.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace ProductList\Model;
define("PRODUCT", "product");
define("DVD", "dvd");
define("BOOK", "book");
define("FURNITURE", "furniture");
class Database
{
const SERVERNAME = "127.0.0.1";
const DATABASE = "scandiweb";
const USERNAME = "root";
const PASSWORD = "root";
public static function connect()
{
$conn = new \mysqli(self::SERVERNAME, self::USERNAME, self::PASSWORD);
$conn->select_db(self::DATABASE);
return $conn;
}
}

98
src/Model/Furniture.php Normal file
View File

@ -0,0 +1,98 @@
<?php
namespace ProductList\Model;
class Furniture extends Product
{
private $height;
private $width;
private $length;
public function __construct(
$SKU,
$name,
$price,
$height,
$width,
$length,
$productId = null,
$variationId = null
) {
parent::__construct($SKU, $name, $price, $productId, $variationId);
$this->height = $height;
$this->width = $width;
$this->length = $length;
}
public static function fromRow($row) : self
{
return new Furniture(
$row['sku'],
$row['name'],
$row['price'],
$row['height'],
$row['width'],
$row['length'],
$row['product_id'],
$row['variation_id']
);
}
public function getHeight()
{
return $this->height;
}
public function getWidth()
{
return $this->width;
}
public function getLength()
{
return $this->length;
}
public function getFormatedAttr()
{
$height = $this->getHeight();
$width = $this->getWidth();
$length = $this->getLength();
return 'Dimension: '.$height.'x'.$width.'x'.$length;
}
private static function getSelectAllQuery() : string
{
return 'SELECT '.PRODUCT.'.*, '.FURNITURE.'.id as variation_id, size
FROM '.PRODUCT.'
LEFT JOIN '.FURNITURE.' ON '.PRODUCT.'.id = '.FURNITURE.'.product_id';
}
public function insert($conn = null) : int
{
if ($conn === null) {
$conn = Database::connect();
}
if ($this->getProductId() === null) {
parent::insert($conn);
}
$productId = $this->getProductId();
$height = $this->getHeight();
$width = $this->getWidth();
$length = $this->getLength();
$stmt = $conn->prepare(
"INSERT INTO ".FURNITURE." (product_id, height, width, length)
VALUES (?, ?, ?, ?);"
);
$stmt->bind_param('iddd', $productId, $height, $width, $length);
if ($stmt->execute() === true) {
$this->setVariationId($conn->insert_id);
return $conn->insert_id;
} else {
throw new Exception("Unable to insert object");
}
}
}

26
src/Model/Model.php Normal file
View File

@ -0,0 +1,26 @@
<?php
namespace ProductList\Model;
trait Model
{
abstract public static function fromRow($row) : self;
abstract public function insert($conn = null) : int; // should return id
abstract private static function getSelectAllQuery() : string;
public static function selectAll($conn = null) : array
{
if ($conn === null) {
$conn = Database::connect();
}
$sql = self::getSelectAllQuery();
$rows = $conn->query($sql)->fetch_all(MYSQLI_ASSOC);
$products = array();
foreach($rows as $row) {
array_push($products, self::fromRow($row));
}
return $products;
}
}

117
src/Model/Product.php Normal file
View File

@ -0,0 +1,117 @@
<?php
namespace ProductList\Model;
abstract class Product implements \JsonSerializable
{
use Model;
private $variationId;
private $SKU;
private $name;
private $price;
private $productId;
public function __construct(
$SKU,
$name,
$price,
$productId = null,
$variationId = null
) {
$this->productId = $productId;
$this->variationId = $variationId;
$this->SKU = $SKU;
$this->name = $name;
$this->price = $price;
}
public function getSKU()
{
return $this->SKU;
}
public function getName()
{
return $this->name;
}
public function getPrice()
{
return $this->price;
}
public function getProductId()
{
return $this->productId;
}
public function setVariationId($id)
{
$this->variationId = $id;
}
public function setProductId($id)
{
$this->productId = $id;
}
abstract public function getFormatedAttr();
public static function fromRow($row) : self
{
if ($row['size'] !== null) {
return DVD::fromRow($row);
} elseif ($row['weight'] !== null) {
return Book::fromRow($row);
} elseif ($row['height'] !== null) {
return Furniture::fromRow($row);
} else {
throw new Exception("Product without a type");
}
}
private static function getSelectAllQuery() : string
{
return 'SELECT '.PRODUCT.'.id as product_id,
COALESCE('.DVD.'.id, '.BOOK.'.id, '.FURNITURE.'.id) as variation_id,
name, sku, price, size, weight, width, height, length
FROM '.PRODUCT.'
LEFT JOIN '.DVD.' ON '.PRODUCT.'.id = '.DVD.'.product_id
LEFT JOIN '.BOOK.' ON '.PRODUCT.'.id = '.BOOK.'.product_id
LEFT JOIN '.FURNITURE.' ON '.PRODUCT.'.id = '.FURNITURE.'.product_id;';
}
public function insert($conn = null) : int
{
if ($conn === null) {
$conn = Database::connect();
}
$SKU = $this->getSKU();
$name = $this->getName();
$price = $this->getPrice();
$stmt = $conn->prepare(
'INSERT INTO '.PRODUCT.' (sku, name, price)
VALUES (?, ?, ?);'
);
$stmt->bind_param('ssd', $SKU, $name, $price);
if ($stmt->execute() === true) {
$this->setProductId($conn->insert_id);
return $conn->insert_id;
} else {
throw new Exception("Unable to insert object");
}
}
public function jsonSerialize() : mixed
{
return [
'id' => $this->getProductId(),
'sku' => $this->getSKU(),
'name' => $this->getName(),
'price' => $this->getPrice(),
'attribute' => $this->getFormatedAttr()
];
}
}

4
src/View/Product.php Normal file
View File

@ -0,0 +1,4 @@
<?php
require '../../Autoload.php';
echo json_encode(ProductList\Model\Product::selectAll());