Apparel DB - First PHP Project
Translated from German using DeepL.
Date: December 2021
Reading time: 6 minutes
Description
My task was to create a CRUD web application with PHP. In this post I describe my approach and show the current project status.
Brainstorming
Since I am interested in caps and especially sneakers, I thought of a kind of product management.
This would allow you to add, edit and delete items. There should also be an overview page that provides an overview of personal products.
Planning
Once I had decided on the idea, I started to define possible classes and their interaction. I did this by creating a UML diagram.
Setup
When the diagram was ready, I could start with the setup. I had to prepare three things.
MAMP
In order to use PHP, the scripting language must be installed or provided by a web server. I use MAMP (opens in a new tab) as a web server.
Since I created the project in the htdocs folder, I can access it via the localhost.
Database
I use MySQL as my database. I can manage the data via phpMyAdmin.
This page is also accessible via the localhost: http://localhost:8888/phpMyAdmin
I then created the corresponding tables there.
IDE
I use PhpStorm as my development environment. Alternatively, Eclipse, NetBeans or Visual Studio Code could be used.
Software
My project is structured as follows.
Root
The root directory mainly contains the pages of the website. The individual files are described in more detail here.
index.php
This file contains the start page. Here you can choose whether you want to view the caps or sneakers.
header.php
The header is used in all pages and contains the basic layout:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><?= $title; ?></title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<nav>
<ul>
<li><a href="index.php">Home</a></li>
<li><a href="editProduct.php">Edit</a></li>
</ul>
</nav>
If you now want to use the header, you only need to include it. If you have previously assigned a value to the title variable, this is then displayed.
$title = "Apparel DB | Edit Product";
include "header.php";
sneakers.php
This file uses Database.php
object to fetch the data from the database. It then displays it by looping over the corresponding arrays.
<?php foreach ($sneakers as $sneaker): ?>
<div class='m-product'>
<img src="<?= $sneaker->getImg(); ?>" alt="sneaker">
<?php if ($sneaker->getName() !== null) : ?>
<p>
<span class="a-title"><?= $sneaker->getName(); ?></span>
<br>
<?php endif; ?>
<?php if (!empty($sneaker->getBrands())) : ?>
<?php foreach ($sneaker->getBrands() as $brand): ?>
<?= $brand->getName(); ?>.
<?php endforeach; ?>
<br>
<?php endif; ?>
<?php if ($sneaker->getModel() !== null) : ?>
<?= $sneaker->getModel(); ?>
<br>
<?php endif; ?>
<?php if ($sneaker->getColour() !== null) : ?>
Color: <?= $sneaker->getColour(); ?>
<br>
<?php endif; ?>
<?php if ($sneaker->getQuantity() !== null) : ?>
Quantity: <?= $sneaker->getQuantity(); ?>
<br>
<?php endif; ?>
<?php if ($sneaker->getSize() !== null) : ?>
Size: <?= $sneaker->getSize(); ?>
<br>
<?php endif; ?>
<?php if ($sneaker->getPrice() !== null) : ?>
Price: <?= $sneaker->getPrice(); ?> $
<br>
<?php endif; ?>
<?php if ($sneaker->getRetail() !== null) : ?>
Retail: <?= $sneaker->getRetail(); ?> $
<br>
<?php endif; ?>
<?php if (!empty($sneaker->getArtists())) : ?>
Artist/s:
<?php foreach ($sneaker->getArtists() as $artist): ?>
<?= $artist->getName(); ?>
<br>
<?php endforeach; ?>
<?php endif; ?>
<?php if (!empty($sneaker->getDesigners())) : ?>
Designer/s:
<?php foreach ($sneaker->getDesigners() as $designer): ?>
<?= $designer->getName(); ?>
<br>
<?php endforeach; ?>
<?php endif; ?>
<?php if (!empty($sneaker->getAthletes())) : ?>
Athlete/s:
<?php foreach ($sneaker->getAthletes() as $athlete): ?>
<?= $athlete->getName(); ?>
<br>
<?php endforeach; ?>
<?php endif; ?>
</p>
</div>
<?php endforeach; ?>
editProduct.php
To edit, the data is also retrieved using the database class and then written to input fields.
If the user has edited, created or deleted something, the form is sent. The post is sent to its own file. This is why this
file also contains logic.
It can recognize the changes made, process them, send them to the Database.php file and then output a message.
For example, a new brand is created.
if ($post->get("addBrand") !== null) {
if (!empty($post->get("brandNewName"))) {
$brand = new Brand(0, $post->get("brandNewName"));
$data->addBrand($brand);
$messageHandler->addSuccessMessage("successfully added " . $brand->getName());
}
}
Chosen
For some fields, it is expected that a certain number of defined values can be selected. This is the case with the brands, for example:
To display such a multiple select field, I used CHOSEN
. This is a jQuery plugin. If you import it into the project, you only have to define the CSS class that should receive the styles.
Scripts
I manually filled the database with data. If I change or even destroy something during testing, it would take a lot of time to re-enter everything.
to enter everything again.
For this reason, I have created two files in the scripts folder.
createDb.php
Creates the database if it does not exist:
<?php
$mysqli = new mysqli("localhost", "root", "root");
if (!mysqli_select_db($mysqli, "apparel")) {
$mysqli->query("CREATE DATABASE apparel");
mysqli_select_db($mysqli, "apparel");
}
$mysqli->close();
?>
createTables.php
Creates the tables, e.g. for brand
:
<?php filename="brand tale create in createTables.php"
$mysqli = new mysqli("localhost", "root", "root", "apparel");
$brandSql = "
CREATE TABLE IF NOT EXISTS brand(
name VARCHAR(25) NOT NULL ,
brand_id INT NOT NULL AUTO_INCREMENT ,
PRIMARY KEY (brand_id)
);
";
$mysqli->query($brandSql);
?>
Class structure
Database.php
This class is the heart of the application. With the connect
and disconnect
methods, it is responsible for the database connection.
In addition, the CRUD operations on the database are also controlled via an object of this ORM class.
public function delete(Entity $entity) {
$this->mysqli->query("DELETE FROM " . $entity->getTableName() . "
WHERE " . $entity->getTablePrimaryKey() . " = " . $entity->getId());
}
Entity classes
The entity folder contains the classes that represent the products.
Next to the Brand, Sport and Team classes is the Entity class. This class is abstract. This means that no object can be instantiated from it. can be instantiated from it.
This class contains the Name and Id attributes. It is inherited by all other classes.
In addition, Entity.php
has defined two abstract methods. These do not yet have any functionality and must be implemented by the respective inheriting classes.
abstract public function getTableName();
abstract public function getTablePrimaryKey();
Brand inherits from Entity and is therefore obliged to implement the two methods.
Example: Fire class
class Brand extends Entity {
public function getTableName() {
return "brand";
}
public function getTablePrimaryKey() {
return "brand_id";
}
}
Without these methods, Database.php
would have to have several delete functions.
- deleteBrand(Brand $brand)
- deleteSneaker(Sneaker $sneaker)
- ...
This could be standardized with the help of the entity class.
Now one method is enough to execute the correct SQL statements. All objects that inherit from Entity can then be passed to this method,
can be passed to this method.
This means that not only the ID, but also the table name and primary key can be read.
public function delete(Entity $entity) {
$this->mysqli->query("DELETE FROM " . $entity->getTableName() . "
WHERE
" . $entity->getTablePrimaryKey() . " = " . $entity->getId());
}
Person
This directory contains the classes that represent persons.
Person.php
is an abstract class. It also inherits from Entity.php
and stores additional attributes that all persons have in common. These
age, place of residence and nationality.
This means that the Artist, Athlete and Designer classes only need to store details.
Product
The Product folder contains the two product classes Cap and Sneaker. Here, too, there is an abstract class that contains common attributes.
MessageHandler
To improve usability, I have added a MessageHandler. This shows whether the SQL command was executed correctly.
Message.php
The message can store a message and its status.
MessageHandler.php
The message handler creates, saves and then returns these messages.
Utility
Malicious commands can enter the program through the post method. Therefore it is important to validate the input.
To do this, I have written a Post class. This removes dangerous parts of the input and returns a clean value.
namespace Utility;
class Post {
private function clean(string $string) {
return trim(htmlspecialchars(strip_tags($string)));
}
public function get(string $post) {
if (isset($_POST[$post])) {
if (is_array($_POST[$post])) {
$postArray = [];
foreach ($_POST[$post] as $item) {
array_push($postArray, $this->clean($item));
}
return $postArray;
} else {
return $this->clean($_POST[$post]);
}
}
return null;
}
}
Line 10 checks whether the content of the post is an array. In this case, you must loop over the array and only format the format the values. Otherwise the array would be destroyed.
Conclusion
So far I have found it a very exciting task. I have improved a lot, especially in the area of refactoring. I have also learned more about namespaces, ORM and dealing with files. I was also unfamiliar with the interaction with such a database.