Apparel DB - Erstes PHP Projekt
Datum: Dezember 2021
Lesedauer: 6 Minuten
Beschreibung
Meine Aufgabe war es, mit PHP eine CRUD-Webapplikation zu erstellen. In diesem Post beschreibe ich mein Vorgehen und zeige den aktuellen Projektstand.
Ideenfindung
Da ich mich für Caps und insbesondere Sneakers interessiere, dachte ich an eine Art Produktverwaltung.
In dieser kann man dann Artikel hinzufügen, editieren und löschen. Zudem soll es eine Übersichtsseite geben, welche einen Überblick über die persönlichen Produkte bietet.
Planung
Nachdem ich mich für die Idee entschieden habe, begann ich mögliche Klassen und deren Zusammenspiel zu definieren. Dies tat ich, indem ich ein UML-Diagramm erstellte.
Setup
Als das Diagramm fertig war, konnte ich mit dem Setup beginnen. Dazu musste ich drei Dinge vorbereiten.
MAMP
Damit man PHP verwenden kann, muss die Skriptsprache installiert oder von einem Webserver zur Verfügung gestellt werden. Ich verwende MAMP (opens in a new tab) als Webserver.
Da ich das Projekt im htdocs Ordner anlegte, kann ich es über den Localhost erreichen.
Datenbank
Als Datenbank verwende ich MySQL. Die Daten kann ich über phpMyAdmin verwalten.
Diese Seite ist ebenfalls über den Localhost zugänglich: http://localhost:8888/phpMyAdmin
Darin habe ich dann die entsprechenden Tabellen erstellt.
IDE
Als Entwicklungsumgebung verwende ich PhpStorm. Alternativ könnten Eclipse, NetBeans oder Visual Studio Code genutzt werden.
Software
Mein Projekt ist folgendermassen strukturiert.
Root
Im Root Verzeichnis trifft man hauptsächlich die Seiten der Webseite an. Die einzelnen Files werden hier genauer beschrieben.
index.php
Diese Datei enthält die Startseite. Auf dieser hat man die Wahl, ob man sich die Caps oder Sneakers ansehen will.
header.php
Der Header wird in allen Seiten verwendet und enthält das Grundlayout:
<!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>
Will man nun den Header verwenden, so muss man ihn nur inkludieren. Hat man zuvor der Titel Variable noch einen Wert zugewiesen, so wird dieser dann angezeigt.
$title = "Apparel DB | Edit Product";
include "header.php";
sneakers.php
Diese Datei verwendet Database.php
Objekt, um die Daten von der Datenbank zu holen. Diese stellt sie dann dar, indem sie über die entsprechenden Arrays loopt.
<?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
Um zu editieren werden ebenfalls die Daten mithilfe der Database Klasse geholt und dann in Input Felder geschrieben.
Hat der User etwas editiert, erstellt oder gelöscht, so wird das Formular gesendet. Hierbei wird der Post ans eigene File geschickt. Deshalb enthält dieses
File auch Logik.
Es kann die getätigten Änderungen erkennen, verarbeiten, zur Database.php Datei schicken und anschliessend eine Message ausgeben.
So wird beispielsweise eine neue Brand erstellt.
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
Bei manchen Feldern wird erwartet, dass eine bestimmte Anzahl an definierter Werte ausgewählt werden können. Dies ist z. B bei den Brands der Fall:
Um ein solches multiple select Feld darzustellen, verwendete ich CHOSEN
. Dies ist ein jQuery Plugin. Importiert man es ins Projekt, so muss man nur die CSS Klasse definieren, welche die Styles erhalten soll.
Scripts
Die Datenbank habe ich manuell mit Daten gefüllt. Wenn ich nun während dem Testen etwas ändere oder sogar zerstöre, dann würde dies viel Zeit
kosten, alles wieder einzutragen.
Aus diesem Grund habe ich im Scripts Ordner zwei Dateien angelegt.
createDb.php
Erstellt die Datenbank falls nicht vorhanden:
<?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
Erstellt die Tabellen, z. B. für 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);
?>
Klassenstruktur
Database.php
Diese Klasse ist das Herzstück der Applikation. Mit den connect
und disconnect
Methoden ist sie für die Datenbank Anbindung zuständig.
Zudem werden auch die CRUD Operationen auf der Datenbank über ein Objekt dieser ORM Klasse gesteuert.
public function delete(Entity $entity) {
$this->mysqli->query("DELETE FROM " . $entity->getTableName() . "
WHERE " . $entity->getTablePrimaryKey() . " = " . $entity->getId());
}
Entity-Klassen
Im Entity Ordner befinden sich die Klassen, welche die Produkte abbilden.
Neben der Brand, Sport und Team Klasse, befindet sich die Entity Klasse. Diese Klasse ist abstrakt. Dies bedeutet, dass aus ihr kein Objekt instanziiert werden kann.
Diese Klasse enthält die Attribute Name und Id. Sie wird von allen anderen Klassen vererbt.
Zudem hat Entity.php
zwei abstrakte Methoden definiert. Diese haben noch keine Funktionalität und müssen von den jeweiligen erbenden Klassen
implementiert werden.
abstract public function getTableName();
abstract public function getTablePrimaryKey();
Brand erbt von Entity und ist daher verpflichtet, die zwei Methoden zu implementieren.
Beispiel: Brand-Klasse
class Brand extends Entity {
public function getTableName() {
return "brand";
}
public function getTablePrimaryKey() {
return "brand_id";
}
}
Ohne diese Methoden, müsste Database.php
über mehrere delete Funktionen verfügen.
- deleteBrand(Brand $brand)
- deleteSneaker(Sneaker $sneaker)
- ...
Mithilfe der Entity Klasse konnte man das vereinheitlichen.
Nun reicht eine Methode, um die korrekten SQL Statements durchzuführen. Dieser Methode können dann alle Objekte, welche von Entity erben,
mitgegeben werden.
Dadurch kann nicht nur die Id, sondern auch der Tabellenname und Primärschlüssel ausgelesen werden.
public function delete(Entity $entity) {
$this->mysqli->query("DELETE FROM " . $entity->getTableName() . "
WHERE
" . $entity->getTablePrimaryKey() . " = " . $entity->getId());
}
Person
In diesem Verzeichnis befinden sich die Klassen, welche Personen abbilden.
Person.php
ist eine abstrakte Klasse. Sie erbt ebenfalls von Entity.php
und speichert zusätzliche Attribute, welche alle Personen gemeinsam haben. Dazu
gehört das Alter, der Wohnort und die Nationalität.
So müssen die Artist, Athlete und Designer Klassen nur noch Einzelheiten speichern.
Product
Im Product Ordner sind die zwei Produkt Klassen Cap und Sneaker. Auch hier gibt es wieder eine abstrakte Klasse, welche gemeinsame Attribute enthält.
MessageHandler
Um die Usability zu verbessern, habe ich einen MessageHandler eingebaut. Dieser zeigt, ob der SQL Befehl korrekt ausgeführt wurde.
Message.php
Die Message kann eine Nachricht und dessen Status speichern.
MessageHandler.php
Der Message Handler erstellt, speichert und gibt dann diese Nachrichten zurück.
Utility
Durch die Post Methode kann schädliche Befehle ins Programm gelangen. Deshalb ist es wichtig, dass man den Input validiert.
Um dies zu tun, habe ich eine Post Klasse geschrieben. Diese entfernt gefährliche Teile der Eingaben und gibt einen sauberen Wert zurück.
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;
}
}
Auf der Zeile 10 wird überprüft, ob es sich beim Inhalt des Posts um einen Array handelt. In diesem Fall muss man über den Array loopen und nur die Werte formatieren. Ansonsten würde man den Array zerstören.
Fazit
Bis jetzt fand ich es eine sehr spannende Aufgabe. Gerade im Bereich Refactoring habe ich mich sehr verbessert. Zudem habe ich mehr über Namespaces, ORM und den Umgang mit Files gelernt. Auch das Zusammenspiel mit einer solchen Datenbank war mir bis anhin noch nicht so bekannt.