myframe/
├── app/
│ ├── controllers/
│ ├── models/
│ └── views/
├── core/
│ ├── Application.php
│ ├── Controller.php
│ ├── Model.php
│ └── Router.php
├── public/
│ └── index.php # 唯一入口
├── vendor/
├── composer.json
└── .htaccess # Apache 重寫
// composer.json
{
"autoload": {
"psr-4": {
"Core\\": "core/",
"App\\": "app/"
}
},
"require": {
"php": ">=8.0"
}
}
執行 composer install 生成 vendor/autoload.php。
// core/Router.php
namespace Core;
class Router {
protected array $routes = [];
public function get(string $path, callable|array $action): void {
$this->routes['GET'][$path] = $action;
}
public function post(string $path, callable|array $action): void {
$this->routes['POST'][$path] = $action;
}
public function resolve(string $method, string $uri): ?array {
$uri = parse_url($uri, PHP_URL_PATH);
$routes = $this->routes[$method] ?? [];
foreach ($routes as $pattern => $action) {
// 簡單參數匹配:/user/{id:\d+}
$regex = preg_replace('/\{(\w+)(?::([^}]+))?\}/', '(?P<$1>$2)', $pattern);
$regex = '@^' . $regex . '$@';
if (preg_match($regex, $uri, $matches)) {
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
return ['action' => $action, 'params' => $params];
}
}
return null;
}
}
// core/Application.php
namespace Core;
class Application {
public Router $router;
public string $basePath;
public Controller $controller;
public function __construct(string $basePath) {
$this->basePath = $basePath;
$this->router = new Router();
require $basePath . '/config/routes.php'; // 載入路由定義
}
public function run(): void {
$method = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
$route = $this->router->resolve($method, $uri);
if ($route) {
[$class, $method] = $route['action'];
$this->controller = new $class($this);
call_user_func_array([$this->controller, $method], $route['params']);
} else {
http_response_code(404);
echo "404 Not Found";
}
}
public function view(string $view, array $data = []): void {
extract($data);
include $this->basePath . "/app/views/{$view}.php";
}
}
// core/Controller.php
namespace Core;
abstract class Controller {
protected Application $app;
public function __construct(Application $app) {
$this->app = $app;
}
protected function render(string $view, array $data = []): void {
$this->app->view($view, $data);
}
protected function json(mixed $data, int $status = 200): void {
http_response_code($status);
header('Content-Type: application/json');
echo json_encode($data);
}
protected function redirect(string $url, int $status = 302): void {
header("Location: $url", true, $status);
exit;
}
}
// core/Model.php
namespace Core;
use PDO;
abstract class Model {
protected PDO $db;
public function __construct(PDO $pdo) {
$this->db = $pdo;
}
protected function query(string $sql, array $params = []): PDOStatement {
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
return $stmt;
}
protected function findOne(string $sql, array $params = []): ?array {
return $this->query($sql, $params)->fetch(PDO::FETCH_ASSOC) ?: null;
}
protected function findAll(string $sql, array $params = []): array {
return $this->query($sql, $params)->fetchAll(PDO::FETCH_ASSOC);
}
protected function execute(string $sql, array $params = []): int {
return $this->query($sql, $params)->rowCount();
}
protected function lastInsertId(): string {
return $this->db->lastInsertId();
}
}
// config/routes.php
use Core\Router;
use App\Controllers\SiteController;
use App\Controllers\ApiController;
$router->get('/', [SiteController::class, 'home']);
$router->get('/post/{id:\d+}', [SiteController::class, 'show']);
$router->post('/comment', [SiteController::class, 'comment']);
$router->get('/api/users', [ApiController::class, 'users']);
// app/controllers/SiteController.php
namespace App\Controllers;
use Core\Controller;
use App\Models\PostModel;
class SiteController extends Controller {
public function home(): void {
$postModel = new PostModel($this->app->db);
$posts = $postModel->findAll('SELECT * FROM posts ORDER BY created_at DESC LIMIT 10');
$this->render('home', ['posts' => $posts]);
}
public function show(int $id): void {
$postModel = new PostModel($this->app->db);
$post = $postModel->findOne('SELECT * FROM posts WHERE id = ?', [$id]);
if (!$post) { $this->app->redirect('/'); return; }
$this->render('post/show', ['post' => $post]);
}
public function comment(int $postId): void {
// 驗證、入庫...
$this->json(['status' => 'ok']);
}
}
// app/models/PostModel.php
namespace App\Models;
use Core\Model;
class PostModel extends Model {
// 可添加業務專用方法
public function latest(int $limit = 10): array {
return $this->findAll('SELECT * FROM posts ORDER BY created_at DESC LIMIT ?', [$limit]);
}
}
<h1>最新文章</h1>
<ul>
<?php foreach ($posts as $p): ?>
<li><a href="/post/<?= $p['id'] ?>"><?= htmlspecialchars($p['title']) ?></a></li>
<?php endforeach; ?>
</ul>
// public/index.php
require __DIR__ . '/../vendor/autoload.php';
$app = new Core\Application(__DIR__ . '/..');
// 資料庫連接(可提取到配置文件)
$pdo = new PDO('mysql:host=127.0.0.1;dbname=myframe;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$app->db = $pdo; // 掛載到 Application,Controller/Model 可通過 $this->app->db 訪問
$app->run();
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
掌握這套核心結構後,閱讀 Laravel、Symfony 源碼將不再陌生,所有框架本質上都是在這套基礎上疊加功能模組。