abschluss
This commit is contained in:
39
app/Core/Container.php
Normal file
39
app/Core/Container.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace Blog\Core;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Ein einfacher Dependency Injection Container.
|
||||
*/
|
||||
class Container {
|
||||
/**
|
||||
* @var array Liste der registrierten Instanzen.
|
||||
*/
|
||||
private array $instances = [];
|
||||
|
||||
/**
|
||||
* Registriert eine Instanz oder Factory-Funktion im Container.
|
||||
*
|
||||
* @param string $key Der eindeutige Schlüssel für die Instanz.
|
||||
* @param callable $factory Eine Factory-Funktion, die eine Instanz erzeugt.
|
||||
* @return void
|
||||
*/
|
||||
public function set(string $key, callable $factory): void {
|
||||
$this->instances[$key] = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruft eine registrierte Instanz ab und erstellt sie falls nötig.
|
||||
*
|
||||
* @param string $key Der Schlüssel der angeforderten Instanz.
|
||||
* @return mixed Die abgerufene Instanz.
|
||||
* @throws Exception Wenn keine Instanz mit dem gegebenen Schlüssel existiert.
|
||||
*/
|
||||
public function get(string $key): mixed {
|
||||
if(!isset($this->instances[$key])) {
|
||||
throw new Exception("No instance found for {$key}");
|
||||
}
|
||||
return $this->instances[$key]($this);
|
||||
}
|
||||
}
|
@ -1,18 +1,40 @@
|
||||
<?php
|
||||
namespace Blog\Core;
|
||||
|
||||
use Blog\Middleware\middlewareInterface;
|
||||
use Blog\Http\request;
|
||||
use Blog\Http\response;
|
||||
use Blog\Middleware\MiddlewareInterface;
|
||||
use Blog\Http\Request;
|
||||
use Blog\Http\Response;
|
||||
|
||||
/**
|
||||
* Router-Klasse für das Routing von HTTP-Anfragen.
|
||||
*/
|
||||
class Router {
|
||||
/**
|
||||
* @var array Liste der registrierten Routen.
|
||||
*/
|
||||
private array $routes = [];
|
||||
|
||||
/**
|
||||
* Fügt eine neue Route hinzu.
|
||||
*
|
||||
* @param string $method HTTP-Methode (z.B. GET, POST).
|
||||
* @param string $path Pfad der Route, kann Platzhalter enthalten.
|
||||
* @param callable $handler Handler-Funktion für die Route.
|
||||
* @param array $middlewares Liste der Middlewares für die Route.
|
||||
* @return void
|
||||
*/
|
||||
public function addRoute(string $method, string $path, callable $handler, array $middlewares = []): void {
|
||||
$path = preg_replace('/{(\w+)}/', '(?P<$1>[^/]+)', $path);
|
||||
$this->routes[] = compact("method", "path", "handler", "middlewares");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet eine eingehende HTTP-Anfrage und sucht nach einer passenden Route.
|
||||
*
|
||||
* @param Request $req Die eingehende HTTP-Anfrage.
|
||||
* @param Response $res Die HTTP-Antwort.
|
||||
* @return void
|
||||
*/
|
||||
public function dispatch(Request $req, Response $res): void {
|
||||
$method = $req->getMethod();
|
||||
$uri = $req->getPath();
|
||||
@ -36,6 +58,14 @@ class Router {
|
||||
->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt die definierten Middlewares für eine Anfrage aus.
|
||||
*
|
||||
* @param array $middlewares Liste der Middlewares.
|
||||
* @param Request $req Die eingehende HTTP-Anfrage.
|
||||
* @param Response $res Die HTTP-Antwort.
|
||||
* @return bool Gibt true zurück, wenn alle Middlewares erfolgreich ausgeführt wurden, andernfalls false.
|
||||
*/
|
||||
private function handleMiddlewares(array $middlewares, Request $req, Response $res): bool {
|
||||
foreach($middlewares as $middleware) {
|
||||
$middlewareInstance = is_string($middleware) ? new $middleware() : $middleware;
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
namespace Blog\Core;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Container {
|
||||
private array $instances = [];
|
||||
|
||||
public function set(string $key, callable $factory): void {
|
||||
$this->instances[$key] = $factory;
|
||||
}
|
||||
|
||||
public function get(string $key): mixed {
|
||||
if(!isset($this->instances[$key])) {
|
||||
throw new Exception("No instance found for {$key}");
|
||||
}
|
||||
return $this->instances[$key]($this);
|
||||
}
|
||||
}
|
@ -4,9 +4,21 @@ namespace Blog\Database;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* Verwaltet die Verbindung zur MySQL-Datenbank.
|
||||
*/
|
||||
class Database {
|
||||
/**
|
||||
* @var PDO|null Statische Instanz der Datenbankverbindung.
|
||||
*/
|
||||
private static ?PDO $pdo = null;
|
||||
|
||||
/**
|
||||
* Stellt eine Verbindung zur Datenbank her oder gibt die bestehende zurück.
|
||||
*
|
||||
* @return PDO Die aktive PDO-Verbindung.
|
||||
* @throws PDOException Falls die Verbindung fehlschlägt.
|
||||
*/
|
||||
public static function getConnection(): PDO {
|
||||
if(self::$pdo === null) {
|
||||
$config = parse_ini_file(__DIR__ . "/../../.env");
|
72
app/Entity/Post.php
Normal file
72
app/Entity/Post.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace Blog\Entity;
|
||||
|
||||
/**
|
||||
* Repräsentiert einen Blogpost mit ID, Titel, Inhalt, Autor und Zeitstempel.
|
||||
*/
|
||||
class Post {
|
||||
/**
|
||||
* Erstellt eine neue Post-Instanz.
|
||||
*
|
||||
* @param int $id Die eindeutige ID des Blogposts.
|
||||
* @param string $title Der Titel des Blogposts.
|
||||
* @param string $content Der Inhalt des Blogposts.
|
||||
* @param string $author Der Name des Autors.
|
||||
* @param int $stamp Der Zeitstempel der Veröffentlichung.
|
||||
*/
|
||||
public function __construct(
|
||||
private int $id,
|
||||
private string $title,
|
||||
private string $content,
|
||||
private string $author,
|
||||
private int $stamp
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Gibt die ID des Blogposts zurück.
|
||||
*
|
||||
* @return int Die eindeutige ID.
|
||||
*/
|
||||
public function getId(): int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Titel des Blogposts zurück.
|
||||
*
|
||||
* @return string Der Titel des Blogposts.
|
||||
*/
|
||||
public function getTitle(): string {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Inhalt des Blogposts zurück. Optional kann eine maximale Länge angegeben werden.
|
||||
*
|
||||
* @param int|null $maxlength Die maximale Zeichenanzahl, falls angegeben.
|
||||
* @return string Der gekürzte oder vollständige Inhalt des Blogposts.
|
||||
*/
|
||||
public function getContent($maxlength = null): string {
|
||||
return $maxlength
|
||||
? mb_strimwidth($this->content, 0, $maxlength, "...")
|
||||
: $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Namen des Autors zurück.
|
||||
*
|
||||
* @return string Der Name des Autors.
|
||||
*/
|
||||
public function getAuthor(): string {
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt das Veröffentlichungsdatum und die Uhrzeit formatiert zurück.
|
||||
*
|
||||
* @return string Das Datum und die Uhrzeit im Format `d.m.Y H:i:s`.
|
||||
*/
|
||||
public function getDateTime(): string {
|
||||
return date('d.m.Y H:i:s', $this->stamp);
|
||||
}
|
||||
}
|
47
app/Entity/User.php
Normal file
47
app/Entity/User.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace Blog\Entity;
|
||||
|
||||
/**
|
||||
* Repräsentiert einen Benutzer mit ID, Benutzernamen und Passwort.
|
||||
*/
|
||||
class User {
|
||||
/**
|
||||
* Erstellt eine neue User-Instanz.
|
||||
*
|
||||
* @param int $id Die eindeutige ID des Benutzers.
|
||||
* @param string $username Der Benutzername.
|
||||
* @param string $password Das Passwort des Benutzers.
|
||||
*/
|
||||
public function __construct(
|
||||
private int $id,
|
||||
private string $username,
|
||||
private string $password
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Gibt die ID des Benutzers zurück.
|
||||
*
|
||||
* @return int Die eindeutige ID des Benutzers.
|
||||
*/
|
||||
public function getId(): int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Benutzernamen zurück.
|
||||
*
|
||||
* @return string Der Benutzername.
|
||||
*/
|
||||
public function getUsername(): string {
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt das Passwort des Benutzers zurück.
|
||||
*
|
||||
* @return string Das Passwort.
|
||||
*/
|
||||
public function getPassword(): string {
|
||||
return $this->password;
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
namespace Blog\Entity;
|
||||
|
||||
class Post {
|
||||
public function __construct(
|
||||
private int $id,
|
||||
private string $title,
|
||||
private string $content,
|
||||
private string $author,
|
||||
private int $stamp
|
||||
) {}
|
||||
|
||||
public function getId() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getContent($maxlength = null) {
|
||||
return $maxlength ? mb_strimwidth($this->content, 0, $maxlength, "...") : $this->content;
|
||||
}
|
||||
|
||||
public function getAuthor() {
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
public function getDateTime() {
|
||||
return $this->stamp;
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
namespace Blog\Entity;
|
||||
|
||||
class User {
|
||||
private $id;
|
||||
private $username;
|
||||
private $password;
|
||||
|
||||
public function __construct($id, $username, $password) {
|
||||
$this->id = $id;
|
||||
$this->username = $username;
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
public function getId() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUsername() {
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getPassword() {
|
||||
return $this->password;
|
||||
}
|
||||
}
|
106
app/Http/Request.php
Normal file
106
app/Http/Request.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
namespace Blog\Http;
|
||||
|
||||
/**
|
||||
* Stellt eine HTTP-Request-Objektklasse bereit.
|
||||
*
|
||||
* Kapselt HTTP-Methoden, Pfad, POST-Daten, Header und Rohdatenzugriff.
|
||||
*/
|
||||
class Request {
|
||||
private string $method;
|
||||
private string $path;
|
||||
private array $postData;
|
||||
|
||||
/**
|
||||
* Konstruktor für das Request-Objekt.
|
||||
*
|
||||
* @param string $method HTTP-Methode (z.B. GET, POST)
|
||||
* @param string $uri URI der Anfrage
|
||||
* @param array $postData Optional: POST-Daten (Standard: $_POST)
|
||||
*/
|
||||
public function __construct(string $method, string $uri, array $postData = []) {
|
||||
$this->method = strtoupper($method);
|
||||
$this->path = parse_url($uri, PHP_URL_PATH) ?? '/';
|
||||
$this->postData = $postData ?: $_POST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die HTTP-Methode zurück.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMethod(): string {
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Pfad der Anfrage zurück.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath(): string {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt einen POST-Wert anhand des Schlüssels zurück.
|
||||
*
|
||||
* @param string $key Schlüssel im POST-Array
|
||||
* @param mixed $default Optionaler Standardwert
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPost(string $key, $default = null): mixed {
|
||||
return $this->postData[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle POST-Daten als Array zurück.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function allPost(): array {
|
||||
return $this->postData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt einen Query-Parameter anhand des Schlüssels zurück.
|
||||
*
|
||||
* @param string $key Schlüssel im Query-String
|
||||
* @param mixed $default Optionaler Standardwert
|
||||
* @return mixed
|
||||
*/
|
||||
public function getQuery(string $key, $default = null): mixed {
|
||||
$query = [];
|
||||
parse_str(parse_url($this->path, PHP_URL_QUERY) ?? '', $query);
|
||||
return $query[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt einen HTTP-Header zurück.
|
||||
*
|
||||
* @param string $key Header-Name (z.B. 'Content-Type')
|
||||
* @return string|null
|
||||
*/
|
||||
public function getHeader(string $key): ?string {
|
||||
$headerKey = 'HTTP_' . strtoupper(str_replace('-', '_', $key));
|
||||
return $_SERVER[$headerKey] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den rohen Anfrage-Body zurück.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawInput(): string {
|
||||
return file_get_contents('php://input');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Anfrage-Body als assoziatives Array zurück (JSON).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJson(): array {
|
||||
return json_decode($this->getRawInput(), true) ?? [];
|
||||
}
|
||||
}
|
95
app/Http/Response.php
Normal file
95
app/Http/Response.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace Blog\Http;
|
||||
|
||||
/**
|
||||
* Stellt eine HTTP-Response-Objektklasse bereit.
|
||||
*
|
||||
* Kapselt Statuscode, Header, Body und Methoden zum Senden von Antworten.
|
||||
*/
|
||||
class Response {
|
||||
private int $status = 200;
|
||||
private array $headers = [];
|
||||
private string $body = "";
|
||||
|
||||
/**
|
||||
* Setzt den HTTP-Statuscode.
|
||||
*
|
||||
* @param int $status HTTP-Statuscode
|
||||
* @return self
|
||||
*/
|
||||
public function setStatus(int $status): self {
|
||||
$this->status = $status;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt einen HTTP-Header hinzu.
|
||||
*
|
||||
* @param string $key Header-Name
|
||||
* @param string $val Header-Wert
|
||||
* @return self
|
||||
*/
|
||||
public function addHeader(string $key, string $val): self {
|
||||
$this->headers[$key] = $val;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt das Response-Objekt zurück (für Method Chaining).
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function getBody(): self {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt dem Body Inhalt hinzu.
|
||||
*
|
||||
* @param string $content Inhalt, der angehängt wird
|
||||
* @return self
|
||||
*/
|
||||
public function write(string $content): self {
|
||||
$this->body .= $content;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet die HTTP-Antwort an den Client.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function send(): void {
|
||||
http_response_code($this->status);
|
||||
foreach($this->headers as $key => $val)
|
||||
header("{$key}: {$val}");
|
||||
echo $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet eine JSON-Antwort.
|
||||
*
|
||||
* @param array $data Zu sendende Daten
|
||||
* @param int $status Optionaler HTTP-Statuscode (Standard: 200)
|
||||
* @return self
|
||||
*/
|
||||
public function json(array $data, int $status = 200): self {
|
||||
$this->setStatus($status);
|
||||
header("Content-Type: application/json");
|
||||
$this->body = json_encode($data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt eine HTTP-Weiterleitung durch.
|
||||
*
|
||||
* @param string $url Ziel-URL
|
||||
* @param int $status Optionaler Statuscode (Standard: 302)
|
||||
* @return void
|
||||
*/
|
||||
public function redirect(string $url, int $status = 302): void {
|
||||
http_response_code($status);
|
||||
header("Location: {$url}");
|
||||
exit;
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
namespace Blog\Http;
|
||||
|
||||
class Request {
|
||||
private string $method;
|
||||
private string $path;
|
||||
private array $postData;
|
||||
|
||||
public function __construct(string $method, string $uri, array $postData = []) {
|
||||
$this->method = strtoupper($method);
|
||||
$this->path = parse_url($uri, PHP_URL_PATH) ?? '/';
|
||||
$this->postData = $postData ?: $_POST;
|
||||
}
|
||||
|
||||
public function getMethod(): string {
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function getPath(): string {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getPost(string $key, $default = null): mixed {
|
||||
return $this->postData[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function allPost(): array {
|
||||
return $this->postData;
|
||||
}
|
||||
|
||||
public function getQuery(string $key, $default = null): mixed {
|
||||
$query = [];
|
||||
parse_str(parse_url($this->path, PHP_URL_QUERY) ?? '', $query);
|
||||
return $query[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function getHeader(string $key): ?string {
|
||||
$headerKey = 'HTTP_' . strtoupper(str_replace('-', '_', $key));
|
||||
return $_SERVER[$headerKey] ?? null;
|
||||
}
|
||||
|
||||
public function getRawInput(): string {
|
||||
return file_get_contents('php://input');
|
||||
}
|
||||
|
||||
public function getJson(): array {
|
||||
return json_decode($this->getRawInput(), true) ?? [];
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
<?php
|
||||
namespace Blog\Http;
|
||||
|
||||
class Response {
|
||||
private int $status = 200;
|
||||
private array $headers = [];
|
||||
private string $body = "";
|
||||
|
||||
public function setStatus(int $status): self {
|
||||
$this->status = $status;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addHeader(string $key, string $val): self {
|
||||
$this->headers[$key] = $val;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBody(): self {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function write(string $content): self {
|
||||
$this->body .= $content;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function send(): void {
|
||||
http_response_code($this->status);
|
||||
foreach($this->headers as $key => $val)
|
||||
header("{$key}: {$val}");
|
||||
echo $this->body;
|
||||
}
|
||||
|
||||
public function json(array $data, int $status = 200): self {
|
||||
$this->setStatus($status);
|
||||
header("Content-Type: application/json");
|
||||
$this->body = json_encode($data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function redirect(string $url, int $status = 302): void {
|
||||
http_response_code($status);
|
||||
header("Location: {$url}");
|
||||
exit;
|
||||
}
|
||||
}
|
@ -1,11 +1,23 @@
|
||||
<?php
|
||||
namespace Blog\Middleware;
|
||||
|
||||
use Blog\Middleware\middlewareInterface;
|
||||
use Blog\Http\request;
|
||||
use Blog\Http\response;
|
||||
use Blog\Middleware\MiddlewareInterface;
|
||||
use Blog\Http\Request;
|
||||
use Blog\Http\Response;
|
||||
|
||||
/**
|
||||
* Middleware zur Authentifizierung und CSRF-Prüfung.
|
||||
*
|
||||
* Prüft, ob ein Benutzer eingeloggt ist und ob ein gültiges CSRF-Token vorliegt.
|
||||
*/
|
||||
class AuthMiddleware implements MiddlewareInterface {
|
||||
/**
|
||||
* Führt die Authentifizierungs- und CSRF-Prüfung durch.
|
||||
*
|
||||
* @param Request $request Das aktuelle Request-Objekt
|
||||
* @param Response $response Das aktuelle Response-Objekt
|
||||
* @return bool true, wenn die Anfrage fortgesetzt werden darf, sonst false
|
||||
*/
|
||||
public function handle(Request $request, Response $response): bool {
|
||||
if(!isset($_SESSION['user'])) {
|
||||
$response
|
||||
@ -28,11 +40,22 @@ class AuthMiddleware implements MiddlewareInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
private function validateCSRFToken(Request $request): bool {
|
||||
/**
|
||||
* Prüft, ob das CSRF-Token gültig ist.
|
||||
*
|
||||
* @param Request $request Das aktuelle Request-Objekt
|
||||
* @return bool true, wenn das Token gültig ist, sonst false
|
||||
*/
|
||||
public static function validateCSRFToken(Request $request): bool {
|
||||
$token = $request->getPost('_csrf_token') ?? '';
|
||||
return hash_equals($_SESSION['_csrf_token'] ?? '', $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert und gibt ein CSRF-Token zurück.
|
||||
*
|
||||
* @return string Das generierte oder vorhandene CSRF-Token
|
||||
*/
|
||||
public static function generateCSRFToken(): string {
|
||||
if(!isset($_SESSION['_csrf_token'])) {
|
||||
$_SESSION['_csrf_token'] = bin2hex(random_bytes(32));
|
21
app/Middleware/MiddlewareInterface.php
Normal file
21
app/Middleware/MiddlewareInterface.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Blog\Middleware;
|
||||
|
||||
use Blog\Http\Request;
|
||||
use Blog\Http\Response;
|
||||
|
||||
/**
|
||||
* Interface für Middleware-Komponenten.
|
||||
*
|
||||
* Definiert eine Methode zur Bearbeitung von HTTP-Anfragen und -Antworten.
|
||||
*/
|
||||
interface MiddlewareInterface {
|
||||
/**
|
||||
* Bearbeitet die eingehende Anfrage und Antwort.
|
||||
*
|
||||
* @param Request $request Das aktuelle Request-Objekt
|
||||
* @param Response $response Das aktuelle Response-Objekt
|
||||
* @return bool true, wenn die Anfrage fortgesetzt werden darf, sonst false
|
||||
*/
|
||||
public function handle(Request $request, Response $response): bool;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
<?php
|
||||
namespace Blog\Middleware;
|
||||
|
||||
use Blog\Http\request;
|
||||
use Blog\Http\response;
|
||||
|
||||
interface MiddlewareInterface {
|
||||
public function handle(Request $request, Response $response): bool;
|
||||
}
|
117
app/Model/PostModel.php
Normal file
117
app/Model/PostModel.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
namespace Blog\Model;
|
||||
|
||||
use Blog\Entity\Post;
|
||||
use Blog\Database\Database;
|
||||
use Exception;
|
||||
|
||||
class PostModel {
|
||||
private $db;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = Database::getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Blogposts als Array von Post-Objekten zurück.
|
||||
*
|
||||
* @return Post[] Array mit allen Blogposts.
|
||||
* @throws Exception Wenn keine Einträge vorhanden sind.
|
||||
*/
|
||||
public function getPosts(): array {
|
||||
$posts = [];
|
||||
$query = $this->db->query(<<<SQL
|
||||
SELECT p.id, u.username, p.title, p.content, p.stamp
|
||||
FROM posts p
|
||||
JOIN users u ON u.id = p.author_id
|
||||
ORDER BY p.id DESC
|
||||
SQL);
|
||||
|
||||
if(!$query->rowCount())
|
||||
throw new Exception("no entries available");
|
||||
|
||||
while($row = $query->fetchObject()) {
|
||||
$posts[] = new Post($row->id, $row->title, $row->content, $row->username, $row->stamp);
|
||||
}
|
||||
return $posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt einen einzelnen Blogpost anhand der ID zurück.
|
||||
*
|
||||
* @param int $id Die ID des Blogposts.
|
||||
* @return Post|null Das Post-Objekt oder null, falls nicht gefunden.
|
||||
* @throws Exception Wenn kein Eintrag gefunden wurde.
|
||||
*/
|
||||
public function getPost($id): ?Post {
|
||||
$stmt = $this->db->prepare(<<<SQL
|
||||
SELECT p.id, u.username, p.title, p.content, p.stamp
|
||||
FROM posts p
|
||||
JOIN users u ON u.id = p.author_id
|
||||
WHERE p.id = :id
|
||||
LIMIT 1
|
||||
SQL);
|
||||
$stmt->bindParam(":id", $id, $this->db::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
if(!$stmt->rowCount())
|
||||
throw new Exception("no entry found");
|
||||
|
||||
$row = $stmt->fetchObject();
|
||||
return new Post($row->id, $row->title, $row->content, $row->username, $row->stamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert den Inhalt eines Blogposts.
|
||||
*
|
||||
* @param int $id Die ID des Blogposts.
|
||||
* @param string $content Der neue Inhalt.
|
||||
* @return bool Erfolg der Aktualisierung.
|
||||
*/
|
||||
public function updatePostContent($id, $content) {
|
||||
$stmt = $this->db->prepare(<<<SQL
|
||||
UPDATE posts
|
||||
SET content = :content
|
||||
WHERE id = :id
|
||||
SQL);
|
||||
$stmt->bindValue(':content', $content, \PDO::PARAM_STR);
|
||||
$stmt->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Legt einen neuen Blogpost an.
|
||||
*
|
||||
* @param string $title Der Titel des Blogposts.
|
||||
* @param string $content Der Inhalt des Blogposts.
|
||||
* @param int $authorId Die ID des Autors.
|
||||
* @return bool Erfolg des Einfügens.
|
||||
*/
|
||||
public function createPost($title, $content, $authorId) {
|
||||
$stmt = $this->db->prepare(<<<SQL
|
||||
INSERT INTO posts (title, content, author_id, stamp)
|
||||
VALUES
|
||||
(:title, :content, :author_id, :stamp)
|
||||
SQL);
|
||||
$stmt->bindValue(':title', $title, \PDO::PARAM_STR);
|
||||
$stmt->bindValue(':content', $content, \PDO::PARAM_STR);
|
||||
$stmt->bindValue(':author_id', $authorId, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(':stamp', time(), \PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht einen Blogpost anhand der ID.
|
||||
*
|
||||
* @param int $id Die ID des Blogposts.
|
||||
* @return bool Erfolg des Löschens.
|
||||
*/
|
||||
public function deletePost($id) {
|
||||
$stmt = $this->db->prepare(<<<SQL
|
||||
DELETE FROM posts
|
||||
WHERE id = :id
|
||||
SQL);
|
||||
$stmt->bindValue(':id', $id, \PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
}
|
||||
}
|
46
app/Model/UserModel.php
Normal file
46
app/Model/UserModel.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace Blog\Model;
|
||||
|
||||
use Blog\Entity\User;
|
||||
use Blog\Database\Database;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Modellklasse für Benutzer-bezogene Datenbankoperationen.
|
||||
*/
|
||||
class UserModel {
|
||||
/**
|
||||
* @var PDO Die Datenbankverbindung.
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* Konstruktor. Stellt die Datenbankverbindung her.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->db = Database::getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt einen Benutzer anhand des Benutzernamens.
|
||||
*
|
||||
* @param string $username Der Benutzername.
|
||||
* @return User|null Das User-Objekt oder null, falls nicht gefunden.
|
||||
*/
|
||||
public function getUserByUsername(string $username):?User {
|
||||
$stmt = $this->db->prepare(<<<SQL
|
||||
SELECT id, username, password
|
||||
FROM users
|
||||
WHERE username = :username
|
||||
LIMIT 1
|
||||
SQL);
|
||||
$stmt->bindParam(':username', $username);
|
||||
$stmt->execute();
|
||||
|
||||
$row = $stmt->fetchObject();
|
||||
if(!$row)
|
||||
return null;
|
||||
|
||||
return new User($row->id, $row->username, $row->password);
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
namespace Blog\Model;
|
||||
|
||||
use Blog\Entity\post;
|
||||
use Blog\Database\database;
|
||||
use Exception;
|
||||
|
||||
class PostModel {
|
||||
private $db;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = Database::getConnection();
|
||||
}
|
||||
|
||||
public function getPosts(): array {
|
||||
$posts = [];
|
||||
$query = $this->db->query(<<<SQL
|
||||
SELECT p.id, u.username, p.title, p.content, p.stamp
|
||||
FROM posts p
|
||||
JOIN users u ON u.id = p.author_id
|
||||
SQL);
|
||||
|
||||
if(!$query->rowCount())
|
||||
throw new Exception("no entries available");
|
||||
|
||||
while($row = $query->fetchObject()) {
|
||||
$posts[] = new Post($row->id, $row->title, $row->content, $row->username, $row->stamp);
|
||||
}
|
||||
return $posts;
|
||||
}
|
||||
|
||||
public function getPost($id): ?Post {
|
||||
$query = $this->db->prepare(<<<SQL
|
||||
SELECT p.id, u.username, p.title, p.content, p.stamp
|
||||
FROM posts p
|
||||
JOIN users u ON u.id = p.author_id
|
||||
WHERE p.id = :id
|
||||
LIMIT 1
|
||||
SQL);
|
||||
$query->bindParam(":id", $id, $this->db::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if(!$query->rowCount())
|
||||
throw new Exception("no entry found");
|
||||
|
||||
$row = $query->fetchObject();
|
||||
return new Post($row->id, $row->title, $row->content, $row->username, $row->stamp);
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
namespace Blog\Model;
|
||||
|
||||
use Blog\Entity\user;
|
||||
use Blog\Database\database;
|
||||
use PDO;
|
||||
|
||||
class UserModel {
|
||||
private $db;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = Database::getConnection();
|
||||
}
|
||||
|
||||
public function getUserByUsername(string $username):?User {
|
||||
$query = $this->db->prepare(<<<SQL
|
||||
SELECT id, username, password
|
||||
FROM users
|
||||
WHERE username = :username
|
||||
LIMIT 1
|
||||
SQL);
|
||||
$query->bindParam(':username', $username);
|
||||
$query->execute();
|
||||
|
||||
$row = $query->fetchObject();
|
||||
if(!$row)
|
||||
return null;
|
||||
|
||||
return new User($row->id, $row->username, $row->password);
|
||||
}
|
||||
}
|
156
app/Template/Twig.php
Normal file
156
app/Template/Twig.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
namespace Blog\Template;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Einfache Template-Engine zur Verarbeitung von Twig-ähnlichen Templates.
|
||||
*/
|
||||
class Twig {
|
||||
/**
|
||||
* @var array Enthält die erkannten Template-Blöcke.
|
||||
*/
|
||||
private array $blocks = [];
|
||||
|
||||
/**
|
||||
* @var string Pfad zum Template-Verzeichnis.
|
||||
*/
|
||||
private string $viewsPath;
|
||||
|
||||
/**
|
||||
* @var array Globale Variablen, die in allen Templates verfügbar sind.
|
||||
*/
|
||||
private array $globals = [];
|
||||
|
||||
/**
|
||||
* Konstruktor.
|
||||
*
|
||||
* @param string $viewsPath Pfad zum Template-Verzeichnis.
|
||||
*/
|
||||
public function __construct($viewsPath) {
|
||||
$this->viewsPath = rtrim($viewsPath, '/') . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt globale Variablen für alle Templates.
|
||||
*
|
||||
* @param array $globals Assoziatives Array mit globalen Variablen.
|
||||
*/
|
||||
public function setGlobals(array $globals) {
|
||||
$this->globals = $globals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert ein Template mit den übergebenen Daten.
|
||||
*
|
||||
* @param string $file Name der Template-Datei (ohne Pfad).
|
||||
* @param array $data Assoziatives Array mit Variablen für das Template.
|
||||
* @return string Gerenderter HTML-Code.
|
||||
* @throws Exception Wenn das Template nicht gefunden oder ein Fehler auftritt.
|
||||
*/
|
||||
public function render($file, $data = []): string {
|
||||
$data = array_merge($this->globals, $data);
|
||||
|
||||
try {
|
||||
$code = $this->includeFiles($file);
|
||||
$code = $this->compileCode($code);
|
||||
extract($data, EXTR_SKIP);
|
||||
|
||||
ob_start();
|
||||
eval('?>' . $code);
|
||||
return ob_get_clean();
|
||||
} catch(Exception $e) {
|
||||
throw new Exception("Error rendering template: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wandelt den Template-Code in ausführbaren PHP-Code um.
|
||||
*
|
||||
* @param string $code Der Template-Code.
|
||||
* @return string Kompilierter PHP-Code.
|
||||
*/
|
||||
private function compileCode($code): string {
|
||||
$code = $this->compileBlock($code);
|
||||
$code = $this->compileYield($code);
|
||||
$code = $this->compileEchos($code);
|
||||
$code = $this->compilePHP($code);
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt eingebundene oder erweiterte Templates ein.
|
||||
*
|
||||
* @param string $file Name der Template-Datei.
|
||||
* @return string Template-Code mit eingebundenen Dateien.
|
||||
* @throws Exception Wenn die Datei nicht gefunden wird.
|
||||
*/
|
||||
private function includeFiles($file): string {
|
||||
$filePath = $this->viewsPath . preg_replace("/\.twig$/", "", $file) . ".twig";
|
||||
|
||||
if(!file_exists($filePath)) {
|
||||
throw new Exception("View file not found: {$filePath}");
|
||||
}
|
||||
|
||||
$code = file_get_contents($filePath);
|
||||
preg_match_all('/{% ?(extends|include) ?\'?(.*?)\'? ?%}/i', $code, $matches, PREG_SET_ORDER);
|
||||
|
||||
foreach($matches as $match) {
|
||||
$includedCode = $this->includeFiles($match[2]);
|
||||
$code = str_replace($match[0], $includedCode, $code);
|
||||
}
|
||||
return preg_replace('/{% ?(extends|include) ?\'?(.*?)\'? ?%}/i', '', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wandelt {% ... %} in PHP-Code um.
|
||||
*
|
||||
* @param string $code Der Template-Code.
|
||||
* @return string PHP-Code.
|
||||
*/
|
||||
private function compilePHP($code): string {
|
||||
return preg_replace('~\{%\s*(.+?)\s*%}~is', '<?php $1 ?>', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wandelt {{ ... }} in PHP-Echo-Ausgaben um.
|
||||
*
|
||||
* @param string $code Der Template-Code.
|
||||
* @return string PHP-Code mit Echo-Ausgaben.
|
||||
*/
|
||||
private function compileEchos($code): string {
|
||||
$code = preg_replace('~\{\{\{\s*(.+?)\s*\}\}\}~is', '<?=htmlspecialchars($1, ENT_QUOTES, "UTF-8")?>', $code);
|
||||
return preg_replace('~\{\{\s*(.+?)\s*\}\}~is', '<?=$1?>', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sammelt und entfernt Block-Definitionen aus dem Template.
|
||||
*
|
||||
* @param string $code Der Template-Code.
|
||||
* @return string Template-Code ohne Block-Definitionen.
|
||||
*/
|
||||
private function compileBlock($code): string {
|
||||
preg_match_all('/{% ?block ?(.*?) ?%}(.*?){% ?endblock ?%}/is', $code, $matches, PREG_SET_ORDER);
|
||||
foreach($matches as $match) {
|
||||
if(!isset($this->blocks[$match[1]])) {
|
||||
$this->blocks[$match[1]] = '';
|
||||
}
|
||||
$this->blocks[$match[1]] = str_replace('@parent', $this->blocks[$match[1]], $match[2]);
|
||||
$code = str_replace($match[0], '', $code);
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ersetzt yield-Platzhalter durch Block-Inhalte.
|
||||
*
|
||||
* @param string $code Der Template-Code.
|
||||
* @return string Template-Code mit ersetzten Yields.
|
||||
*/
|
||||
private function compileYield($code): string {
|
||||
foreach($this->blocks as $block => $value) {
|
||||
$code = preg_replace('/{% ?yield ?' . $block . ' ?%}/', $value, $code);
|
||||
}
|
||||
return preg_replace('/{% ?yield ?(.*?) ?%}/i', '', $code);
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
<?php
|
||||
namespace Blog\Template;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Twig {
|
||||
private array $blocks = [];
|
||||
private string $viewsPath;
|
||||
|
||||
public function __construct($viewsPath) {
|
||||
$this->viewsPath = rtrim($viewsPath, '/') . '/';
|
||||
}
|
||||
|
||||
public function render($file, $data = []): string {
|
||||
try {
|
||||
$code = $this->includeFiles($file);
|
||||
$code = $this->compileCode($code);
|
||||
extract($data, EXTR_SKIP);
|
||||
|
||||
ob_start();
|
||||
eval('?>' . $code);
|
||||
return ob_get_clean();
|
||||
} catch(Exception $e) {
|
||||
throw new Expection("Error rendering template: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function compileCode($code): string {
|
||||
$code = $this->compileBlock($code);
|
||||
$code = $this->compileYield($code);
|
||||
$code = $this->compileEchos($code);
|
||||
$code = $this->compilePHP($code);
|
||||
return $code;
|
||||
}
|
||||
|
||||
private function includeFiles($file): string {
|
||||
$filePath = $this->viewsPath . preg_replace("/\.twig$/", "", $file) . ".twig";
|
||||
|
||||
if(!file_exists($filePath)) {
|
||||
throw new Exception("View file not found: {$filePath}");
|
||||
}
|
||||
|
||||
$code = file_get_contents($filePath);
|
||||
preg_match_all('/{% ?(extends|include) ?\'?(.*?)\'? ?%}/i', $code, $matches, PREG_SET_ORDER);
|
||||
|
||||
foreach($matches as $match) {
|
||||
$includedCode = $this->includeFiles($match[2]);
|
||||
$code = str_replace($match[0], $includedCode, $code);
|
||||
}
|
||||
return preg_replace('/{% ?(extends|include) ?\'?(.*?)\'? ?%}/i', '', $code);
|
||||
}
|
||||
|
||||
private function compilePHP($code): string {
|
||||
return preg_replace('~\{%\s*(.+?)\s*%}~is', '<?php $1 ?>', $code);
|
||||
}
|
||||
|
||||
private function compileEchos($code): string {
|
||||
$code = preg_replace('~\{\{\s*(.+?)\s*\}\}~is', '<?=$1?>', $code);
|
||||
return preg_replace('~\{\{\{\s*(.+?)\s*\}\}\}~is', '<?=htmlspecialchars($1, ENT_QUOTES, "UTF-8")?>', $code);
|
||||
}
|
||||
|
||||
private function compileEscapedEchos($code):string {
|
||||
return preg_replace('~\{{{\s*(.+?)\s*}}}~is', '<?=htmlentities($1, ENT_QUOTES, "UTF-8")?>', $code);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
*/
|
||||
private function compileBlock($code): string {
|
||||
preg_match_all('/{% ?block ?(.*?) ?%}(.*?){% ?endblock ?%}/is', $code, $matches, PREG_SET_ORDER);
|
||||
foreach($matches as $match) {
|
||||
if(!isset($this->blocks[$match[1]])) {
|
||||
$this->blocks[$match[1]] = '';
|
||||
}
|
||||
$this->blocks[$match[1]] = str_replace('@parent', $this->blocks[$match[1]], $match[2]);
|
||||
$code = str_replace($match[0], '', $code);
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
private function compileYield($code): string {
|
||||
foreach($this->blocks as $block => $value) {
|
||||
$code = preg_replace('/{% ?yield ?' . $block . ' ?%}/', $value, $code);
|
||||
}
|
||||
return preg_replace('/{% ?yield ?(.*?) ?%}/i', '', $code);
|
||||
}
|
||||
}
|
@ -1,16 +1,36 @@
|
||||
<?php
|
||||
namespace Blog\Utils;
|
||||
|
||||
use Blog\Model\userModel;
|
||||
use Blog\Model\UserModel;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Hilfsklasse für Authentifizierung und Session-Management.
|
||||
*/
|
||||
class AuthHelper {
|
||||
/**
|
||||
* @var UserModel|null
|
||||
*/
|
||||
private static ?UserModel $userModel = null;
|
||||
|
||||
/**
|
||||
* Initialisiert den AuthHelper mit einem UserModel.
|
||||
*
|
||||
* @param UserModel $userModel
|
||||
* @return void
|
||||
*/
|
||||
public static function initialize(UserModel $userModel): void {
|
||||
self::$userModel = $userModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Versucht, einen Benutzer mit Benutzername und Passwort einzuloggen.
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return bool True bei Erfolg, sonst false.
|
||||
* @throws Exception Wenn das UserModel nicht initialisiert wurde.
|
||||
*/
|
||||
public static function login(string $username, string $password): bool {
|
||||
if(!self::$userModel)
|
||||
throw new Exception("AuthHelper was not initialized. Please pass UserModel.");
|
||||
@ -28,15 +48,30 @@ class AuthHelper {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loggt den aktuellen Benutzer aus und zerstört die Session.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function logout(): void {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob ein Benutzer eingeloggt ist.
|
||||
*
|
||||
* @return bool True, wenn ein Benutzer eingeloggt ist, sonst false.
|
||||
*/
|
||||
public static function isLoggedIn(): bool {
|
||||
return isset($_SESSION['user']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die aktuellen Benutzerdaten aus der Session zurück.
|
||||
*
|
||||
* @return array|null Benutzerdaten oder null, falls nicht eingeloggt.
|
||||
*/
|
||||
public static function getCurrentUser(): ?array {
|
||||
return $_SESSION['user'] ?? null;
|
||||
}
|
Reference in New Issue
Block a user