來自:yuansir-web.com / [email protected]
代碼下載: https://github.com/yuansir/tiny-php-framework
PHP的框架衆多,對於哪個框架最好,哪個框架最爛,是否應該用框架,對於這些爭論在論壇裏面都有人爭論,這裏不做評價,
個人覺得根據自己需求,選中最佳最適合自己MVC框架,並在開發中能夠體現出敏捷開發的效果就OK了,作爲一個PHPer要提高自己的對PHP和MVC的框架的認識,所以自己寫一個MVC框架是很有必要的,即使不是很完善,但是自己動手寫一個輕量簡潔的PHP MVC框架起碼對MVC的思想有一定的瞭解,而且經過自己後期的完善會漸漸形成一個自己熟悉的一個PHP框架。
來寫一個PHP MVC框架開發的簡明教程,首先聲明,教程裏面的框架不是一個完善的框架,只是一種思路,當然每個人對MVC框架實現的方法肯定是有差異的,希望高手多提意見多指正,和我一樣的菜鳥多討論多交流,剛接觸MVC的PHPer多學習。
首先,我們在項目中建立如下目錄和文件:
app
|-controller 存放控制器文件
|-model 存放模型文件
|-view 存放視圖文件
|-lib 存放自定義類庫
|-config 存放配置文件
|--config.php 系統配置文件
|-system 系統核心目錄
|-index.php 入口文件
新件的index.php爲入口文件,我們這裏採用單一入口,入口文件的內容很簡單:
<?php
/**
* 應用入口文件
* @copyright Copyright(c) 2011
* @author yuansir <[email protected]/yuansir-web.com>
* @version 1.0
*/
require dirname(__FILE__).'/system/app.php';
require dirname(__FILE__).'/config/config.php';
Application::run($CONFIG);
入口文件主要做了2件事,第一引入系統的驅動類,第二是引入配置文件,然後運行run()方法,傳入配置作爲參數,具體這2個文件是什麼內容,我們接下來繼續看。
先看一下config/config.php文件,裏面其實是一個$CONFIG變量,這個變量存放的全局的配置:
<?php
/**
* 系統配置文件
* @copyright Copyright(c) 2011
* @author yuansir <[email protected]/yuansir-web.com>
* @version 1.0
*/
/*數據庫配置*/
$CONFIG['system']['db'] = array(
'db_host' => 'localhost',
'db_user' => 'root',
'db_password' => '',
'db_database' => 'app',
'db_table_prefix' => 'app_',
'db_charset' => 'urf8',
'db_conn' => '', //數據庫連接標識; pconn 爲長久鏈接,默認爲即時鏈接
);
/*自定義類庫配置*/
$CONFIG['system']['lib'] = array(
'prefix' => 'my' //自定義類庫的文件前綴
);
$CONFIG['system']['route'] = array(
'default_controller' => 'home', //系統默認控制器
'default_action' => 'index', //系統默認控制器
'url_type' => 1 /*定義URL的形式 1 爲普通模式 index.php?c=controller&a=action&id=2
* 2 爲PATHINFO index.php/controller/action/id/2(暫時不實現)
*/
);
/*緩存配置*/
$CONFIG['system']['cache'] = array(
'cache_dir' => 'cache', //緩存路徑,相對於根目錄
'cache_prefix' => 'cache_',//緩存文件名前綴
'cache_time' => 1800, //緩存時間默認1800秒
'cache_mode' => 2, //mode 1 爲serialize ,model 2爲保存爲可執行文件
);
我這裏有意識的定義$CONFIG['system']數組表示是系統的配置文件,當然你可以在裏面定義$CONFIG['myconfig']爲表示在定義的配置,以後在程序的控制器,模型,視圖中來調用,都個很自由。
具體配置值代表什麼意思注視很清楚了,下面的如果程序中有詳細註釋的我就不解釋啦,呵呵
再來看一下system/app.php文件,主要是幹嘛的:
<?php
/**
* 應用驅動類
* @copyright Copyright(c) 2011
* @author yuansir <[email protected]/yuansir-web.com>
* @version 1.0
*/
define('SYSTEM_PATH', dirname(__FILE__));
define('ROOT_PATH', substr(SYSTEM_PATH, 0,-7));
define('SYS_LIB_PATH', SYSTEM_PATH.'/lib');
define('APP_LIB_PATH', ROOT_PATH.'/lib');
define('SYS_CORE_PATH', SYSTEM_PATH.'/core');
define('CONTROLLER_PATH', ROOT_PATH.'/controller');
define('MODEL_PATH', ROOT_PATH.'/model');
define('VIEW_PATH', ROOT_PATH.'/view');
define('LOG_PATH', ROOT_PATH.'/error/');
final class Application {
public static $_lib = null;
public static $_config = null;
public static function init() {
self::setAutoLibs();
require SYS_CORE_PATH.'/model.php';
require SYS_CORE_PATH.'/controller.php';
}
/**
* 創建應用
* @access public
* @param array $config
*/
public static function run($config){
self::$_config = $config['system'];
self::init();
self::autoload();
self::$_lib['route']->setUrlType(self::$_config['route']['url_type']);
$url_array = self::$_lib['route']->getUrlArray();
self::routeToCm($url_array);
}
/**
* 自動加載類庫
* @access public
* @param array $_lib
*/
public static function autoload(){
foreach (self::$_lib as $key => $value){
require (self::$_lib[$key]);
$lib = ucfirst($key);
self::$_lib[$key] = new $lib;
}
//初始化cache
if(is_object(self::$_lib['cache'])){
self::$_lib['cache']->init(
ROOT_PATH.'/'.self::$_config['cache']['cache_dir'],
self::$_config['cache']['cache_prefix'],
self::$_config['cache']['cache_time'],
self::$_config['cache']['cache_mode']
);
}
}
/**
* 加載類庫
* @access public
* @param string $class_name 類庫名稱
* @return object
*/
public static function newLib($class_name){
$app_lib = $sys_lib = '';
$app_lib = APP_LIB_PATH.'/'.self::$_config['lib']['prefix'].'_'.$class_name.'.php';
$sys_lib = SYS_LIB_PATH.'/lib_'.$class_name.'.php';
if(file_exists($app_lib)){
require ($app_lib);
$class_name = ucfirst(self::$_config['lib']['prefix']).ucfirst($class_name);
return new $class_name;
}else if(file_exists($sys_lib)){
require ($sys_lib);
return self::$_lib['$class_name'] = new $class_name;
}else{
trigger_error('加載 '.$class_name.' 類庫不存在');
}
}
/**
* 自動加載的類庫
* @access public
*/
public static function setAutoLibs(){
self::$_lib = array(
'route' => SYS_LIB_PATH.'/lib_route.php',
'mysql' => SYS_LIB_PATH.'/lib_mysql.php',
'template' => SYS_LIB_PATH.'/lib_template.php',
'cache' => SYS_LIB_PATH.'/lib_cache.php',
'thumbnail' => SYS_LIB_PATH.'/lib_thumbnail.php'
);
}
/**
* 根據URL分發到Controller和Model
* @access public
* @param array $url_array
*/
public static function routeToCm($url_array = array()){
$app = '';
$controller = '';
$action = '';
$model = '';
$params = '';
if(isset($url_array['app'])){
$app = $url_array['app'];
}
if(isset($url_array['controller'])){
$controller = $model = $url_array['controller'];
if($app){
$controller_file = CONTROLLER_PATH.'/'.$app.'/'.$controller.'Controller.php';
$model_file = MODEL_PATH.'/'.$app.'/'.$model.'Model.php';
}else{
$controller_file = CONTROLLER_PATH.'/'.$controller.'Controller.php';
$model_file = MODEL_PATH.'/'.$model.'Model.php';
}
}else{
$controller = $model = self::$_config['route']['default_controller'];
if($app){
$controller_file = CONTROLLER_PATH.'/'.$app.'/'.self::$_config['route']['default_controller'].'Controller.php';
$model_file = MODEL_PATH.'/'.$app.'/'.self::$_config['route']['default_controller'].'Model.php';
}else{
$controller_file = CONTROLLER_PATH.'/'.self::$_config['route']['default_controller'].'Controller.php';
$model_file = MODEL_PATH.'/'.self::$_config['route']['default_controller'].'Model.php';
}
}
if(isset($url_array['action'])){
$action = $url_array['action'];
}else{
$action = self::$_config['route']['default_action'];
}
if(isset($url_array['params'])){
$params = $url_array['params'];
}
if(file_exists($controller_file)){
if (file_exists($model_file)) {
require $model_file;
}
require $controller_file;
$controller = $controller.'Controller';
$controller = new $controller;
if($action){
if(method_exists($controller, $action)){
isset($params) ? $controller ->$action($params) : $controller ->$action();
}else{
die('控制器方法不存在');
}
}else{
die('控制器方法不存在');
}
}else{
die('控制器不存在');
}
}
}
我叫它框架驅動類,也許不合適,但是我是這樣理解的,它用來啓動這個框架,做好一些初始化的工作,下面我來詳細分析一下每個方法的功能:
1.首先時定義了一些常量,很明瞭,不解釋了
2.setAutoLibs 這個方法其實就是設定那些是系統啓動時自動加載的類庫,類庫文件都存放在SYS_LIB_PATH下面,以lib_開頭的,當然這裏你可以根據自己的規則來命名
3.autoload 這個方法就是用來引入你要自動加載的類,然後來實例化,用$_lib數組來保存類的實例,比如$lib['route']是system/lib/lib_route.php中lib_route類的實例
4.newLib 這個方法是用來加載你自定義的類的,自定義類存放在根目錄下的lib中,但是自定義的類的文件前綴是你自己定義的,看系統配置文件裏面有,我定義的是my,這樣我就可以在lib
目錄下新建一個自定義的類了,比如 my_test.php
<?php
class MyTest {
function __construct() {
echo "my lib test";
}
}
爲什麼類名這樣命名,看下newLib方法的實現就知道,其實這些你完全可以定義自己的規則,這個方法會首先去着lib下面有沒有這個類,如果有就會引入實例化,如果沒有就去找系統目錄下面的類,有就實例化
5.init 就是一個初始化的方法,裏面其實就是加載自動加載的類,以及引入核心控制器和核心模型,這個2個核心文件過會我們再來分析
6.run 方法就是啓動這個框架的了,裏面的最後2步很重要,就是獲取URL然後拆分成一個數組的形似,然後由routeToCm來分發到Controller和Model
7.routeToCm 很重要,根據URL分發到Controller和Model,這個我們過會來說
在run方法中
self::$_lib['route']->setUrlType(self::$_config['route']['url_type']); //設置url的類型
$url_array = self::$_lib['route']->getUrlArray(); //將url轉發成數組
好吧,我們來看下route的系統類到底做了說明
<?php
/**
* URL處理類
* @copyright Copyright(c) 2011
* @author yuansir <[email protected]/yuansir-web.com>
* @version 1.0
*/
final class Route{
public $url_query;
public $url_type;
public $route_url = array();
public function __construct() {
$this->url_query = parse_url($_SERVER['REQUEST_URI']);
}
/**
* 設置URL類型
* @access public
*/
public function setUrlType($url_type = 2){
if($url_type > 0 && $url_type <3){
$this->url_type = $url_type;
}else{
trigger_error("指定的URL模式不存在!");
}
}
/**
* 獲取數組形式的URL
* @access public
*/
public function getUrlArray(){
$this->makeUrl();
return $this->route_url;
}
/**
* @access public
*/
public function makeUrl(){
switch ($this->url_type){
case 1:
$this->querytToArray();
break;
case 2:
$this->pathinfoToArray();
break;
}
}
/**
* 將query形式的URL轉化成數組
* @access public
*/
public function querytToArray(){
$arr = !empty ($this->url_query['query']) ?explode('&', $this->url_query['query']) :array();
$array = $tmp = array();
if (count($arr) > 0) {
foreach ($arr as $item) {
$tmp = explode('=', $item);
$array[$tmp[0]] = $tmp[1];
}
if (isset($array['app'])) {
$this->route_url['app'] = $array['app'];
unset($array['app']);
}
if (isset($array['controller'])) {
$this->route_url['controller'] = $array['controller'];
unset($array['controller']);
}
if (isset($array['action'])) {
$this->route_url['action'] = $array['action'];
unset($array['action']);
}
if(count($array) > 0){
$this->route_url['params'] = $array;
}
}else{
$this->route_url = array();
}
}
/**
* 將PATH_INFO的URL形式轉化爲數組
* @access public
*/
public function pathinfoToArray(){
}
}
注意querytToArray方法,將將query形式的URL轉化成數組,比如原來是localhost/myapp/index.php/app=admin&controller=index&action=edit&id=9&fid=10 這樣的url就會被轉發成如下的數組
array(
'app' =>'admin',
'controller' =>'index',
'action' =>'edit',
'id' =>array(
'id' =>9,
'fid' =>10
)
)
這下再耐心來看下我寫的笨拙的routeToCm,來通過數組參數來分發到控制器,找到控制器以後還要引用相應的模型,然後就實例化控制器和模型,呵呵,貌似有點成型了。
下面就要開始實現 控制器-模型-視圖了
我們的思路是這樣的,建立一個核心模型和核心控制器,在以後自己的模型和控制器中來繼承核心模型和控制器,核心模型和控制器中主要可以是一些通用的方法和必須的組建的加載,下面我們先來寫核心控制器,
新建system/core/controller.php
<?php
/**
* 核心控制器
* @copyright Copyright(c) 2011
* @author yuansir <[email protected]/yuansir-web.com>
* @version 1.0
*/
class Controller{
public function __construct() {
// header('Content-type:text/html;chartset=utf-8');
}
/**
* 實例化模型
* @access final protected
* @param string $model 模型名稱
*/
final protected function model($model) {
if (empty($model)) {
trigger_error('不能實例化空模型');
}
$model_name = $model . 'Model';
return new $model_name;
}
/**
* 加載類庫
* @param string $lib 類庫名稱
* @param Bool $my 如果FALSE默認加載系統自動加載的類庫,如果爲TRUE則加載非自動加載類庫
* @return object
*/
final protected function load($lib,$auto = TRUE){
if(empty($lib)){
trigger_error('加載類庫名不能爲空');
}elseif($auto === TRUE){
return Application::$_lib[$lib];
}elseif($auto === FALSE){
return Application::newLib($lib);
}
}
/**
* 加載系統配置,默認爲系統配置 $CONFIG['system'][$config]
* @access final protected
* @param string $config 配置名
*/
final protected function config($config){
return Application::$_config[$config];
}
/**
* 加載模板文件
* @access final protect
* @param string $path 模板路徑
* @return string 模板字符串
*/
final protected function showTemplate($path,$data = array()){
$template = $this->load('template');
$template->init($path,$data);
$template->outPut();
}
}
註釋都寫的很清楚了吧,其實很簡單,這裏的加載模板的方法中load了一個系統自動加載的模板類,這個類我們在建立視圖的時候再來講,然後我們再來建核心模型的文件
system/core/model.php
<?php
/**
* 核心模型類
* @copyright Copyright(c) 2011
* @author yuansir <[email protected]/yuansir-web.com>
* @version 1.0
*/
class Model {
protected $db = null;
final public function __construct() {
header('Content-type:text/html;chartset=utf-8');
$this->db = $this->load('mysql');
$config_db = $this->config('db');
$this->db->init(
$config_db['db_host'],
$config_db['db_user'],
$config_db['db_password'],
$config_db['db_database'],
$config_db['db_conn'],
$config_db['db_charset']
); //初始話數據庫類
}
/**
* 根據表前綴獲取表名
* @access final protected
* @param string $table_name 表名
*/
final protected function table($table_name){
$config_db = $this->config('db');
return $config_db['db_table_prefix'].$table_name;
}
/**
* 加載類庫
* @param string $lib 類庫名稱
* @param Bool $my 如果FALSE默認加載系統自動加載的類庫,如果爲TRUE則加載自定義類庫
* @return type
*/
final protected function load($lib,$my = FALSE){
if(empty($lib)){
trigger_error('加載類庫名不能爲空');
}elseif($my === FALSE){
return Application::$_lib[$lib];
}elseif($my === TRUE){
return Application::newLib($lib);
}
}
/**
* 加載系統配置,默認爲系統配置 $CONFIG['system'][$config]
* @access final protected
* @param string $config 配置名
*/
final protected function config($config=''){
return Application::$_config[$config];
}
}
因爲模型基本是處理數據庫的相關內容,所以我們加載了mysql類,這個mysql類就不在這裏寫了,你可以自己根據習慣寫自己的mysql的操作類,如果你想支持其他的數據庫,完全可以自己靈活添加。
核心模型控制器已經有了,其實裏面還可以添加其他你覺得必要的全局函數,這樣我們開始新建一個自己的控制器和模型,來實例運用一下
新建controller/testController.php
<?php
/**
* 測試控制器
* @copyright Copyright(c) 2011
* @author yuansir <[email protected]/yuansir-web.com>
* @version 1.0
*/
class testController extends Controller {
public function __construct() {
parent::__construct();
}
public function index() {
echo "test";
}
public function testDb() {
$modTest = $this->model('test'); //示例化test模型
$databases = $modTest->testDatebases(); //調用test模型中 testDatebases()方法
var_dump($databases);
}
}
testController 繼承我們的核心控制器,其實在以後的每個控制器中都要繼承的,現在我們通過瀏覽器訪問 http://localhost/myapp/index.php?controller=test ,哈哈,可以輸出 test 字符串了
然後我們再新建一個模型model/testModel.php
<?php
/**
* 測試模型
* @copyright Copyright(c) 2011
* @author yuansir <[email protected]/yuansir-web.com>
* @version 1.0
*/
class testModel extends Model{
function testDatabases(){
$this->db->show_databases();
}
}
其實就是定義了一個獲取所有的數據庫的方法,打開瀏覽器訪問 http://localhost/myapp/index.php?controller=test&action=testDb,不管你信不信,反正我的瀏覽器是輸出了所有的數據庫了
現在就差視圖了,其實在覈心控制器的controller.php文件中已經有了一個showTemplate方法,其實就是實現了加載模板類,$data就是我們要傳遞給模板的變量,然後輸出模板
/**
* 加載模板文件
* @access final protect
* @param string $path 模板路徑
* @param array $data 模板變量
* @return string 模板字符串
*/
final protected function showTemplate($path,$data = array()){
$template = $this->load('template');
$template->init($path,$data);
$template->outPut();
}
下面我們來看一下template類
<?php
/**
* 模板類
* @copyright Copyright(c) 2011
* @author yuansir <[email protected]/yuansir-web.com>
* @version 1.0
*/
final class Template {
public $template_name = null;
public $data = array();
public $out_put = null;
public function init($template_name,$data = array()) {
$this->template_name = $template_name;
$this->data = $data;
$this->fetch();
}
/**
* 加載模板文件
* @access public
* @param string $file
*/
public function fetch() {
$view_file = VIEW_PATH . '/' . $this->template_name . '.php';
if (file_exists($view_file)) {
extract($this->data);
ob_start();
include $view_file;
$content = ob_get_contents();
ob_end_clean();
$this->out_put = $content;
} else {
trigger_error('加載 ' . $view_file . ' 模板不存在');
}
}
/**
* 輸出模板
* @access public
* @return string
*/
public function outPut(){
echo $this->out_put;
}
是不是簡單,就是引入你的靜態模版文件,放在緩衝區,然後輸出,其實如果你想靜態化某個模版,那個這個放在緩衝區的$this->out_put就有用了,你可以在裏面添加一個靜態化的方法。
好了,現在我們來在新建一個視圖文件 view/test.php
<html>
<body>
這是<?php echo $test; ?>,呵呵
</body>
<html>
然後修改一些我們的testController.php中的index()
public function index() {
$data['test'] = "yuansir-web.com";
$this->showTemplate('test', $data);
}
再來瀏覽 http://localhost/myapp/index.php?controller=test ,可以輸出 “這是 yuansir-web.com,呵呵”,那麼顯然我們的視圖也完成了。
這樣我們的自己寫PHP的MVC的框架就完成了,再補充一下,有人可能疑惑如果我是想建立前臺後臺的,單一入口怎麼辦呢,其實你要是從頭就看我的這個教程,看下代碼就會發現,其實只要在 controller目錄下新建
一個admin目錄就可以在裏面寫控制器了,比如controller/admin/testController.php 模板引用也是同樣的道理,建立 view/admin/test.php ,然後模板加上路徑就可以了,$this->showTemplate('admin/test', $data);
是不是很簡單,很靈活。
好了,這樣我們《自己動手寫PHP MVC框架》的教程就結束了,你可以模仿自己寫一個,也可以根據自己的思路來寫一個,我教程中的可以自己擴增成一個完善的框架,再次申明一下,教程中的代碼不完善,沒有做過任何基準測試,效率神馬的不考慮,便捷性神馬的看個人,呵呵