Files
MVC/init.php
T

1879 lines
61 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Ultimate MVC Project Bootstrapper (Fixed Version with Header & Auth)
* Erstellt ein voll funktionsfähiges Projekt inkl. Dev-Server, Header, DarkMode & Auth-System.
*/
if (php_sapi_name() !== 'cli') {
die("Dieses Script muss über die CLI ausgeführt werden.\n");
}
$projectName = $argv[1] ?? null;
if (!$projectName) {
echo "Bitte gib einen Projektnamen an: ";
$handle = fopen("php://stdin", "r");
$projectName = trim(fgets($handle));
fclose($handle);
}
if (!$projectName) die("Fehler: Kein Name angegeben.\n");
$baseDir = getcwd() . DIRECTORY_SEPARATOR . $projectName;
// 1. Verzeichnisstruktur erstellen
$dirs = [
'',
'WEBROOT',
'WEBROOT/api',
'WEBROOT/api/v1',
'WEBROOT/commands',
'WEBROOT/static',
'WEBROOT/src',
'WEBROOT/src/config',
'WEBROOT/src/database',
'WEBROOT/src/utils',
'WEBROOT/src/MVC',
'WEBROOT/src/MVC/Core',
'WEBROOT/src/MVC/Classes',
'WEBROOT/src/MVC/Controller',
'WEBROOT/src/MVC/Model',
'WEBROOT/src/MVC/View',
'WEBROOT/src/MVC/View/admin',
'WEBROOT/src/MVC/Layout',
'WEBROOT/src/MVC/Layout/Templates',
'WEBROOT/logs',
];
foreach ($dirs as $dir) {
$path = $baseDir . ($dir ? DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $dir) : '');
if (!is_dir($path)) {
mkdir($path, 0755, true);
echo "📁 Ordner erstellt: $dir\n";
}
}
$files = [];
// --- DEV SERVER: server.php ---
$files['server.php'] = <<<'PHP'
<?php
$port = 8000;
$host = 'localhost';
$root = __DIR__ . '/WEBROOT';
echo "\n🚀 Starte MVC Dev-Server auf http://$host:$port\n";
echo "Drücke STRG+C zum Beenden.\n\n";
passthru("php -S $host:$port -t \"$root\"");
PHP;
$files['.htaccess'] = <<<'HTACCESS'
RewriteEngine On
RewriteRule ^(Commands|config_backups|controller|modules)($|/.*) - [F,L,NC]
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
# -----------------------------------
# .php entfernen
# -----------------------------------
# browser requestet datei.php -> redirect zu /datei
RewriteCond %{THE_REQUEST} \s/+(.+?)\.php[\s?] [NC]
RewriteRule ^ /%1 [R=301,L]
# /datei -> datei.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^(.+)$ $1.php [L]
HTACCESS;
$files[$projectName . '.code-workspace'] = <<<VSCODE
{
"folders": [
{
"path": "."
}
],
"settings": {
"workbench.colorTheme": "Dark Modern",
"workbench.colorCustomizations": {
"activityBar.background": "#162df7ec",
"activityBar.foreground": "#000000",
"breadcrumb.foreground": "#FFFFFF",
},
"files.exclude": {
"*.git*": true,
"WEBROOT/config_backups": true,
"WEBROOT/static": true,
"WEBROOT/templates": true,
},
"editor.renderWhitespace": "all",
"editor.smoothScrolling": true,
"editor.fontSize": 14,
"editor.formatOnSave": true,
"editor.minimap.enabled": true,
"editor.minimap.scale": 2,
"editor.minimap.side": "right",
"editor.minimap.maxColumn": 100,
"editor.minimap.renderCharacters": true,
"editor.minimap.showSlider": "always",
"files.autoSave": "off",
"files.autoSaveDelay": 3000,
"window.title": "MVC",
"workbench.editor.wrapTabs": true,
},
"extensions": {
"recommendations": [
"oderwat.indent-rainbow",
"pranaygp.vscode-css-peek",
"wayou.vscode-todo-highlight",
"mrmlnc.vscode-mustache",
"fabiospampinato.vscode-highlight",
"junstyle.php-cs-fixer",
"mtea.gettext-editor",
"neilbrayfield.php-docblocker",
"bmewburn.vscode-intelephense-client",
"mohamed-ali.html-css-class-completion",
"rifi2k.format-html-in-php",
"mrorz.language-gettext",
"voldemortensen.rainbow-tags",
]
}
}
VSCODE;
$files['WEBROOT/src/utils/databaseMethods.php'] = <<<'PHP'
<?php
function DatabaseCreateTable($table, $columnsDefinition) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "CREATE TABLE IF NOT EXISTS `$table` ($columnsDefinition)";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute()) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseShowTables() {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$tablesResult = $conn->query("SHOW TABLES");
if (!$tablesResult) {
die("Fehler beim Laden der Tabellen: " . $conn->error);
}
$tables = [];
while ($tableRow = $tablesResult->fetch_array()) {
$tables[] = $tableRow[0];
}
return $tables;
}
function DatabaseShowTableStructure($table) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$stmt = $conn->prepare("SHOW COLUMNS FROM `$table`");
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute()) {
die("Fehler beim Execute: " . $stmt->error);
}
$result = $stmt->get_result();
$columns = [];
while ($column = $result->fetch_assoc()) {
$columns[] = [
"Field" => $column["Field"],
"Type" => $column["Type"],
"Null" => $column["Null"],
"Key" => $column["Key"],
"Default" => $column["Default"],
"Extra" => $column["Extra"]
];
}
return $columns;
}
function DatabaseShow($table, $columns = "*", $where = "", ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "SELECT $columns FROM `$table`";
if (!empty($where)) {
$sql .= " WHERE $where";
}
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
$result = $stmt->get_result();
return $result->fetch_all(MYSQLI_ASSOC);
}
function DatabaseInsert($table, $columns, ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$placeholders = rtrim(str_repeat("?,", count($values)), ",");
$sql = "INSERT INTO `$table` ($columns) VALUES ($placeholders)";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseDelete($table, $where, ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "DELETE FROM `$table` WHERE $where";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function DatabaseUpdate($table, $set, $where, ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$sql = "UPDATE `$table` SET $set WHERE $where";
$stmt = $conn->prepare($sql);
if (!$stmt) {
die("Fehler beim Prepare: " . $conn->error);
}
if (!$stmt->execute($values)) {
die("Fehler beim Execute: " . $stmt->error);
}
return true;
}
function getFKValue($conn, $table, $key, $value, $id, &$cache) {
if ($id === null) return null;
$cacheKey = $table . "_" . $id;
if (isset($cache[$cacheKey])) {
return $cache[$cacheKey];
}
$stmt = $conn->prepare("SELECT `$value` FROM `$table` WHERE `$key` = ? LIMIT 1");
if (!$stmt) {
die("FK Prepare Fehler: " . $conn->error);
}
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result()->fetch_assoc();
$valueResult = $result[$value] ?? null;
$cache[$cacheKey] = $valueResult;
return $valueResult;
}
function DatabaseShowWithFK($table, $fkMap = [], $columns = "*", $where = "", ...$values) {
include_once(__DIR__ . '/../database/databaseConnect.php');
$conn = db_connect();
$cache = [];
$select = is_array($columns) ? implode(", ", $columns) : $columns;
$sql = "SELECT $select FROM `$table`";
if (!empty($where)) {
$sql .= " WHERE $where";
}
$stmt = $conn->prepare($sql);
if (!$stmt) die("Fehler beim Prepare: " . $conn->error);
if (!empty($values)) {
$stmt->execute($values);
} else {
$stmt->execute();
}
$result = $stmt->get_result();
if (!$result) die("Fehler beim get_result: " . $conn->error);
$rows = $result->fetch_all(MYSQLI_ASSOC);
foreach ($rows as &$row) {
foreach ($fkMap as $fkColumn => $fkInfo) {
if (!isset($row[$fkColumn]) || $row[$fkColumn] === null) {
continue;
}
$row[$fkColumn] = getFKValue(
$conn,
$fkInfo['table'],
$fkInfo['key'],
$fkInfo['value'],
$row[$fkColumn],
$cache
);
}
}
return [
"rows" => $rows
];
}
PHP;
// --- CONFIG: env.ini ---
$files['WEBROOT/src/config/env.ini'] = <<<INI
[database]
APP_ENV = "dev"
DB_ROOT_USERNAME = "root"
DB_ROOT_PASSWORD = ""
DB_DEV_HOST = "localhost"
DB_DEV_USERNAME = "worker"
DB_DEV_PASSWORD = "worker_password"
DB_DEV_DATABASE = "project_db"
DB_LIVE_HOST = ""
DB_LIVE_USERNAME = ""
DB_LIVE_PASSWORD = ""
DB_LIVE_DATABASE = ""
[mail]
MAIL_FROM = "bruno.eckhardt07@gmail.com"
MAIL_TO = "bruno.eckhardt07@gmail.com"
INI;
// --- DB: databaseConnect.php ---
$files['WEBROOT/src/database/databaseConnect.php'] = <<<'PHP'
<?php
function db_connect() {
$config = parse_ini_file(__DIR__ . '/../config/env.ini');
$env = strtoupper($config['APP_ENV']);
try {
$conn = new mysqli(
$config["DB_{$env}_HOST"],
$config["DB_{$env}_USERNAME"],
$config["DB_{$env}_PASSWORD"],
$config["DB_{$env}_DATABASE"]
);
$conn->set_charset("utf8mb4");
return $conn;
} catch (Exception $e) {
return null;
}
}
function db_connect_root() {
$config = parse_ini_file(__DIR__ . '/../config/env.ini');
$env = strtoupper($config['APP_ENV']);
try {
$conn = new mysqli(
$config["DB_{$env}_HOST"],
$config["DB_ROOT_USERNAME"],
$config["DB_ROOT_PASSWORD"]
);
$conn->set_charset("utf8mb4");
return $conn;
} catch (Exception $e) {
return null;
}
}
PHP;
// -- CLASSES --
$files['WEBROOT/src/MVC/Classes/Api.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Api {
public static function jsonResponse($data, int $statusCode = 200) {
header('Content-Type: application/json');
http_response_code($statusCode);
echo json_encode(
[
'status' => $statusCode === 200 ? 'success' : 'error',
'data' => $data,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
exit;
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Client.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Client {
public static function ip() {
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ipList = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return trim($ipList[0]);
} else if (!empty($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR'];
} else {
return 'UNKNOWN';
}
}
public static function port() {
return $_SERVER['REMOTE_PORT'] ?? 'UNKNOWN';
}
public static function country() {
$response = file_get_contents('http://ip-api.com/json/{' . self::ip() . '}?fields=country,countryCode');
$data = json_decode($response);
return $data->country ?? 'UNKNOWN';
}
public static function userAgent() {
return $_SERVER['HTTP_USER_AGENT'] ?? 'UNKNOWN';
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Cookie.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Cookie {
public function set(string $CookieName, int $CookieValue, int $CookieTime) {
setcookie($CookieName, $CookieValue, time()+($CookieTime*60));
}
public function read(string $CookieName) {
if (isset($_COOKIE[$CookieName])) {
return $_COOKIE[$CookieName];
}
return 'Cookie not set';
}
public function Delete(string $CookieName) {
setcookie($CookieName, "", time()-(3600*24));
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Request.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Request {
public static function method() {
return $_SERVER['REQUEST_METHOD'] ?? 'UNKNOWN';
}
public static function uri() {
return $_SERVER['REQUEST_URI'] ?? 'UNKNOWN';
}
public static function headers() {
return getallheaders();
}
public static function body() {
return file_get_contents('php://input');
}
public static function queryParams() {
return $_GET;
}
public static function time() {
return $_SERVER['REQUEST_TIME'] ?? 'UNKNOWN';
}
public static function referrer() {
return $_SERVER['HTTP_REFERER'] ?? 'UNKNOWN';
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Logger.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Logger {
private static function getLogDir() { return dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'logs'; }
public static function log($message, $level = 'INFO', $file = 'system.log') {
$logDir = self::getLogDir();
if (!is_dir($logDir) && !mkdir($logDir, 0777, true)) return;
$timestamp = date('Y-m-d H:i:s');
file_put_contents($logDir . DIRECTORY_SEPARATOR . $file, "[$timestamp] [$level] $message\n", FILE_APPEND | LOCK_EX);
}
public static function logException(\Throwable $e) {
$msg = sprintf("Uncaught Exception: '%s' with message '%s' in %s:%d\nStack trace:\n%s", get_class($e), $e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString());
self::log($msg, 'CRITICAL', 'error.log');
}
public static function logError($errno, $errstr, $errfile, $errline) {
$levels = [E_ERROR => 'ERROR', E_WARNING => 'WARNING', E_PARSE => 'PARSE_ERROR', E_NOTICE => 'NOTICE', E_USER_ERROR => 'USER_ERROR', E_USER_WARNING => 'USER_WARNING', E_USER_NOTICE => 'USER_NOTICE', E_DEPRECATED => 'DEPRECATED', E_USER_DEPRECATED => 'USER_DEPRECATED'];
self::log("$errstr in $errfile on line $errline", $levels[$errno] ?? 'UNKNOWN', 'error.log');
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Mailer.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
use MVC\Classes\Client;
use MVC\Classes\Request;
use MVC\Model\Mailing;
class Mailer {
private static function getConfig() {
return parse_ini_file(__DIR__ . '/../../config/env.ini');
}
public static function send($subject, $message, $url = null, bool $log = true) {
$config = self::getConfig();
$from = $config['MAIL_FROM'] ?? "bruno.eckhardt07@gmail.com";
$to = $config['MAIL_TO'] ?? "bruno.eckhardt07@gmail.com";
$headers = "From: " . $from . "\r\n";
$headers .= "Reply-To: " . $from . "\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
$success = mail($to, $subject, $message, $headers);
if ($log) {
self::log($subject, $message, $url, false);
}
return $success;
}
public static function error($title, $error, bool $log = true) {
$subject = "ERROR: $title";
$config = self::getConfig();
$from = $config['MAIL_FROM'] ?? "bruno.eckhardt07@gmail.com";
$to = $config['MAIL_TO'] ?? "bruno.eckhardt07@gmail.com";
$headers = "From: " . $from . "\r\n" . "Reply-To: " . $from . "\r\n" . "X-Mailer: PHP/" . phpversion();
$success = mail($to, $subject, $error, $headers);
if ($log) self::log($subject, $error, null, true);
return $success;
}
private static function log($subject, $message, $url, bool $isError) {
$url = $url ?? Request::uri();
$requestTime = Request::time();
$date = ($requestTime !== 'UNKNOWN') ? date('Y-m-d H:i:s', $requestTime) : date('Y-m-d H:i:s');
$ip = Client::ip();
try {
if (class_exists('MVC\Model\Mailing')) {
Mailing::log($subject, $message, $url, $ip, $date);
return;
}
} catch (\Exception $e) {}
self::logToFile($subject, $message, $url, $ip, $date, $isError);
}
private static function logToFile($subject, $message, $url, $ip, $date, bool $isError) {
$logDir = __DIR__ . '/../../../logs';
if (!is_dir($logDir)) mkdir($logDir, 0755, true);
$fileName = $isError ? 'mail_errors.log' : 'mails.log';
$logFile = $logDir . '/' . $fileName;
$logEntry = "[$date] IP: $ip | URL: $url\nSubject: $subject\nMessage: $message\n" . str_repeat("-", 50) . "\n";
file_put_contents($logFile, $logEntry, FILE_APPEND);
}
}
PHP;
$files['WEBROOT/src/MVC/Classes/Server.php'] = <<<'PHP'
<?php
namespace MVC\Classes;
class Server {
public static function documentRoot() {
return $_SERVER['DOCUMENT_ROOT'] ?? 'UNKNOWN';
}
public static function currentDir() {
return __DIR__;
}
public static function currentScript() {
return $_SERVER['SCRIPT_NAME'] ?? 'UNKNOWN';
}
public static function name() {
return $_SERVER['SERVER_NAME'] ?? 'UNKNOWN';
}
public static function port() {
return $_SERVER['SERVER_PORT'] ?? 'UNKNOWN';
}
}
PHP;
// --- CORE ---
$files['WEBROOT/src/MVC/Core/Controller.php'] = <<<'PHP'
<?php
namespace MVC\Core;
class Controller {
protected $view;
public function __construct() {
$this->view = new View(__DIR__ . '/../View');
}
protected function render($template, $data = []) {
foreach ($data as $key => $value) {
$this->view->assign($key, $value);
}
return $this->view->render($template);
}
protected function json($data, $status = 200) {
header('Content-Type: application/json');
http_response_code($status);
echo json_encode($data);
exit;
}
}
PHP;
$files['WEBROOT/src/MVC/Core/Model.php'] = <<<'PHP'
<?php
namespace MVC\Core;
require_once __DIR__ . '/../../database/databaseConnect.php';
require_once __DIR__ . '/../../utils/databaseMethods.php';
abstract class Model {
protected static $table;
protected static $conn;
protected static function getConnection() {
if (!self::$conn) {
self::$conn = \db_connect();
}
return self::$conn;
}
public static function all() {
return \DatabaseShow(static::$table, "*");
}
public static function find($id, $column = 'ID') {
$result = \DatabaseShow(static::$table, "*", "$column = ?", $id);
return $result[0] ?? null;
}
public static function where($where, ...$params) {
return \DatabaseShow(static::$table, "*", $where, ...$params);
}
public static function insert($data) {
$columns = implode(", ", array_keys($data));
$values = array_values($data);
return \DatabaseInsert(static::$table, $columns, ...$values);
}
public static function update($data, $where, ...$whereParams) {
$sets = [];
$values = [];
foreach ($data as $column => $value) {
$sets[] = "$column = ?";
$values[] = $value;
}
$setString = implode(", ", $sets);
return \DatabaseUpdate(static::$table, $setString, $where, ...array_merge($values, $whereParams));
}
public static function delete($where, ...$params) {
return \DatabaseDelete(static::$table, $where, ...$params);
}
public static function query($sql, ...$params) {
$conn = self::getConnection();
if (empty($params)) {
return $conn->query($sql);
}
$stmt = $conn->prepare($sql);
$types = str_repeat('s', count($params));
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt->get_result();
}
}
PHP;
$files['WEBROOT/src/MVC/Core/View.php'] = <<<'PHP'
<?php
namespace MVC\Core;
class View {
protected $data = [];
protected $viewPath;
public function __construct($viewPath) {
$this->viewPath = $viewPath;
}
public function assign($key, $value) {
$this->data[$key] = $value;
}
public function render($template) {
extract($this->data);
$file = $this->viewPath . '/' . $template . '.php';
if (file_exists($file)) {
ob_start();
include $file;
return ob_get_clean();
}
return "View $template not found at $file";
}
}
PHP;
$files['WEBROOT/src/MVC/Core/Router.php'] = <<<'PHP'
<?php
namespace MVC\Core;
class Router {
protected $routes = [];
protected $middlewares = [];
public function add($service, $controller, $method, $middlewares = []) {
$this->routes[$service] = [
'controller' => $controller,
'method' => $method,
'middlewares' => $middlewares
];
}
public function dispatch($service) {
if (!isset($this->routes[$service])) {
throw new \Exception("Service '$service' not found.");
}
$route = $this->routes[$service];
// Run Middlewares
foreach ($route['middlewares'] as $middleware) {
$this->runMiddleware($middleware);
}
$controllerClass = $route['controller'];
$method = $route['method'];
if (!class_exists($controllerClass)) {
throw new \Exception("Controller class '$controllerClass' not found.");
}
$controller = new $controllerClass();
if (!method_exists($controller, $method)) {
throw new \Exception("Method '$method' not found in controller '$controllerClass'.");
}
return $controller->$method();
}
protected function runMiddleware($middleware) {
if ($middleware === 'admin') {
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
die("Access Denied: Admin rights required.");
}
}
if ($middleware === 'auth') {
if (empty($_SESSION['user_logged_in'])) {
header('Location: ?service=login');
exit;
}
}
}
}
PHP;
$files['WEBROOT/src/MVC/Layout/Layout.php'] = <<<'PHP'
<?php
namespace MVC\Layout;
class Layout {
protected $blocks = [];
protected $template;
public function __construct($template = 'main') {
$this->template = $template;
}
public function setBlock($name, $content) {
$this->blocks[$name] = $content;
}
public function getBlock($name) {
return $this->blocks[$name] ?? '';
}
public function render() {
$layoutFile = __DIR__ . '/Templates/' . $this->template . '.php';
if (file_exists($layoutFile)) {
ob_start();
include $layoutFile;
return ob_get_clean();
}
return "Layout template nicht gefunden.";
}
}
PHP;
$files['WEBROOT/src/MVC/autoloader.php'] = <<<'PHP'
<?php
spl_autoload_register(function ($class) {
$prefix = 'MVC\\';
$base_dir = __DIR__ . '/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
}
});
PHP;
// --- LAYOUT TEMPLATE ---
$files['WEBROOT/src/MVC/Layout/Templates/main.php'] = <<<'HTML'
<!DOCTYPE html>
<html lang="de" class="<?= ($_SESSION['theme'] ?? 'light') === 'dark' ? 'dark-mode' : '' ?>">
<head>
<meta charset="UTF-8">
<title><?= $this->getBlock('title') ?></title>
<style>
:root { --bg: #ffffff; --text: #333; --sidebar: #f4f4f4; --header: #007bff; --accent: #007bff; --border: #ddd; }
.dark-mode { --bg: #1a1a1a; --text: #f4f4f4; --sidebar: #252525; --header: #111; --accent: #3498db; --border: #333; }
body { font-family: 'Segoe UI', sans-serif; margin: 0; background: var(--bg); color: var(--text); transition: 0.3s; height: 100vh; overflow: hidden; display: flex; flex-direction: column; }
.main-header { height: 60px; background: var(--header); color: white; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); z-index: 10; }
.main-container { flex: 1; display: flex; overflow: hidden; }
.sidebar { width: 260px; background: var(--sidebar); padding: 20px; border-right: 1px solid var(--border); overflow-y: auto; }
.content { flex: 1; padding: 40px; overflow-y: auto; }
.btn-toggle { padding: 8px 15px; border: 1px solid rgba(255,255,255,0.3); border-radius: 5px; cursor: pointer; background: transparent; color: white; font-size: 0.9rem; }
.btn-toggle:hover { background: rgba(255,255,255,0.1); }
nav a { display: block; padding: 12px; text-decoration: none; color: inherit; border-radius: 5px; margin-bottom: 5px; }
nav a:hover { background: rgba(0,0,0,0.05); }
nav a.active { background: rgba(0,0,0,0.1); }
.logo { font-size: 1.4rem; font-weight: bold; }
</style>
</head>
<body>
<header class="main-header">
<div class="logo"><?= $this->getBlock('header_title') ?></div>
<div class="header-actions">
<?= $this->getBlock('header_actions') ?>
</div>
</header>
<div class="main-container">
<div class="sidebar">
<?= $this->getBlock('sidebar') ?>
</div>
<div class="content">
<?= $this->getBlock('content') ?>
</div>
</div>
<script>
function toggleTheme() { fetch('?service=toggle_theme').then(() => location.reload()); }
</script>
</body>
</html>
HTML;
// --- CONTROLLERS ---
$files['WEBROOT/src/MVC/Controller/AdminController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Mailer;
use MVC\Model\Users;
class AdminController extends Controller {
public function mail_service() {
$msg = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (Mailer::send($_POST['subject'] ?? '', $_POST['body'] ?? '')) $msg = ["type" => "success", "text" => "Mail gesendet!"];
else $msg = ["type" => "error", "text" => "Fehler!"];
}
return $this->render('admin/mail', ['message' => $msg]);
}
public function user_service() {
$msg = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = $_POST['username'] ?? '';
$pass = $_POST['password'] ?? '';
if ($user && $pass) {
if (Users::insert(['USERNAME' => $user, 'PASSWORD' => password_hash($pass, PASSWORD_DEFAULT), 'EMAIL' => $_POST['email'] ?? '', 'ROLE' => $_POST['role'] ?? 'user'])) $msg = ["type" => "success", "text" => "User $user erstellt!"];
else $msg = ["type" => "error", "text" => "Fehler (DB)!"];
} else $msg = ["type" => "error", "text" => "Name/Passwort fehlen!"];
}
return $this->render('admin/users', ['message' => $msg]);
}
}
PHP;
$files['WEBROOT/src/MVC/View/admin/mail.php'] = <<<'HTML'
<h2>📧 E-Mail Service</h2>
<?php if (isset($message)): ?>
<div style="padding:15px;margin-bottom:20px;border-radius:5px;background:<?= $message['type']==='success'?'rgba(46,204,113,0.2)':'rgba(231,76,60,0.2)'?>;"><?= $message['text']?></div>
<?php endif; ?>
<form method="POST" style="background:var(--sidebar);padding:20px;border-radius:8px;">
<input type="text" name="subject" placeholder="Betreff" required style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<textarea name="body" placeholder="Nachricht" required style="width:100%;height:150px;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);"></textarea>
<button type="submit" style="background:var(--accent);color:white;padding:10px 20px;border:none;border-radius:4px;cursor:pointer;">Senden</button>
</form>
HTML;
$files['WEBROOT/src/MVC/View/admin/users.php'] = <<<'HTML'
<h2>👤 Benutzerverwaltung</h2>
<?php if (isset($message)): ?>
<div style="padding:15px;margin-bottom:20px;border-radius:5px;background:<?= $message['type']==='success'?'rgba(46,204,113,0.2)':'rgba(231,76,60,0.2)'?>;"><?= $message['text']?></div>
<?php endif; ?>
<form method="POST" style="background:var(--sidebar);padding:20px;border-radius:8px;">
<input type="text" name="username" placeholder="Benutzername" required style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<input type="password" name="password" placeholder="Passwort" required style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<input type="email" name="email" placeholder="E-Mail" style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<select name="role" style="width:100%;margin-bottom:10px;padding:10px;background:var(--bg);color:var(--text);border:1px solid var(--border);">
<option value="user">User</option><option value="admin">Admin</option>
</select>
<button type="submit" style="background:var(--accent);color:white;padding:10px 20px;border:none;border-radius:4px;cursor:pointer;">Erstellen</button>
</form>
HTML;
$files['WEBROOT/src/MVC/Controller/ApiController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Api;
class ApiController extends Controller {
public static function get(){
return Api::jsonResponse([
'data' => 'DummyData',
]);
}
}
PHP;
$files['WEBROOT/src/MVC/Controller/HomeController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Client;
use MVC\Classes\Cookie;
use MVC\Classes\Request;
use MVC\Classes\Server;
class HomeController extends Controller {
public function index() {
return $this->render('home');
}
public function toggle_theme() {
$_SESSION['theme'] = ($_SESSION['theme'] ?? 'light') === 'dark' ? 'light' : 'dark';
return $this->json(
[
'success' => true
]
);
}
}
PHP;
$files['WEBROOT/src/MVC/Controller/AuthController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Model\Users;
class AuthController extends Controller {
public function login() {
if (isset($_SESSION['user_logged_in'])) {
header('Location: ?service=home');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$user = Users::getByUsername($username);
if ($user && password_verify($password, $user['PASSWORD'])) {
$_SESSION['user_logged_in'] = true;
$_SESSION['username'] = $user['USERNAME'];
$_SESSION['role'] = $user['ROLE'];
$_SESSION['is_admin'] = ($user['ROLE'] === 'admin');
header('Location: ?service=home');
exit;
} else {
return $this->render('login', ['error' => 'Ungültige Anmeldedaten']);
}
}
return $this->render('login');
}
public function logout() {
session_destroy();
header('Location: ?service=login');
exit;
}
}
PHP;
$files['WEBROOT/src/MVC/Controller/DocsController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
class DocsController extends Controller {
public function index() {
return $this->render('docs', [
'title' => '📚 System-Dokumentation'
]);
}
}
PHP;
$files['WEBROOT/src/MVC/Controller/TestController.php'] = <<<'PHP'
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Client;
use MVC\Classes\Cookie;
use MVC\Classes\Request;
use MVC\Classes\Server;
class TestController extends Controller {
public function service1() {
return $this->render(
'test1',
[
'msg' => 'Service 1 ist bereit!'
]
);
}
public function service2() {
return $this->render(
'test2',
[
'data' =>
[
'Layout' => 'Header + Sidebar',
'Theme' => 'Auto-Toggle'
]
]
);
}
}
PHP;
// --- API ---
$files['WEBROOT/api/v1/get.php'] = <<<'PHP'
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../../src/MVC/autoloader.php';
use MVC\Controller\ApiController;
$api = (new ApiController());
try {
echo ($api->get());
} catch (\Exception $e) {
echo ($e->getMessage());
}
PHP;
// --- VIEWS ---
$files['WEBROOT/src/MVC/View/home.php'] = "<h1>Dashboard</h1><p>Willkommen in deinem neuen MVC-System mit integriertem Header.</p>";
$files['WEBROOT/src/MVC/View/test1.php'] = "<h1>Service 1</h1><p><?= \$msg ?></p>";
$files['WEBROOT/src/MVC/View/test2.php'] = "<h1>System Check</h1><ul><?php foreach(\$data as \$k => \$v): ?><li><b><?= \$k ?>:</b> <?= \$v ?></li><?php endforeach; ?></ul>";
$files['WEBROOT/src/MVC/View/docs.php'] = <<<'HTML'
<div class="docs-container">
<h1>🏛️ Framework Architektur & Dokumentation</h1>
<div class="toc">
<h3>Inhalt</h3>
<ul>
<li><a href="#intro">1. Einführung</a></li>
<li><a href="#files">2. Datei-Referenz (Deep Dive)</a></li>
<li><a href="#api">3. API-System</a></li>
<li><a href="#database">4. Datenbank & Models</a></li>
<li><a href="#classes">5. Helper Klassen</a></li>
<li><a href="#generators">6. CLI-Generatoren</a></li>
<li><a href="#mailing">7. Mailing-System</a></li>
<li><a href="#logging">8. Error-Logging</a></li>
<li><a href="#private-services">9. Private Services</a></li>
<li><a href="#best-practices">10. Best Practices</a></li>
</ul>
</div>
<section id="intro">
<h2>1. Einführung</h2>
<p>Dieses Framework basiert auf dem <strong>Model-View-Controller (MVC)</strong> Entwurfsmuster und nutzt einen <strong>Front Controller</strong> (index.php). Alle Anfragen werden zentral verarbeitet, was globale Middleware (wie Authentifizierung) und ein einheitliches Layout ermöglicht.</p>
</section>
<section id="files">
<h2>2. Datei-Referenz (Deep Dive)</h2>
<h3>Framework Core</h3>
<ul>
<li><strong>Router.php:</strong> Verwaltet die Routen. Unterstützt Middlewares. Das Dispatching erfolgt über den <code>service</code> Parameter in der URL.</li>
<li><strong>Controller.php:</strong> Basisklasse für alle Controller. Bietet <code>render()</code> für Views und <code>json()</code> für API-Responses.</li>
<li><strong>Model.php:</strong> Abstrakte Basis für Datenbank-Entitäten. Kapselt die <code>databaseMethods.php</code> in ein objektorientiertes Interface.</li>
<li><strong>View.php:</strong> Behandelt das Extrahieren von Daten in HTML-Templates.</li>
</ul>
<h3>Konfiguration</h3>
<ul>
<li><strong>env.ini:</strong> Speichert Datenbank-Zugangsdaten für <code>dev</code> und <code>live</code> Umgebungen.</li>
<li><strong>autoloader.php:</strong> Registriert einen PSR-4 ähnlichen Autoloader für den <code>MVC\</code> Namespace.</li>
</ul>
</section>
<section id="api">
<h2>3. API-System</h2>
<p>APIs liegen im Verzeichnis <code>WEBROOT/api/v1/</code>. Sie nutzen die <code>ApiController</code>-Klasse.</p>
<pre>
// Beispiel: WEBROOT/api/v1/get.php
use MVC\Classes\Api;
use MVC\Controller\ApiController;
Api::jsonResponse((new ApiController())->get());
</pre>
</section>
<section id="database">
<h2>4. Datenbank & Models</h2>
<p>Das Framework nutzt <code>mysqli</code> mit Prepared Statements für maximale Sicherheit.</p>
<h3>Installation (install_db.php)</h3>
<p>Mit dem Befehl <code>php install_db.php</code> können vordefinierte Tabellen und deren Models automatisch erstellt werden. Standardmäßig sind folgende Tabellen enthalten:</p>
<ul>
<li><strong>Mailing:</strong> Für das E-Mail-Logging (wird vom Mailer genutzt).</li>
<li><strong>Users:</strong> Eine Grundstruktur für Benutzer (ID, Username, Password, Email, Role, Created_at).</li>
</ul>
<h3>databaseMethods.php (Die Power-Tools)</h3>
<ul>
<li><code>DatabaseShow($table, $columns, $where, ...$values)</code></li>
<li><code>DatabaseInsert($table, $columns, ...$values)</code></li>
<li><code>DatabaseUpdate($table, $set, $where, ...$values)</code></li>
<li><code>DatabaseDelete($table, $where, ...$values)</code></li>
</ul>
</section>
<section id="classes">
<h2>5. Helper Klassen (MVC\Classes)</h2>
<ul>
<li><strong>Request:</strong> Zugriff auf <code>method()</code>, <code>queryParams()</code>, <code>headers()</code>.</li>
<li><strong>Client:</strong> Ermittelt <code>ip()</code>, <code>userAgent()</code>, <code>country()</code>.</li>
<li><strong>Cookie:</strong> Interface zum <code>set()</code>, <code>read()</code>, <code>Delete()</code> von Cookies.</li>
</ul>
</section>
<section id="generators">
<h2>6. CLI-Generator</h2>
<p><code>php make.php module [Name]</code> - Erstellt Controller, View, Route & Nav.</p>
<p><code>php make.php model [Tabelle] [Spalten]</code> - Erstellt ein Model.</p>
</section>
<section id="mailing">
<h2>7. Mailing-System</h2>
<p>Das Framework verfügt über eine integrierte <code>Mailer</code>-Klasse für den E-Mail-Versand und die Protokollierung.</p>
<h3>Konfiguration</h3>
<p>In der <code>env.ini</code> können Absender und Empfänger unter <code>[mail]</code> definiert werden.</p>
<h3>Nutzung</h3>
<pre>
use MVC\Classes\Mailer;
// Einfache Mail
Mailer::send("Betreff", "Inhalt");
// Error Mail (wird speziell markiert)
Mailer::error("Fehlertitel", "Details");
</pre>
<h3>Logging & Fallback</h3>
<p>Der Mailer versucht standardmäßig in die Datenbank-Tabelle <code>Mailing</code> zu loggen. Falls diese nicht existiert, erfolgt ein automatischer Fallback in Textdateien im Ordner <code>/logs/</code>.</p>
<p><strong>Installation:</strong> Führe <code>php install_db.php</code> aus, um die Datenbank-Struktur anzulegen.</p>
</section>
<section id="logging">
<h2>8. Error-Logging</h2>
<p>Das Framework verfügt über ein zentrales Logging-System, das in der <code>index.php</code> global registriert ist.</p>
<h3>Automatische Erfassung</h3>
<p>Alle unangefangenen Exceptions und PHP-Fehler (Warnings, Notices, etc.) werden automatisch in die Datei <code>/logs/error.log</code> geschrieben, inklusive Stacktrace bei Exceptions.</p>
<h3>Manuelle Nutzung</h3>
<pre>
use MVC\Classes\Logger;
// Einfache Nachricht in system.log
Logger::log("Etwas ist passiert");
// Spezifisches Level und Datei
Logger::log("Kritischer Fehler", "ERROR", "error.log");
</pre>
<p>Die Log-Dateien befinden sich im Hauptverzeichnis des Projekts im Ordner <code>/logs/</code>.</p>
</section>
<section id="best-practices">
<h2>9. Best Practices</h2>
<p><strong>Naming:</strong> Controller immer mit <code>Controller</code>-Suffix. Models im Singular.</p>
<p><strong>Security:</strong> Nutze <code>['auth']</code> Middleware konsequent für alle internen Seiten.</p>
</section>
</div>
<style>
.docs-container { max-width: 1000px; color: var(--text); }
.toc { background: var(--sidebar); padding: 20px; border-radius: 8px; margin-bottom: 30px; border: 1px solid var(--border); }
.toc h3 { margin-top: 0; }
section { margin-bottom: 50px; padding-bottom: 20px; border-bottom: 1px solid var(--border); }
h2 { color: var(--accent); font-size: 1.8rem; margin-bottom: 20px; }
pre { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; overflow-x: auto; border: 1px solid #333; font-size: 0.9rem; }
</style>
HTML;
$files['WEBROOT/src/MVC/View/login.php'] = <<<'HTML'
<style>
.login-container { max-width: 400px; margin: 50px auto; padding: 30px; border: 1px solid var(--border); border-radius: 8px; background: var(--sidebar); box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
.login-container h2 { text-align: center; margin-top: 0; color: var(--text); }
.login-container input { width: 100%; padding: 12px; margin: 10px 0; border: 1px solid var(--border); border-radius: 4px; box-sizing: border-box; background: var(--bg); color: var(--text); }
.login-container button { width: 100%; padding: 12px; background: var(--header); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; font-weight: bold; }
.login-container button:hover { opacity: 0.9; }
.error-msg { color: #e74c3c; background: rgba(231, 76, 60, 0.1); padding: 10px; border-radius: 4px; margin-bottom: 15px; text-align: center; font-size: 0.9rem; }
</style>
<div class="login-container">
<h2>🔑 Login</h2>
<?php if (isset($error)): ?>
<div class="error-msg"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST">
<div>
<label>Benutzername</label>
<input type="text" name="username" required autofocus placeholder="Admin / User">
</div>
<div>
<label>Passwort</label>
<input type="password" name="password" required placeholder="********">
</div>
<button type="submit">Anmelden</button>
</form>
</div>
HTML;
// --- CONFIG: routes.php ---
$files['WEBROOT/src/config/routes.php'] = <<<'PHP'
<?php
/**
* Define your routes here.
* Format: $router->add(service_name, controller_class, method_name, options);
*/
$router->add('home', \MVC\Controller\HomeController::class, 'index');
$router->add('toggle_theme', \MVC\Controller\HomeController::class, 'toggle_theme');
$router->add('docs', \MVC\Controller\DocsController::class, 'index');
$router->add('test1', \MVC\Controller\TestController::class, 'service1', ['auth']);
$router->add('test2', \MVC\Controller\TestController::class, 'service2', ['auth']);
$router->add('login', \MVC\Controller\AuthController::class, 'login');
$router->add('logout', \MVC\Controller\AuthController::class, 'logout');
$router->add('admin_mail', \MVC\Controller\AdminController::class, 'mail_service', ['auth', 'admin']);
$router->add('admin_users', \MVC\Controller\AdminController::class, 'user_service', ['auth', 'admin']);
PHP;
// --- CONFIG: navigation.php ---
$files['WEBROOT/src/config/navigation.php'] = <<<'PHP'
<?php
/**
* Define your sidebar navigation items here.
* Format: 'service_name' => ['label' => 'Label', 'auth' => true/false]
*/
return [
'home' => ['label' => '🏠 Home', 'auth' => false],
'test1' => ['label' => '🛠️ Test 1', 'auth' => true],
'test2' => ['label' => '📊 Test 2', 'auth' => true],
'admin_mail' => ['label' => '📧 Mail Service', 'auth' => true],
'admin_users' => ['label' => '👤 User Service', 'auth' => true],
'docs' => ['label' => '📚 Dokumentation', 'auth' => false],
];
PHP;
// --- ENTRY POINT ---
$files['WEBROOT/index.php'] = <<<'PHP'
<?php
session_start();
require_once __DIR__ . '/src/MVC/autoloader.php';
use MVC\Classes\Logger;
set_exception_handler([Logger::class, 'logException']);
set_error_handler([Logger::class, 'logError']);
$service = $_GET['service'] ?? 'home';
try {
$router = new \MVC\Core\Router();
// Load routes from config
require_once __DIR__ . '/src/config/routes.php';
$layout = new \MVC\Layout\Layout('main');
$layout->setBlock('title', 'MVC Starter');
$layout->setBlock('header_title', '🚀 MVC Dashboard');
$headerActions = '<button class="btn-toggle" onclick="toggleTheme()">🌓 Theme umschalten</button>';
if (isset($_SESSION['user_logged_in'])) {
$headerActions = '<span style="margin-right: 15px; font-size: 0.9rem;">👤 ' . htmlspecialchars($_SESSION['username']) . '</span>' . '<a href="?service=logout" class="btn-toggle" style="text-decoration: none; background: rgba(255,255,255,0.2); margin-right: 10px;">🚪 Logout</a> ' . $headerActions;
} else {
$headerActions = '<a href="?service=login" class="btn-toggle" style="text-decoration: none; background: var(--accent); color: white; margin-right: 10px;">🔑 Login</a> ' . $headerActions;
}
$layout->setBlock('header_actions', $headerActions);
// Sidebar Block
ob_start();
// Load navigation from config
$navItems = require __DIR__ . '/src/config/navigation.php';
?>
<nav>
<?php foreach ($navItems as $navKey => $item):
$isLoggedIn = isset($_SESSION['user_logged_in']);
$isAdmin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
if ($item['auth'] && !$isLoggedIn) continue;
if (strpos($navKey, 'admin_') === 0 && !$isAdmin) continue;
?>
<a href="?service=<?= $navKey ?>" class="<?= $service === $navKey ? 'active' : '' ?>"><?= $item['label'] ?></a>
<?php endforeach; ?>
<?php if (!isset($_SESSION['user_logged_in'])): ?>
<div style="margin-top: 20px; padding: 10px; font-size: 0.8rem; opacity: 0.5; border-top: 1px solid var(--border);">
🔒 Melden Sie sich an, um alle Funktionen zu sehen.
</div>
<?php endif; ?>
</nav>
<?php
$layout->setBlock('sidebar', ob_get_clean());
$layout->setBlock('content', $router->dispatch($service));
echo $layout->render();
} catch (\Throwable $e) {
Logger::logException($e);
http_response_code(500);
echo "<h1>Error</h1><p>" . htmlspecialchars($e->getMessage()) . "</p>";
if (ini_get('display_errors')) echo "<pre>" . htmlspecialchars($e->getTraceAsString()) . "</pre>";
}
PHP;
// --- GENERATOR: make.php ---
$files['make.php'] = <<<'PHP'
<?php
/**
* MVC Unified CLI Tool
* Nutzung: php make.php <command> [args]
*/
if (php_sapi_name() !== 'cli') {
die("Dieses Script muss über die CLI ausgeführt werden.\n");
}
$command = $argv[1] ?? null;
if (!$command || in_array($command, ['help', '-h', '--help'])) {
showHelp();
exit;
}
switch ($command) {
case 'model':
createModel(array_slice($argv, 2));
break;
case 'module':
createModule(array_slice($argv, 2));
break;
default:
echo "❌ Unbekannter Befehl: $command\n";
showHelp();
exit(1);
}
function showHelp() {
echo "MVC CLI Tool\n";
echo "-----------\n";
echo "Nutzung: php make.php <befehl> [optionen]\n\n";
echo "Befehle:\n";
echo " model Erstellt ein neues Model basierend auf einer Datenbanktabelle\n";
echo " module Erstellt ein neues Modul (Controller, View, Route, Navigation)\n\n";
echo "Details zu model:\n";
echo " php make.php model <TabellenName> <Spalte1,Spalte2,...>\n\n";
echo "Details zu module:\n";
echo " php make.php module <Name> [optionen]\n";
echo " Optionen:\n";
echo " -c, --controller Nur Controller erstellen\n";
echo " -v, --view Nur View erstellen\n";
echo " -r, --route Nur Route hinzufügen\n";
echo " -n, --nav Nur Navigationspunkt hinzufügen\n";
echo " -a, --auth Route/Nav erfordert Login\n";
echo " -l, --label Label für Navigationspunkt\n";
}
function createModel($args) {
$tableName = $args[0] ?? null;
$columnsInput = $args[1] ?? null;
// Interaktive Abfrage, falls Argumente fehlen
if (!$tableName) {
echo "Tabellenname eingeben (z.B. Benutzer): ";
$handle = fopen("php://stdin", "r");
$tableName = trim(fgets($handle));
fclose($handle);
}
if (!$columnsInput) {
echo "Spalten kommagetrennt eingeben (z.B. NAME,EMAIL,ROLLE): ";
$handle = fopen("php://stdin", "r");
$columnsInput = trim(fgets($handle));
fclose($handle);
}
if (!$tableName) die("❌ Fehler: Kein Tabellenname angegeben.\n");
// Namen formatieren
$className = str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($tableName))));
$modelFile = __DIR__ . "/WEBROOT/src/MVC/Model/{$className}.php";
// Spalten aufbereiten
$columns = array_map('trim', explode(',', $columnsInput));
$columnList = implode("', '", $columns);
// Model-Inhalt generieren
$content = <<<PHPMODEL
<?php
namespace MVC\Model;
use MVC\Core\Model;
/**
* Model für die Tabelle '{$tableName}'
* Verfügbare Spalten: '{$columnList}'
*/
class {$className} extends Model {
protected static \$table = "{$tableName}";
// Hier kannst du spezifische Methoden für dieses Model hinzufügen
/**
* Beispiel: Suche nach einer spezifischen Spalte
*/
public static function getByFirstColumn(\$value) {
return self::where("{$columns[0]} = ?", \$value);
}
}
PHPMODEL;
// Datei schreiben
if (!is_dir(dirname($modelFile))) {
mkdir(dirname($modelFile), 0755, true);
}
if (!file_exists($modelFile)) {
file_put_contents($modelFile, $content);
echo "✅ Model erstellt: WEBROOT/src/MVC/Model/{$className}.php\n";
echo "--- INFO ---\n";
echo "Tabelle: {$tableName}\n";
echo "Klasse: MVC\\Model\\{$className}\n";
echo "Spalten: " . implode(', ', $columns) . "\n";
} else {
echo "⚠️ Model {$className}.php existiert bereits.\n";
}
}
function createModule($args) {
// Da wir args als Slice übergeben, müssen wir sie manuell parsen
$name = null;
$manualFlags = [];
$label = null;
for ($i = 0; $i < count($args); $i++) {
$arg = $args[$i];
if ($arg === '-l' || $arg === '--label') {
$label = $args[++$i] ?? null;
} else if (strpos($arg, '-') === 0) {
$flag = ltrim($arg, '-');
$manualFlags[] = $flag;
} else if ($name === null) {
$name = $arg;
}
}
if (!$name) {
echo "❌ Fehler: Kein Modulname angegeben.\n";
return;
}
$hasFlag = function($short, $long) use ($manualFlags) {
return in_array($short, $manualFlags) || in_array($long, $manualFlags);
};
$doController = $hasFlag('c', 'controller');
$doView = $hasFlag('v', 'view');
$doRoute = $hasFlag('r', 'route');
$doNav = $hasFlag('n', 'nav');
$isAuth = $hasFlag('a', 'auth');
// Wenn gar keine spezifischen Flags gesetzt wurden, mache alles
if (!($doController || $doView || $doRoute || $doNav)) {
$doController = $doView = $doRoute = $doNav = true;
}
// Namen formatieren
$key = strtolower(str_replace(' ', '_', $name));
$className = str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
$label = $label ?: ucwords(str_replace('_', ' ', $key));
$controllerFile = __DIR__ . "/WEBROOT/src/MVC/Controller/{$className}Controller.php";
$viewFile = __DIR__ . "/WEBROOT/src/MVC/View/{$key}.php";
$routesFile = __DIR__ . "/WEBROOT/src/config/routes.php";
$navFile = __DIR__ . "/WEBROOT/src/config/navigation.php";
// 1. Controller erstellen
if ($doController) {
$controllerContent = <<<PHPCTRL
<?php
namespace MVC\Controller;
use MVC\Core\Controller;
use MVC\Classes\Client;
use MVC\Classes\Cookie;
use MVC\Classes\Request;
use MVC\Classes\Server;
// use MVC\Model\YourModel; // Importiere dein Model
class {$className}Controller extends Controller {
public function {$key}() {
// Nutze Models für den Datenbankzugriff
// \$data = YourModel::all();
return \$this->render('{$key}', [
'title' => '{$className} Modul',
'message' => 'Willkommen in deinem neuen Modul {$className}!'
]);
}
}
PHPCTRL;
if (!file_exists($controllerFile)) {
file_put_contents($controllerFile, $controllerContent);
echo "✅ Controller erstellt: WEBROOT/src/MVC/Controller/{$className}Controller.php\n";
} else {
echo "⚠️ Controller existiert bereits.\n";
}
}
// 2. View erstellen
if ($doView) {
$viewContent = <<<HTML
<h2><?= htmlspecialchars(\$title) ?></h2>
<div class="card">
<p><?= htmlspecialchars(\$message) ?></p>
<p>Hier kannst du nun dein Projekt aufbauen.</p>
</div>
<style>
.card {
background: var(--card-bg);
padding: 20px;
border-radius: 8px;
border: 1px solid var(--card-border);
}
</style>
HTML;
if (!file_exists($viewFile)) {
file_put_contents($viewFile, $viewContent);
echo "✅ View erstellt: WEBROOT/src/MVC/View/{$key}.php\n";
} else {
echo "⚠️ View existiert bereits.\n";
}
}
// 3. Route hinzufügen
if ($doRoute) {
if (file_exists($routesFile)) {
$routesContent = file_get_contents($routesFile);
$routeLine = "\$router->add('{$key}', \\MVC\\Controller\\{$className}Controller::class, '{$key}'" . ($isAuth ? ", ['auth']" : "") . ");\n";
if (strpos($routesContent, "'{$key}'") === false) {
file_put_contents($routesFile, $routesContent . $routeLine);
echo "✅ Route hinzugefügt: {$routesFile}\n";
} else {
echo "⚠️ Route für '{$key}' existiert bereits in routes.php\n";
}
} else {
echo "❌ Fehler: routes.php nicht gefunden!\n";
}
}
// 4. Navigation hinzufügen
if ($doNav) {
if (file_exists($navFile)) {
$navContent = file_get_contents($navFile);
$authStr = $isAuth ? 'true' : 'false';
$navItem = " '{$key}' => ['label' => '{$label}', 'auth' => {$authStr}],\n";
if (strpos($navContent, "'{$key}'") === false) {
// Wir suchen nach dem Ende des Arrays ];
$newNavContent = preg_replace('/(\];\s*)$/', $navItem . '$1', trim($navContent));
file_put_contents($navFile, $newNavContent . "\n");
echo "✅ Navigation hinzugefügt: {$navFile}\n";
} else {
echo "⚠️ Navigation für '{$key}' existiert bereits in navigation.php\n";
}
} else {
echo "❌ Fehler: navigation.php nicht gefunden!\n";
}
}
echo "\nFertig!\n";
}
PHP;
$files['Dockerfile'] = <<<'DOCKER'
FROM php:8.2-apache
RUN apt-get update && apt-get install -y libpng-dev libonig-dev libxml2-dev zip unzip curl
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-install mysqli pdo_mysql mbstring exif pcntl bcmath gd
RUN a2enmod rewrite
WORKDIR /var/www/html
ENV APACHE_DOCUMENT_ROOT /var/www/html/WEBROOT
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
EXPOSE 80
DOCKER;
$files['docker-compose.yml'] = <<<'YAML'
version: '3.8'
services:
web:
build: .
container_name: mvc_web
ports:
- "8080:80"
volumes:
- .:/var/www/html
depends_on:
- db
networks:
- mvc_network
db:
image: mariadb:10.11
container_name: mvc_db
restart: always
environment:
MARIADB_ROOT_PASSWORD: root_password
MARIADB_DATABASE: mvc_database
MARIADB_USER: mvc_user
MARIADB_PASSWORD: mvc_password
networks:
- mvc_network
networks:
mvc_network:
driver: bridge
YAML;
$files['install_db.php'] = <<<'PHP'
<?php
if (php_sapi_name() !== 'cli') die("CLI only.\n");
require_once __DIR__ . '/WEBROOT/src/database/databaseConnect.php';
require_once __DIR__ . '/WEBROOT/src/utils/databaseMethods.php';
$config = parse_ini_file(__DIR__ . '/WEBROOT/src/config/env.ini');
$env = strtoupper($config['APP_ENV']);
$dbName = $config["DB_{$env}_DATABASE"];
$workerUser = $config["DB_{$env}_USERNAME"];
$workerPass = $config["DB_{$env}_PASSWORD"];
echo "🚀 Starte Datenbank-Setup als Root...\n";
$rootConn = db_connect_root();
if (!$rootConn) {
die("❌ Fehler: Verbindung als Root fehlgeschlagen. Bitte prüfe DB_ROOT credentials in env.ini\n");
}
// 1. Datenbank erstellen
echo "📁 Erstelle Datenbank '$dbName'...\n";
$rootConn->query("CREATE DATABASE IF NOT EXISTS `$dbName` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
// 2. Worker User erstellen und berechtigen (intern & extern)
echo "👤 Erstelle Worker-User '$workerUser'...\n";
$rootConn->query("CREATE USER IF NOT EXISTS '$workerUser'@'localhost' IDENTIFIED BY '$workerPass'");
$rootConn->query("CREATE USER IF NOT EXISTS '$workerUser'@'%' IDENTIFIED BY '$workerPass'");
echo "🔐 Vergebe Berechtigungen...\n";
$rootConn->query("GRANT ALL PRIVILEGES ON `$dbName`.* TO '$workerUser'@'localhost'");
$rootConn->query("GRANT ALL PRIVILEGES ON `$dbName`.* TO '$workerUser'@'%'");
$rootConn->query("FLUSH PRIVILEGES");
$rootConn->close();
echo "🚀 Datenbank-Tabellen-Installation...\n";
$tables = [
'Mailing' => [
'columns' => "`ID` INT AUTO_INCREMENT PRIMARY KEY, `DATUM` DATETIME NOT NULL, `SUBJECT` VARCHAR(255), `MESSAGE` TEXT, `URL` VARCHAR(255), `CLIENT_IP` VARCHAR(45)",
'model' => "<?php\n\nnamespace MVC\Model;\n\nuse MVC\Core\Model;\n\nclass Mailing extends Model {\n protected static \$table = \"Mailing\";\n public static function log(\$subject, \$message, \$url, \$ip, \$date) {\n return self::insert(['DATUM' => \$date, 'SUBJECT' => \$subject, 'MESSAGE' => \$message, 'URL' => \$url, 'CLIENT_IP' => \$ip]);\n }\n}"
],
'Users' => [
'columns' => "`ID` INT AUTO_INCREMENT PRIMARY KEY, `USERNAME` VARCHAR(50) UNIQUE NOT NULL, `PASSWORD` VARCHAR(255) NOT NULL, `EMAIL` VARCHAR(100), `ROLE` VARCHAR(20) DEFAULT 'user', `CREATED_AT` DATETIME DEFAULT CURRENT_TIMESTAMP",
'model' => "<?php\n\nnamespace MVC\Model;\n\nuse MVC\Core\Model;\n\nclass Users extends Model {\n protected static \$table = \"Users\";\n public static function getByUsername(\$username) {\n \$result = self::where(\"USERNAME = ?\", \$username);\n return \$result[0] ?? null;\n }\n}"
]
];
foreach ($tables as $name => $info) {
echo "--- $name ---\n";
try {
if (DatabaseCreateTable($name, $info['columns'])) {
$path = __DIR__ . "/WEBROOT/src/MVC/Model/{$name}.php";
if (!file_exists($path)) {
if (!is_dir(dirname($path))) mkdir(dirname($path), 0755, true);
file_put_contents($path, $info['model']);
echo "✅ bereit.\n";
} else {
echo "ℹ️ Model existiert bereits.\n";
}
}
} catch (Exception $e) { echo "❌: " . $e->getMessage() . "\n"; }
}
// 3. Test-User anlegen
echo "👤 Erstelle Test-User...\n";
require_once __DIR__ . '/WEBROOT/src/MVC/Model/Users.php';
try {
if (!\MVC\Model\Users::getByUsername('admin')) {
\MVC\Model\Users::insert([
'USERNAME' => 'admin',
'PASSWORD' => password_hash('admin123', PASSWORD_DEFAULT),
'EMAIL' => 'admin@example.com',
'ROLE' => 'admin'
]);
echo "✅ Test-User 'admin' mit Passwort 'admin123' erstellt.\n";
} else {
echo "ℹ️ Test-User 'admin' existiert bereits.\n";
}
} catch (Exception $e) {
echo "❌ Fehler beim Erstellen des Test-Users: " . $e->getMessage() . "\n";
}
echo "\n✨ Installation abgeschlossen.\n";
PHP;
// 3. Dateien schreiben
foreach ($files as $file => $content) {
// Docker-Dateien und die .code-workspace Datei sollen im Verzeichnis der init.php (Root) landen
if ($file === 'Dockerfile' || $file === 'docker-compose.yml' || str_ends_with($file, '.code-workspace')) {
$path = getcwd() . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $file);
} else {
$path = $baseDir . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $file);
}
file_put_contents($path, $content);
echo "📄 Datei erstellt: $file\n";
}
echo "\n✨ Projekt '$projectName' wurde initialisiert!\n";
echo "1. Richte deine Datenbankverbindung in WEBROOT/src/config/env.ini ein.\n";
echo "2. Datenbank installieren: php install_db.php\n";
echo " (Ohne Tabelle werden Logs in /logs/ gespeichert)\n";
echo "3. CLI-Tool nutzen: php make.php help\n";
echo "4. Starten: php server.php\n";
echo "5. Öffnen: http://localhost:8000\n";