Set up modern PHP MVC project structure for GetYourBand platform

- Implemented clean MVC architecture with Router, Controller, and Model base classes
- Created database migrations for users, bands, bookings, reviews, and availability
- Set up Tailwind CSS with yellow color scheme and modern design
- Added Alpine.js for reactive JavaScript components
- Configured Vite for asset building and hot module replacement
- Created authentication and role-based middleware
- Implemented helper functions and configuration system
- Added comprehensive README with setup instructions
- Configured Apache with proper rewrite rules and security headers
- Set up Composer and npm package management with modern dependencies
This commit is contained in:
Claude
2025-12-02 21:31:08 +00:00
parent 798a2785e7
commit 143fe3d488
37 changed files with 2015 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
<?php
namespace App\Core;
class Controller
{
protected function view(string $view, array $data = []): void
{
extract($data);
$viewPath = __DIR__ . '/../Views/' . str_replace('.', '/', $view) . '.php';
if (!file_exists($viewPath)) {
throw new \RuntimeException("View not found: {$view}");
}
require_once $viewPath;
}
protected function json($data, int $status = 200): void
{
http_response_code($status);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
protected function redirect(string $path): void
{
header("Location: {$path}");
exit;
}
protected function back(): void
{
$referer = $_SERVER['HTTP_REFERER'] ?? '/';
$this->redirect($referer);
}
protected function input(string $key, $default = null)
{
return $_POST[$key] ?? $_GET[$key] ?? $default;
}
protected function validate(array $rules): array
{
$errors = [];
$data = [];
foreach ($rules as $field => $fieldRules) {
$value = $this->input($field);
$fieldRules = explode('|', $fieldRules);
foreach ($fieldRules as $rule) {
if ($rule === 'required' && empty($value)) {
$errors[$field][] = ucfirst($field) . ' is required';
}
if (str_starts_with($rule, 'min:')) {
$min = (int) substr($rule, 4);
if (strlen($value) < $min) {
$errors[$field][] = ucfirst($field) . " must be at least {$min} characters";
}
}
if (str_starts_with($rule, 'max:')) {
$max = (int) substr($rule, 4);
if (strlen($value) > $max) {
$errors[$field][] = ucfirst($field) . " must not exceed {$max} characters";
}
}
if ($rule === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$field][] = ucfirst($field) . ' must be a valid email';
}
}
$data[$field] = $value;
}
if (!empty($errors)) {
$_SESSION['errors'] = $errors;
$_SESSION['old'] = $data;
$this->back();
}
return $data;
}
protected function auth()
{
return $_SESSION['user'] ?? null;
}
protected function isAuthenticated(): bool
{
return isset($_SESSION['user']);
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
namespace App\Core;
use Database\Database;
use PDO;
abstract class Model
{
protected PDO $db;
protected string $table;
protected string $primaryKey = 'id';
protected array $fillable = [];
public function __construct()
{
$this->db = Database::connect();
}
public function all(): array
{
$stmt = $this->db->query("SELECT * FROM {$this->table}");
return $stmt->fetchAll();
}
public function find(int $id): ?array
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE {$this->primaryKey} = ? LIMIT 1");
$stmt->execute([$id]);
$result = $stmt->fetch();
return $result ?: null;
}
public function where(string $column, $value): array
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE {$column} = ?");
$stmt->execute([$value]);
return $stmt->fetchAll();
}
public function first(string $column, $value): ?array
{
$stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE {$column} = ? LIMIT 1");
$stmt->execute([$value]);
$result = $stmt->fetch();
return $result ?: null;
}
public function create(array $data): int
{
$data = $this->filterFillable($data);
$columns = implode(', ', array_keys($data));
$placeholders = implode(', ', array_fill(0, count($data), '?'));
$sql = "INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})";
$stmt = $this->db->prepare($sql);
$stmt->execute(array_values($data));
return (int) $this->db->lastInsertId();
}
public function update(int $id, array $data): bool
{
$data = $this->filterFillable($data);
$set = implode(' = ?, ', array_keys($data)) . ' = ?';
$sql = "UPDATE {$this->table} SET {$set} WHERE {$this->primaryKey} = ?";
$stmt = $this->db->prepare($sql);
return $stmt->execute([...array_values($data), $id]);
}
public function delete(int $id): bool
{
$stmt = $this->db->prepare("DELETE FROM {$this->table} WHERE {$this->primaryKey} = ?");
return $stmt->execute([$id]);
}
public function query(string $sql, array $params = []): array
{
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
public function execute(string $sql, array $params = []): bool
{
$stmt = $this->db->prepare($sql);
return $stmt->execute($params);
}
protected function filterFillable(array $data): array
{
if (empty($this->fillable)) {
return $data;
}
return array_intersect_key($data, array_flip($this->fillable));
}
}
+118
View File
@@ -0,0 +1,118 @@
<?php
namespace App\Core;
class Router
{
private array $routes = [];
private array $middlewareStack = [];
public function get(string $path, $handler): void
{
$this->addRoute('GET', $path, $handler);
}
public function post(string $path, $handler): void
{
$this->addRoute('POST', $path, $handler);
}
public function put(string $path, $handler): void
{
$this->addRoute('PUT', $path, $handler);
}
public function delete(string $path, $handler): void
{
$this->addRoute('DELETE', $path, $handler);
}
public function group(array $attributes, callable $callback): void
{
$previousMiddleware = $this->middlewareStack;
if (isset($attributes['middleware'])) {
$this->middlewareStack = array_merge(
$this->middlewareStack,
(array) $attributes['middleware']
);
}
$callback($this);
$this->middlewareStack = $previousMiddleware;
}
private function addRoute(string $method, string $path, $handler): void
{
$this->routes[] = [
'method' => $method,
'path' => $path,
'handler' => $handler,
'middleware' => $this->middlewareStack,
];
}
public function dispatch(string $requestMethod, string $requestUri): void
{
$requestUri = parse_url($requestUri, PHP_URL_PATH);
foreach ($this->routes as $route) {
if ($route['method'] !== $requestMethod) {
continue;
}
$pattern = $this->convertToPattern($route['path']);
if (preg_match($pattern, $requestUri, $matches)) {
array_shift($matches); // Remove full match
// Execute middleware
foreach ($route['middleware'] as $middleware) {
$this->executeMiddleware($middleware);
}
// Execute handler
$this->executeHandler($route['handler'], $matches);
return;
}
}
// 404 Not Found
http_response_code(404);
echo "404 - Page Not Found";
}
private function convertToPattern(string $path): string
{
$pattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '([^/]+)', $path);
return '#^' . $pattern . '$#';
}
private function executeMiddleware(string $middleware): void
{
$parts = explode(':', $middleware);
$name = $parts[0];
$params = $parts[1] ?? null;
$middlewareClass = "App\\Middleware\\" . ucfirst($name) . "Middleware";
if (!class_exists($middlewareClass)) {
throw new \RuntimeException("Middleware not found: {$middlewareClass}");
}
$instance = new $middlewareClass();
$instance->handle($params);
}
private function executeHandler($handler, array $params): void
{
if (is_array($handler)) {
[$class, $method] = $handler;
$controller = new $class();
call_user_func_array([$controller, $method], $params);
} elseif (is_callable($handler)) {
call_user_func_array($handler, $params);
}
}
}