2021
Apparel (PHP)

Apparel DB - First PHP Project

Translated from German using DeepL.

Date: December 2021
Reading time: 6 minutes


home

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.

uml

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.

db

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.

structure

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:

header.php
<!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.

editProduct.php
$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.

foreach in sneakers.php
<?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.

add brand in editProduct.php
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:

chosen-brands

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:

createDb.php
<?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.

entity

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.

abstrakte Methoden in Entity.php
abstract public function getTableName();
abstract public function getTablePrimaryKey();

Brand inherits from Entity and is therefore obliged to implement the two methods.

Example: Fire class
Brand.php
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.

delete in Database.php
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.

Post.php
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.