介绍
无论你使用 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 - 初始版本