在 1小时内,编写自己的PHP MVC框架代码

分享于 

28分钟阅读

Web开发

  繁體 雙語

介绍

无论你使用 Java。C#。PHP。iOS项目,MVC架构 Pattern 几乎都在任何地方。 这可能不是 100%精确的,但是PHP社区拥有最多的MVC框架。 今天你可以能使用 Zend,明天在另一个项目中,你可以能需要更改为to或者 to。 如果你是新的MVC框架,你可能会在看到框架代码的源代码,是很复杂的。 因这里,了解MVC框架的核心设计方法很关键,否则你可能会觉得在使用新框架获得新框架时必须了解另一个框架。


理解MVC的最好方法是从头开始编写自己的MVC框架 ! 在本系列文章中,我将向你展示如何编码一个,以便你可以了解为什么certains事件发生在框架中。

MVC体系结构 Pattern

M: 模型

V: 视图

C: 控制器

MVC的核心概念是将业务逻辑与显示( 视图部分) 分离开来。 首先让我解释一下HTTP请求 & HTTP响应的整个工作流。 例如我们有一个商业网站,我们想添加一个特定的产品。 一个非常简单的URL如下所示:

http://bestshop.com/index.php?p=admin&c=goods&a=add

http://bestshop.com 是域 NAME 或者基本 URL ;

p=admin表示平台的管理面板,或者系统的后端站点。 我们还有一个前端站点,即 public 到世界( 在本例中,它是 p=public )


c=goods& a=add表示这里URL请求'商品添加'控制器操作方法。

前端控制器设计 Pattern

上面 示例中的index.php 是什么? 这个文件在php框架的MVC中称为'前端控制器'。 这个 NAME 通常是 index.php的,但是你可以对这个文件中的某个函数进行 NAME,不管在URL中,它都是一个独立的入口点,它会首先进入这个 index.php 文件。 为什么魔术怎么会发生? 前面的控制器设计 Pattern 是用PHP的服务器配置文件的。分发来实现的。 在这个文件中,我们可以告诉Apache服务器使用重写 MODULE 将所有请求重定向到 index.php。 你可以编写类似于以下内容的代码:


<IfModule mod_rewrite.c>



 Options +FollowSymLinks



 RewriteEngine on



 # Send request via index.php



 RewriteCond %{REQUEST_FILENAME}!-f



 RewriteCond %{REQUEST_FILENAME}!-d



 RewriteRule ^(.*)$ index.php/$1 [L]



</IfModule>



这个配置文件非常强大,当你改变它时,你不需要重启 Apache。 如果更改Apache具有它的他配置文件,则需要重新启动它,因为它的他的Apache配置文件将在启动时重新启动( Apache主要是用C 语言编写的)。 但对于。htaccess分发配置文件,任何更改都不需要重新启动。


简而言之,index.php 也会对框架做适当的初始化,它将请求路由到适当的控制器( 上面 中的goodsController示例),并将操作方法放到控制器类中。

我们的MVC架构

让我们创建框架结构。

应用目录是应用程序的网络目录;

框架目录是针对框架本身的;

public 目录是存储所有 public static 资源,如 html,css和js文件。

index.php 是'入口点'文件,前端控制器。

在应用程序文件夹中,我们创建这些子文件夹:

config - 存储应用程序文件的配置

控制器- 这适用于应用类的所有控制器

Model - 这适用于所有应用类模型

View - 适用于应用程序类的所有视图

在 application/控制卡folder中,我们将为前端和后端平台创建两个文件夹:

在视图文件夹中,前端和后端的内容相同:

如你所见,在应用程序文件夹中,我们在控制器和视图文件夹中创建了前端和后端子文件夹,因为我们的应用程序有前端站点和后端站点。 但是为什么我们在模型文件夹中没有做同样的事情?

嗯,这里的原因是,通常是针对一个网络应用程序:

前后端可以是两个不同的'网站',但在同一数据库中是 CRUD,这就是为什么内部用户更新了产品的价格,即在前端和前台共享相同的数据库/表格。

这就是为什么后端和前端可以共享一组模型类,这就是为什么我们没有在模型文件夹中创建单独的文件夹。

现在让我们进入框架目录,使用框架的对这个文件夹进行一些框架,如'symfony'。 在这里文件夹中,我们首先创建这些子文件夹:

core - 它将存储框架类的核心

数据库- 数据库相关类,如数据库驱动程序类

帮助器- 帮助/辅助函数

库- 类库

现在转移到 public 文件夹,我们创建这些子文件夹:

css - 用于css文件

图像- 用于图像文件

js - 用于javascript文件

上传- 上传上传的文件,如上传的图片

现在,这是我们的小型MVC架构框架。


框架类的核心

在框架/核心文件夹下,我们将在框架/核心文件夹中创建第一个框架类- Framework.class.php


// framework/core/Framework.class.php



class Framework {



 public static function run() {



 echo "run()";



 }



我们创建了一个名为 run()的static 函数。 现在在 index.php 中测试它:


<?php



require "framework/core/Framework.class.php";



Framework::run();



你可以在浏览器( 这里跳过了如何配置虚拟主机) 中看到结果。 通常这个 static 函数叫做 run() 或者 bootstrap()。 在这里函数中,我们可以在这里执行 3个主要操作,如下所示:


class Framework {



 public static function run() {



// echo"run()";



 self::init();



 self::autoload();



 self::dispatch();



 }



 private static function init() {



 }



 private static function autoload() {



 }



 private static function dispatch() {



 }



}



初始化

下面是 init() 方法的代码:


// Initialization



private static function init() {



 // Define path constants



 define("DS", DIRECTORY_SEPARATOR);



 define("ROOT", getcwd(). DS);



 define("APP_PATH", ROOT. 'application'. DS);



 define("FRAMEWORK_PATH", ROOT. "framework". DS);



 define("PUBLIC_PATH", ROOT. "public". DS);



 define("CONFIG_PATH", APP_PATH. "config". DS);



 define("CONTROLLER_PATH", APP_PATH. "controllers". DS);



 define("MODEL_PATH", APP_PATH. "models". DS);



 define("VIEW_PATH", APP_PATH. "views". DS);



 define("CORE_PATH", FRAMEWORK_PATH. "core". DS);



 define('DB_PATH', FRAMEWORK_PATH. "database". DS);



 define("LIB_PATH", FRAMEWORK_PATH. "libraries". DS);



 define("HELPER_PATH", FRAMEWORK_PATH. "helpers". DS);



 define("UPLOAD_PATH", PUBLIC_PATH. "uploads". DS);



 // Define platform, controller, action, for example:



 // index.php?p=admin&c=Goods&a=add



 define("PLATFORM", isset($_REQUEST['p'])? $_REQUEST['p'] : 'home');



 define("CONTROLLER", isset($_REQUEST['c'])? $_REQUEST['c'] : 'Index');



 define("ACTION", isset($_REQUEST['a'])? $_REQUEST['a'] : 'index');



 define("CURR_CONTROLLER_PATH", CONTROLLER_PATH. PLATFORM. DS);



 define("CURR_VIEW_PATH", VIEW_PATH. PLATFORM. DS);



 // Load core classes



 require CORE_PATH. "Controller.class.php";



 require CORE_PATH. "Loader.class.php";



 require DB_PATH. "Mysql.class.php";



 require CORE_PATH. "Model.class.php";



 // Load configuration file



 $GLOBALS['config'] = include CONFIG_PATH. "config.php";



 // Start session



 session_start();



}




从注释中可以看出每个步骤的用途。

自动装入

我们不想手动代码包含或者需要在项目中所有脚本中需要的类文件,这就是 PHP MVC框架。 例如在Symfony中,如果你将自己的类文件放在'lib'文件夹下,那么它将自动加载。 魔术不,没有魔法。 让我们在迷你框架中实现自动加载功能。

这里我们需要使用一个名为spl_autoload_register的PHP


// Autoloading



private static function autoload(){



 spl_autoload_register(array(__CLASS__,'load'));



}



// Define a custom load method



private static function load($classname){



 // Here simply autoload app's controller and model classes



 if (substr($classname, -10) == "Controller"){



 // Controller



 require_once CURR_CONTROLLER_PATH. "$classname.class.php";



 } elseif (substr($classname, -5) == "Model"){



 // Model



 require_once MODEL_PATH. "$classname.class.php";



 }



}




每个框架都有一个 NAME 转换,我们的不例外。 对于控制器类,它应该是 xxxController.class. php,对于模型类,它应该是 xxxModel.class. php。 为什么你遇到一个新的框架,你必须遵循它的命名惯例? 自动自动装弹是。

路由/调度


// Routing and dispatching



private static function dispatch(){



 // Instantiate the controller class and call its action method



 $controller_name = CONTROLLER. "Controller";



 $action_name = ACTION. "Action";



 $controller = new $controller_name;



 $controller->$action_name();



}




在这个步骤中,index.php 将请求分派给适当的Controller::Action() 方法。 这里的例子很简单。

基本控制器类

框架类的核心中始终有一个基本控制器类( 或者几个数)。 例如在Symfony中它被称为 sfActions ;在iOS中它叫做 UIViewController。 在这里,我们只需要 NAME 控制器,文件 NAME 是 Controller.class. php



<?php



// Base Controller



class Controller{



 // Base Controller has a property called $loader, it is an instance of Loader class(introduced later)



 protected $loader;



 public function __construct(){



 $this->loader = new Loader();



 }



 public function redirect($url,$message,$wait = 0){



 if ($wait == 0){



 header("Location:$url");



 } else {



 include CURR_VIEW_PATH. "message.html";



 }



 exit;



 }



}



基本控制器有一个名为 $loader,的属性,它是装载器类( 稍后介绍)的一个实例。 请注意,'它是装载程序类的一个实例'--精确说明,$this-> 加载器是引用/指向负载类实例的引用变量。 我们在这里并没有更多的讨论,但这实际上是一个非常重要的概念。 我遇到一些PHP开发人员,他们相信以下语句:





$this->loader = new Loader();



$this-> 加载程序是一个对象。 不,它是一个引用。这个词从Java开始,在Java之前,它被称为 C++ 或者 Objective C 中的指针。 引用是封装的指针类型。 例如在 iOS(Objective-C) 中,我们使用以下方法创建一个对象:





UIButton *btn = [UIButton alloc] init];




装入类

在 framework.class. php中,我们已经实现了应用程序和模型类'自动。 但是如何在框架目录中加载类? 这里我们可以创建一个名为Loader的新类,它将被用来加载框架和函数类。 当我们需要加载一类框架时,只需调用这个类的加载器。


class Loader{



 // Load library classes



 public function library($lib){



 include LIB_PATH. "$lib.class.php";



 }



 // loader helper functions. Naming conversion is xxx_helper.php;



 public function helper($helper){



 include HELPER_PATH. "{$helper}_helper.php";



 }



}




实现模型

通过创建两个类文件,我们将以最简单的方式实现模型:

Mysql.class.php - 这里类位于框架/数据库之下,它是封装数据库连接和一些基本SQL查询方法。

Model.class.php - 这是基本模型类,它包含所有类型的CRUD的方法



<?php



/**



*================================================================



*framework/database/Mysql.class.php



*Database operation class



*================================================================



*/



class Mysql{



 protected $conn = false; //DB connection resources



 protected $sql; //sql statement



 /**



 * Constructor, to connect to database, select database and set charset



 * @param $config string configuration array



 */



 public function __construct($config = array()){



 $host = isset($config['host'])? $config['host'] : 'localhost';



 $user = isset($config['user'])? $config['user'] : 'root';



 $password = isset($config['password'])? $config['password'] : '';



 $dbname = isset($config['dbname'])? $config['dbname'] : '';



 $port = isset($config['port'])? $config['port'] : '3306';



 $charset = isset($config['charset'])? $config['charset'] : '3306';



 $this->conn = mysql_connect("$host:$port",$user,$password) or die('Database connection error');



 mysql_select_db($dbname) or die('Database selection error');



 $this->setChar($charset);



 }



 /**



 * Set charset



 * @access private



 * @param $charset string charset



 */



 private function setChar($charest){



 $sql = 'set names '.$charest;



 $this->query($sql);



 }



 /**



 * Execute SQL statement



 * @access public



 * @param $sql string SQL query statement



 * @return $result,if succeed, return resrouces; if fail return error message and exit



 */



 public function query($sql){ 



 $this->sql = $sql;



 // Write SQL statement into log



 $str = $sql. " [". date("Y-m-d H:i:s"). "]". PHP_EOL;



 file_put_contents("log.txt", $str,FILE_APPEND);



 $result = mysql_query($this->sql,$this->conn);



 if (! $result) {



 die($this->errno().':'.$this->error().'<br/>Error SQL statement is '.$this->sql.'<br/>');



 }



 return $result;



 }



 /**



 * Get the first column of the first record



 * @access public



 * @param $sql string SQL query statement



 * @return return the value of this column



 */



 public function getOne($sql){



 $result = $this->query($sql);



 $row = mysql_fetch_row($result);



 if ($row) {



 return $row[0];



 } else {



 return false;



 }



 }



 /**



 * Get one record



 * @access public



 * @param $sql SQL query statement



 * @return array associative array



 */



 public function getRow($sql){



 if ($result = $this->query($sql)) {



 $row = mysql_fetch_assoc($result);



 return $row;



 } else {



 return false;



 }



 }



 /**



 * Get all records



 * @access public



 * @param $sql SQL query statement



 * @return $list an 2D array containing all result records



 */



 public function getAll($sql){



 $result = $this->query($sql);



 $list = array();



 while ($row = mysql_fetch_assoc($result)){



 $list[] = $row;



 }



 return $list;



 }



 /**



 * Get the value of a column



 * @access public



 * @param $sql string SQL query statement



 * @return $list array an array of the value of this column



 */



 public function getCol($sql){



 $result = $this->query($sql);



 $list = array();



 while ($row = mysql_fetch_row($result)) {



 $list[] = $row[0];



 }



 return $list;



 }



 /**



 * Get last insert id



 */



 public function getInsertId(){



 return mysql_insert_id($this->conn);



 }



 /**



 * Get error number



 * @access private



 * @return error number



 */



 public function errno(){



 return mysql_errno($this->conn);



 }



 /**



 * Get error message



 * @access private



 * @return error message



 */



 public function error(){



 return mysql_error($this->conn);



 }



}



下面是 Model.class. php


<?php



// framework/core/Model.class.php



// Base Model Class



class Model{



 protected $db; //database connection object



 protected $table; //table name



 protected $fields = array(); //fields list



 public function __construct($table){



 $dbconfig['host'] = $GLOBALS['config']['host'];



 $dbconfig['user'] = $GLOBALS['config']['user'];



 $dbconfig['password'] = $GLOBALS['config']['password'];



 $dbconfig['dbname'] = $GLOBALS['config']['dbname'];



 $dbconfig['port'] = $GLOBALS['config']['port'];



 $dbconfig['charset'] = $GLOBALS['config']['charset'];



 $this->db = new Mysql($dbconfig);



 $this->table = $GLOBALS['config']['prefix']. $table;



 $this->getFields();



 }



 /**



 * Get the list of table fields



 *



 */



 private function getFields(){



 $sql = "DESC". $this->table;



 $result = $this->db->getAll($sql);



 foreach ($result as $v) {



 $this->fields[] = $v['Field'];



 if ($v['Key'] == 'PRI') {



 // If there is PK, save it in $pk



 $pk = $v['Field'];



 }



 }



 // If there is PK, add it into fields list



 if (isset($pk)) {



 $this->fields['pk'] = $pk;



 }



 }



 /**



 * Insert records



 * @access public



 * @param $list array associative array



 * @return mixed If succeed return inserted record id, else return false



 */



 public function insert($list){



 $field_list = ''; //field list string



 $value_list = ''; //value list string



 foreach ($list as $k => $v) {



 if (in_array($k, $this->fields)) {



 $field_list. = "`".$k."`". ',';



 $value_list. = "'".$v."'". ',';



 }



 }



 // Trim the comma on the right



 $field_list = rtrim($field_list,',');



 $value_list = rtrim($value_list,',');



 // Construct sql statement



 $sql = "INSERT INTO `{$this->table}` ({$field_list}) VALUES ($value_list)";



 if ($this->db->query($sql)) {



 // Insert succeed, return the last record's id



 return $this->db->getInsertId();



 //return true;



 } else {



 // Insert fail, return false



 return false;



 }



 }



 /**



 * Update records



 * @access public



 * @param $list array associative array needs to be updated



 * @return mixed If succeed return the count of affected rows, else return false



 */



 public function update($list){



 $uplist = ''; //update fields



 $where = 0; //update condition, default is 0



 foreach ($list as $k => $v) {



 if (in_array($k, $this->fields)) {



 if ($k == $this->fields['pk']) {



 // If it's PK, construct where condition



 $where = "`$k`=$v";



 } else {



 // If not PK, construct update list



 $uplist. = "`$k`='$v'".",";



 }



 }



 }



 // Trim comma on the right of update list



 $uplist = rtrim($uplist,',');



 // Construct SQL statement



 $sql = "UPDATE `{$this->table}` SET {$uplist} WHERE {$where}";



 if ($this->db->query($sql)) {



 // If succeed, return the count of affected rows



 if ($rows = mysql_affected_rows()) {



 // Has count of affected rows 



 return $rows;



 } else {



 // No count of affected rows, hence no update operation



 return false;



 } 



 } else {



 // If fail, return false



 return false;



 }



 }



 /**



 * Delete records



 * @access public



 * @param $pk mixed could be an int or an array



 * @return mixed If succeed, return the count of deleted records, if fail, return false



 */



 public function delete($pk){



 $where = 0; //condition string



 //Check if $pk is a single value or array, and construct where condition accordingly



 if (is_array($pk)) {



 // array



 $where = "`{$this->fields['pk']}` in (".implode(',', $pk).")";



 } else {



 // single value



 $where = "`{$this->fields['pk']}`=$pk";



 }



 // Construct SQL statement



 $sql = "DELETE FROM `{$this->table}` WHERE $where";



 if ($this->db->query($sql)) {



 // If succeed, return the count of affected rows



 if ($rows = mysql_affected_rows()) {



 // Has count of affected rows



 return $rows;



 } else {



 // No count of affected rows, hence no delete operation



 return false;



 } 



 } else {



 // If fail, return false



 return false;



 }



 }



 /**



 * Get info based on PK



 * @param $pk int Primary Key



 * @return array an array of single record



 */



 public function selectByPk($pk){



 $sql = "select * from `{$this->table}` where `{$this->fields['pk']}`=$pk";



 return $this->db->getRow($sql);



 }



 /**



 * Get the count of all records



 *



 */



 public function total(){



 $sql = "select count(*) from {$this->table}";



 return $this->db->getOne($sql);



 }



 /**



 * Get info of pagination



 * @param $offset int offset value



 * @param $limit int number of records of each fetch



 * @param $where string where condition,default is empty



 */



 public function pageRows($offset, $limit,$where = ''){



 if (empty($where)){



 $sql = "select * from {$this->table} limit $offset, $limit";



 } else {



 $sql = "select * from {$this->table} where $where limit $offset, $limit";



 }



 return $this->db->getAll($sql);



 }



}



现在我们可以在应用程序文件夹中创建一个用户模型类,这是针对数据库中的用户 table的。 代码将如下所示:


<?php



// application/models/UserModel.class.php



class UserModel extends Model{



 public function getUsers(){



 $sql = "select * from $this->table";



 $users = $this->db->getAll($sql);



 return $users;



 }



}



我们的应用程序indexController的后端可能如下所示:


<?php



// application/controllers/admin/IndexController.class.php



class IndexController extends BaseController{



 public function mainAction(){



 include CURR_VIEW_PATH. "main.html";



 // Load Captcha class



 $this->loader->library("Captcha");



 $captcha = new Captcha;



 $captcha->hello();



 $userModel = new UserModel("user");



 $users = $userModel->getUsers();



 }



 public function indexAction(){



 $userModel = new UserModel("user");



 $users = $userModel->getUsers();



 // Load View template



 include CURR_VIEW_PATH. "index.html";



 }



 public function menuAction(){



 include CURR_VIEW_PATH. "menu.html";



 }



 public function dragAction(){



 include CURR_VIEW_PATH. "drag.html";



 }



 public function topAction(){



 include CURR_VIEW_PATH. "top.html";



 }



}



到目前为止,我们的后端控制器的应用程序索引是工作的,它与模型通信并将结果变量传递给View模板。

这是一个简单的MVC框架简介,希望它能澄清MVC框架中一些基本概念。

历史记录

Feb 25,2016 - 初始版本


  framework  cod  PHP  HOUR  
相关文章