前言
MVC
模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。MVC
的目的是实现一种动态的程序设计,便于后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部份分离的同时,也赋予了各个基本部分应有的功能。MVC
架构对于 PHP
开发者来说应该都不陌生,我们在日常的项目开发中所使用到的框架,比如:ThinkPHP3.2
, Laravel
, TP5
等,这些框架都是用的 MVC
三层模式。
MVC
各部分的职能:
1、模型Model – 管理大部分的业务逻辑和所有的数据库逻辑。模型提供了连接和操作数据库的抽象层。
2、控制器Controller - 负责响应用户请求、准备数据,以及决定如何展示数据。
3、视图View – 负责渲染数据,通过HTML方式呈现给用户。
解析:Web MVC
流程:
1、Controller获取用户发出的请求;
2、Controller调用Model完成状态的读写操作;
3、Controller把数据传递给View;
4、View渲染最终结果并呈献给用户。
目录准备
开发一款自己的 MVC
框架对开发者的基础知识的掌握要求是非常高的,比如:PHP
内置的各种函数的使用,常量的定义,文件的处理,面向对象的基础等。在开始开发前,让我们先来把项目建立好,假设我们建立的项目为 whphpCMS
,那么接下来的第一步就是把目录结构先设置好。
下面就具体说说每个目录的作用:
1、app – 应用目录,即我们主要写代码的位置
2、admin - 后台模块,里面对应的是其MVC架构
3、home - 前台模块,里面对应的是其MVC架构
4、common/kernel - 框架核心文件,比如:模块路径、控制器路径,自动加载机制等。
5、common/controller - 公共控制器(基类)
6、common/model - 公共模型(基类)
7、config - 公共配置文件、数据库配置、全局辅助函数等
8、public - 公共文件、静态文件等
9、vendor - 第三方类库文件
10、admin.php - 项目后台入口
11、index.php - 项目前台入口
正式开发(后台)
1、本地创建好项目后,先来一个后台入口,任何框架都有一个入口文件,我们定义为admin.php
define("APP", "/admin"); //设置当前应用的目录
require('./common/kernel.php'); //加载框架的入口文件
2、在 common
文件夹中,创建 kernel.php
文件,里面设置模块名、控制器名和模型常量。参考代码:
<?php
/*********************************************************************************
* kernel.php 框架入口文件,所有脚本都是从这个文件开始执行,主要是一些全局设置。 *
* *******************************************************************************
* 许可声明:专为《长乐未央教育》学员提供的“学习型”超轻量级php框架。*
* *******************************************************************************
* 版权所有 (C) 2013-2019 武汉长乐未央网络科技有限公司,并保留所有权利。 *
* 网站地址: https://itfun.tv (长乐教育) *
* *******************************************************************************
* $Author: 黄栋进 (244500972@qq.com) $ *
* $Date: 2019-09-10 10:00:00 $ *
* ******************************************************************************/
header("Content-Type:text/html;charset=utf-8"); //设置系统的输出字符为utf-8
date_default_timezone_set("PRC"); //设置时区(中国)
defined("SITE_PATH") or define("SITE_PATH", getcwd()); //站点路径
defined("APP_PATH") or define("APP_PATH", getcwd() . '/app'); //应用路径
define('MODULES_PATH', APP_PATH . APP); //模块路径
define('CONTROLLERS_PATH', APP_PATH . APP . '/controllers'); //控制器路径
define('MODELS_PATH', APP_PATH . APP . '/models'); //模型路径
define('VIEWS_PATH', APP_PATH . APP . '/views'); //视图路径
//当前访问的模块、控制器、方法
$m = isset($_GET['m']) ? $_GET['m'] : APP;
$c = isset($_GET['c']) ? $_GET['c'] : 'index';
$a = isset($_GET['a']) ? $_GET['a'] : 'index';
define('MODULE_NAME', $m);
define('CONTROLLER_NAME', $c);
define('ACTION_NAME', $a);
//自动加载机制
function autoload($class_name)
{
//判断类名是否是外层的公共类
switch ($class_name) {
case 'Smarty':
$file = SITE_PATH . '/vendor/smarty/libs/Smarty.class.php';
break;
case 'Controller':
$file = SITE_PATH . '/common/controller.class.php';
break;
case 'Model':
$file = SITE_PATH . '/common/model.class.php';
break;
default:
//判断类名是否是模块里面对应的类
$type = substr($class_name, -10) == 'Controller' ? 'controller' : 'model';
if ($type == 'controller') {
$controller_name = '/' . strtolower(substr($class_name, 0, -10)) . '.class.php';
$file = CONTROLLERS_PATH . $controller_name;
}
if ($type == 'model') {
$name = strtolower(substr($class_name, 0, -5));
$file = MODELS_PATH . "/{$name}.class.php";
}
}
// 如果类名存在,加载该类名
if (file_exists($file)) {
require $file;
}
}
spl_autoload_register("autoload");
//autoload('UserModel');
//定义类名
$controller_name = ucfirst($c) . 'Controller';
$controller = new $controller_name;
$controller->$a();
/***
* 打印数组
* @param $array
*/
function dump($array)
{
echo "<pre>";
print_r($array);
echo "</pre>";
}
/**
* 读取配置文件
* @param $key
* @return mixed
*/
function C($key)
{
$config = require SITE_PATH . '/config/database.php';
return $config["$key"];
}
/*
* 实例化公共模型
*/
function M($table)
{
return new Model($table);
}
/*
* 实例化自定义的模型
*/
function D($table)
{
$model = ucfirst($table) . 'Model';
return new $model($table);
}
3、定义公共模型,在 common
文件夹下,创建 model.class.php
文件,里面设置数据库的连接,并自定义查询函数。
<?php
class Model
{
public $db;
private $table;
public function __construct($table)
{
$this->db_config();
$this->table = $table;
}
/**
* 数据库连接
*/
private function db_config()
{
$this->db = new mysqli(C('db_host'), C('db_user'), C('db_pwd'), C('db_name'));
if (mysqli_connect_errno()) {
exit("连接失败: %s<br>" . mysqli_connect_error());
}
$this->db->query("set names utf8");
}
/**
* 查询单条数据
* @param $sql
* @return array
*/
function one($sql)
{
$result = $this->db->query($sql);
return $result->fetch_assoc();
}
/**
* 查询多条记录
* @param $sql
* @return array
*/
function all($sql)
{
$array = [];
$result = $this->db->query($sql);
while ($row = $result->fetch_assoc()) {
$array[] = $row;
}
return $array;
}
}
解析:其中的 C
函数是读取数据库的各项配置。
4、接下来创建数据库配置文件,在 config
文件夹中,创建 database.php
文件,里面写上如下代码:
<?php
return [
'db_host' => '127.0.0.1', // 服务器地址
'db_name' => 'chat', // 数据库名
'db_user' => 'root', // 用户名
'db_pwd' => 'root', // 密码
'DB_CHARSET' => 'utf8', // 字符集
];
接下来在 vendor
文件夹中引入 smarty
类。
5、在 common
文件夹里面创建公共控制器 controller.class.php
,对 smarty
做基础配置,然后定义跳转和重写视图加载方法,具体代码如下:
<?php
class Controller extends Smarty
{
function __construct()
{
parent::__construct();
$this->smarty_config();
}
/**
* smarty 初始化
*/
private function smarty_config()
{
$view_path = VIEWS_PATH . '/';
$this->setTemplateDir($view_path);
$this->setCompileDir(SITE_PATH . '/runtime/templates_c/');
$this->left_delimiter = '{{';
$this->right_delimiter = '}}';
}
/**
* 跳转,并返回信息
* @param string $url
* @param string $info
*/
public function redirect($info = '', $url = '')
{
if ($info) {
echo "<script>alert('" . $info . "')</script>";
}
if ($url) {
echo "<script>location.href='" . $url . "';</script>";
} else {
echo "<script>location.href=document.referrer;</script>";
}
}
/**
* 重写display方法,现在可以不传模板名称了
* @param null $template
* @param null $cache_id
* @param null $compile_id
* @param null $parent
*/
public function display($template = null, $cache_id = null, $compile_id = null, $parent = null)
{
$template = $template ? $template . '.html' : APP_PATH . MODULE_NAME . '/views' . '/' . CONTROLLER_NAME . '/' . ACTION_NAME . '.html';
parent::display($template, $cache_id = null, $compile_id = null, $parent = null);
}
}
解析:此处我加了一个 runtime
文件夹,用于存储模板缓存文件,那么,项目的目录中需要新增一个 runtime
文件夹。
6、测试
在 app\admin\controllers
里面,新建控制器index.class.php
, 里面输出:
<?php
class IndexController extends Controller
{
public function index()
{
echo "this is index_controller";
}
}
终端执行启动命令:php -S localhost:8000
,浏览器访问 http://localhost:8000/admin.php
接下来,我们来测试一点查询,还是在当前控制器的 index
方法中写上如下代码:
public function index()
{
$Article = M('article');
$articles = $Article->all("select * from article");
dump($articles);exit;
}
刷新浏览器,你会看到如下结果:
如果你想访问其他控制器对应的方法,比如想访问 user.class.php
控制器里面的 login
方法,可以这么访问:http://localhost:8000/admin.php?c=user&a=login
如果你想加载静态页面,直接使用 $this->display();
即可。
至此,后台配置已完成!
正式开发(前台)
1、在项目的根目录下创建 index.php
文件,表示前台入口,里面添加代码:
define("APP", "/home"); //设置当前应用的目录
require('./common/kernel.php'); //加载框架的入口文件
2、在 home\controllers
里面创建控制器 index.class.php
,里面添加代码:
<?php
class IndexController extends Controller
{
function index()
{
echo "这是前台首页";
}
}
如图所示:
如果你想访问其他控制器对应的方法,比如想访问 user.class.php
控制器里面的 login
方法,可以这么访问:http://localhost:8000/index.php?c=user&a=login
工厂模式和单例模式的使用
1、使用工厂模式获取实例
在 kernel.php
文件中修改实例化类名的方法。在 common
文件夹中,新增一个类,取名 factory.class.php
,里面写上如下代码:
<?php
class Factory
{
/***
* 使用工厂类获取实例
* @param $c
* @return mixed
*/
public static function build($c)
{
$controller_name = ucfirst($c) . 'Controller';
return new $controller_name;
}
}
然后在 kernel.php
文件中的 switch
加入如下代码:
case 'Factory':
$file = SITE_PATH . '/common/factory.class.php';
break;
****************************************************
//实例化类名,并执行访问方法
$controller = Factory::build($c);
$controller->$a();
2、使用单例模式封装数据库连接
在 common
文件夹中,新增一个类,取名 conn.class.php
,里面写上如下代码:
<?php
class Conn
{
//静态变量要设置为私有,防止被修改
private static $instance;
private static $db;
private function __construct()
{
// echo 21313;
$this->db_config();
}
//克隆函数声明为私有,防止克隆对象
private function __clone()
{
}
//提供一个创建唯一实例的接口
public static function getInstance()
{
// instanceof 用于确定一个 PHP 变量是否属于某一类 class 的实例:
if (!(self::$instance instanceof self)) {
self::$instance = new self();
}
return self::$db;
}
/**
* 数据库连接
*/
private function db_config()
{
self::$db = new mysqli(C('db_host'), C('db_user'), C('db_pwd'), C('db_name'));
if (mysqli_connect_errno()) {
exit("连接失败: %s<br>" . mysqli_connect_error());
}
self::$db->query("set names utf8");
}
}
然后在 kernel.php
文件中的 switch
加入如下代码:
case 'Conn':
$file = SITE_PATH . '/common/conn.class.php';
break;
修改公共模型 model.class.php
代码如下:
<?php
class Model
{
protected $db;
private $table;
function __construct($table)
{
$this->db = Conn::getInstance();
$this->table = $table;
}
/**
* 查询单条数据
* @param $sql
* @return array
*/
function one($sql)
{
$result = $this->db->query($sql);
return $result->fetch_assoc();
}
/**
* 查询多条记录
* @param $sql
* @return array
*/
function all($sql)
{
$array = [];
$result = $this->db->query($sql);
while ($row = $result->fetch_assoc()) {
$array[] = $row;
}
return $array;
}
}
3、实例化自定义模型
在 kernel.php
中添加代码:
/*
* 实例化自定义的模型
*/
function D($table)
{
$model = ucfirst($table) . 'Model';
// echo $model;
return new $model($table);
}
在 admin/models
文件夹中创建模型 article.class.php
。里面添加如下代码:
class ArticleModel extends Model
{
function run()
{
echo 777;
}
}
在 admin/controllers/index.class.php
中写入测试代码,刷新浏览器看是否能得到 777
$Article = D('article');
$Article->run();
封装模型链式方法
修改 model.class.php
代码如下:
<?php
class Model
{
protected $db;
private $field = '*'; //查询所有字段
private $where = '1=1';
private $order = 'id asc';
private $limit = '0, 1000';
private $table;
function __construct($table)
{
$this->db = Conn::getInstance();
$this->table = $table;
}
/***
* 要查询的字段,如果不传值就查所有字段
* @param null $field
* @return $this
*/
public function field($field = null)
{
$this->field = $field;
return $this;
}
/***
* 定义where查询条件
* @param null $where
* @return $this
*/
public function where($where = null)
{
$sql = [];
// 如果控制器以数组形式传多条件,默认连接用 and
if (is_array($where)) {
$_logic = ' and ';
// 如果控制器传了_logic,就拼接条件,但是最后一个条件是没有and的,所以要释放掉
if (isset($where['_logic'])) {
$_logic = ' ' . $where['_logic'] . ' ';
unset($where['_logic']);
}
// 如果控制器传的是数组,对数组进行循环,拼接SQL返回
foreach ($where as $key => $value) {
$sql[] = "{$key} = {$value}";
}
$sql = implode($_logic, $sql);
// echo $sql;
$this->where = $sql;
return $this;
}
$this->where = $where;
return $this;
}
/***
* 查询多条
* @return array
*/
public function select()
{
$sql = "select {$this->field} from {$this->table} where {$this->where} order by {$this->order} limit {$this->limit}";
// echo $sql;exit;
$array = [];
$result = $this->db->query($sql);
while ($row = $result->fetch_assoc()) {
$array[] = $row;
}
return $array;
}
/***
* 查询单条
*/
public function find()
{
$sql = "select {$this->field} from {$this->table} where {$this->where} order by {$this->order} limit {$this->limit}";
$result = $this->db->query($sql);
$row = $result->fetch_assoc();
return $row;
}
/**
* 排序
* @param $order
* @return $this
*/
public function order($order = null)
{
$this->order = $order;
return $this;
}
/**
* 列偏移
* @param $limit
* @return $this
*/
public function limit($limit)
{
$this->limit = $limit;
return $this;
}
}
在后台首页控制器里面的 index
方法中写入如下代码:
$Article = M('article');
$data['id'] = 1;
$articles = $Article->where($data)->select();
dump($articles);
刷新浏览器,看到你想要的结果即可。