- 使用 php 程式語言實作 MVC 框架!
- Model (模組) : 處理大部分的商業邏輯和資料運算邏輯。
- Controller (控制器) : 負責接收、回應使用者請求,以及準備資料、展示資料等工作。
- View (檢視) : 負責整理好資料,通過HTML方式回應給使用者。
- MVC 示意圖如下:
- 先從定義文件與規範開始,再逐一進入MVC階段!
定義目錄架構與環境設定檔案!
- 專案根目錄 helloMVC 下的目錄結構:
helloMVC |-- app | |-- Models | |-- Views | |-- Controlles |-- config |-- kernel |-- public |-- static |-- tmp |-- scripts
- app :應用程式目錄
- config :應用程式組態檔目錄
- kernal :MVC 核心架構程式碼目錄
- public :公開網頁檔案目錄,例如:index.php 等 MVC 框架程式進入點!
- static :靜態網頁目錄,可放置 css 等檔案!
- tmp :臨時放置檔案目錄
- scripts : 可執行系統命令工具目錄
- 撰寫程式風格要求:
- 資料庫表格名稱:使用小寫的名詞
- 模組名稱:使用字首小寫名詞 + Model 組合,亦名為「駝峰命名法」,例:carModel !
- 控制器名稱:使用字首大寫名詞 + Controller 組合,亦名為「雙駝峰命名法」,例:CarController !
- 核心程式名稱:使用字首大寫名詞,例:Route.php, Controller.php 等等 !
- 檢視程式名稱:使用控制器分類名稱以及行為動詞,例: Car/move.php !
- 設定網址目錄轉向!
- 目的:將程式的入口單一化至 index.php
- 靜態目錄下的檔案例外!
- 以 Apache Web Server 為例,編寫 .htaccess , 放置在專案根目錄 helloMVC/public 下:
#<IfModule mod_rewrite.c> # 開啟 Apache Web Server 目錄轉向功能 RewriteEngine On # 網址請求路徑如果存在真實檔名或目錄,就可以直接使用 RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # 如果網址查看的檔案或目錄不存在,則重定向所有請求到 index.php RewriteRule ^(.*)$ index.php?url=$1 [PT,L] #</IfModule mod_rewrite.c>
- 允許靜態檔案直接存取!
- 不存在的檔案或是目錄,直接匯入 index.php 檔案,控制程式進出!
- 可間接最佳化存取網址,有利於 SEO 的使用!
- 撰寫 public 目錄下的 index.php 檔案 !
<?php use kernel\Kernel; // 設定應用程式目錄為當前目錄 define('APP_PATH', __DIR__.'../'); // 開啟除錯模式 define('APP_DEBUG', true); // 載入框架 require(APP_PATH.'kernel/kernel.php'); // 產生實例化物件 (new Kernal())->run();
PS: 不用寫結尾的 ?> - 撰寫 kernel 目錄下的 kernel.php 檔案:
<?php namespace kernel; use config\Config; use config\Router; require_once(APP_PATH.'config/config.php'); require_once(APP_PATH.'config/router.php'); class Kernel { protected $_config; protected $_router; public function __construct(){ $this->_config = new Config(); $this->_router = new Router($_SERVER); } public function run(){ spl_autoload_register(array($this, 'loadClass')); $this->unregisterGlobals(); $this->_config->show(); //由路由設定,取出需要使用的控制器 include ('Router.php'); $uri = $this->_router->run(); $controller = 'App\\Controllers\\'.$uri[1]; //找出控制器後,程式交給控制器執行 if (!class_exists($controller)){ exit($controller.'控制器不存在'); } else { (new $controller())->run(); } } //自動加載類別 public function loadClass($className){ $classMap = $this->classMap(); if (isset($classMap[$className])){ $file = $classMap[$className]; } elseif (strpos($className, '\\') !== false){ //包含 app 目錄下的文件檔案 $file = APP_PATH.str_replace('\\','/',$className).'.php'; if (!is_file($file)){ return ; } } else { return; } include $file; } //類別對應命名空間 public function classMap(){ return [ 'kernel\Controller' => CORE_PATH.'Controller.php', 'kernel\Model' => CORE_PATH.'Model.php', 'kernel\View' => CORE_PATH.'View.php', 'kernel\Router' => CORE_PATH.'Router.php' ]; } //取消全域自定義變數 public function unregisterGlobals(){ if (\ini_get('register_globals')){ $array = array('_SESSION','_POST','_GET','_COOKIE','_REQUEST','_SERVER','_ENV','_FILES'); foreach ($array as $value) { foreach ($GLOBALS[$value] as $key => $var) { if ($var === $GLOBALS[$key]){ unset($GLOBALS[$key]); } } } } } }
- 編寫 config 目錄下的 config.php 檔案:
<?php namespace config; class Config{ public function show(){ $file = fopen("../config/.env",'rb'); while ((! feof($file)) && ($line = fgets($file))){ $line = trim($line); $info = explode('=',$line); if (empty($info[0])){ continue; } define($info[0],$info[1]); } } }
PS: 一般程式常用的變數亦可在此設定! - 撰寫環境設定檔,放在 config 目錄下,例:.env 檔案!
DB_NAME=contact DB_USER=hello DB_PASSWORD=hello$1213 DB_HOST=localhost APP_URL=http://localhost CORE_PATH=../kernel/
- 撰寫框架核心路由程式,放置於 config 目錄下,例:router.php
<?php namespace config; class Router { public $request; public static $routes = array(); //建構子必需要剖析網址 URI 的部份 public function __construct(array $request){ $this->request = basename($request['REQUEST_URI']); } public static function addRoute(string $uri, $controller) : void { self::$routes[$uri] = $controller; } public function hasRoute(string $uri) : bool { $uri = '/'.$uri; return array_key_exists($uri, self::$routes); } public function run(){ //分析參數 $uri = array(); $uri = explode('?',$this->request); if (!isset($uri[1])){ $uri[1] = ""; } if ($this->hasRoute($uri[0])){ return array($uri[1],(self::$routes['/'.$uri[0]])); } } }
- 撰寫路由的路徑設定檔,放置於 kernel 目錄下,例:Router.php
<?php use config\Router; Router::addRoute('/login',LoginController::class); Router::addRoute('/',IndexController::class);
- 撰寫框架核心控制器抽象類別,放置於 kernel 目錄下,例:Controller.php
<?php namespace kernel; abstract class Controller { abstract public function run(); }
- 撰寫一個測試用的 IndexController 控制器,放置於 app/Controllers 目錄下,例:IndexController.php
<?php namespace App\Controllers; use kernel\Controller; use App\Models\indexModel; class IndexController extends Controller { public function run(){ $user = new indexModule(); $result = $user->printName(); print "Hello ,".$result; } }
- 撰寫框架核心模組抽象類別,放置於 kernel 目錄下,例:Model.php
<?php namespace kernel; abstract class Model { abstract public function __construct(); abstract public function __destruct(); }
- 撰寫一個測試用的模組程式,放置於 app/Models 目錄下,例:indexModel.php
<?php namespace App\Models; use kernel\Model; class indexModel extends Model { protected $name; public function __construct(){ $this->name = "Peter"; } public function printName(){ return $this->name; } public function __destruct(){ } }
PS: 檢查是否成功,可使用 http://helloMVC 檢查!
參考文獻