1.什么使MVC框架
MVC是一种软件开发框架,MVC将程序分为三个部分:模型层(M)、视图层(V)和控制层(C),对不同的层进行分层管理和控制,方便程序的修改和扩展
2. 为什么使用MVC框架
在PHP中使用MVC框架,可以实现了分层、分类开发,实现了web的分离,使前端代码与后端分离,某一层的调整,不会对另一层的代码和逻辑造成影响,使用MVC开发框架更加方便程序的扩展,使开发的代码整体更加清晰
3.MVC的含义
M(Model)模型层:提供了对数据库操作和链接的抽象层,主要完成大部分的业务逻辑和数据逻辑的处理
V(view)视图层:主要负责处理结果的显示(渲染),用于与用户进行交互
C(controller)控制层:根据请求进行相关的转发,调用相应的M(模型)对请求进行处理,并将请求结果返回给用户,决定结果的展示形式
浏览器向服务器发起请求,首先由控制器将URL拦截,根据URL中的参数调用相应的模型层,模型层操作数据库,并将操作结果返回给Controller(控制层),控制层调用View层的相关代码对结果进行渲染,View层将渲染结果呈现给用户。
4. MVC目录结构
项目根目录
|-app层
| |-controller
| |-model
| |-view
|-config
5.重定向的作用:
可以直接访问项目中的静态文件,除静态文件外,其他文件都有统一的程序入口,一般为index.php文件,如果使用nginx服务器则可以通过配置php.config中的重定向功能,是对应的字段重定向到指定路径下的指定入口文件,并将参数赋值给REQUEST_URI变量。PHP代码自身并不需要结束符号,不加结束符可以让程序更加安全,因为可以防止在程序的尾部注入
location / {
# 重新向所有非真是存在的请求到index.php
try_files $uri $uri/ /index.php$args;
}
6.入口文件
主要分为两类,一类为项目的入口文件,规定程序访问时的统一入口;另一类是每一模块的入口文件,可以实现对模块的统一管理。入口文件主要实现程序的一些初始化设置,例如一些基础类、配置文件、数据库和模式等。例如:
<?php
define('APP_PATH', __DIR__ . '/');// 应用目录为当前目录
define('APP_DEBUG', true);// 开启调试模式
require(APP_PATH . 'fastphp/Fastphp.php');// 加载框架文件
$config = require(APP_PATH . 'config/config.php');// 加载配置文件
(new fastphp\Fastphp($config))->run();// 实例化框架类通常为程序中的bootstrap.php文件
7.配置文件:
配置文件分为两种,一种为程序的全局配置,例如数据库、默认控制器名和操作名等;另一种为局部配置,这种模块配置只有当访问到相关文件时,才会被对应的加载。全局的配置文件一般为命名为config.php内容如下:
<?php
$config['db']['host'] = 'localhost';// 连接的数据库的域名
$config['db']['username'] = 'root';//用户名
$config['db']['password'] = '123456';//密码
$config['db']['dbname'] = 'project';//使用的数据库名字
$config['defaultController'] = 'Item';//控制器的名字
$config['defaultAction'] = 'index';//通用才操作的名字
return $config;
8.框架类:
入口文件中调用了配置文件初始化了环境配置。定义了加载的控制器以及操作,所以在后续的调用中会根据配置文件的结果实例化框架的内容,实例化时会接受config中的相关参数(例如控制层、模型层和视图层),随后在入口文件中调用run函数进行相关的实例化。run函数的主要功能如下:
1)类自动加载
2)环境检查
3)过滤敏感字符
4)移除全局变量的老用法
5)路由处理
代码:
<?phpnamespace fastphp;
defined('CORE_PATH') or define('CORE_PATH', __DIR__);// 框架根目录
class Fastphp{
protected $config = [];// 配置内容
public function __construct($config)
{
$this->config = $config;
}
public function run()
{// 运行程序
spl_autoload_register(array($this, 'loadClass'));
$this->setReporting();
$this->removeMagicQuotes();
$this->unregisterGlobals();
$this->setDbConfig();
$this->route();
}
public function route()// 路由处理
{
$controllerName = $this->config['defaultController'];
$actionName = $this->config['defaultAction'];
$param = array();
$url = $_SERVER['REQUEST_URI'];
$position = strpos($url, '?');// 清除?之后的内容
$url = $position === false ? $url : substr($url, 0, $position);
$url = trim($url, '/');// 删除前后的“/”
if ($url) {
$urlArray = explode('/', $url);// 使用“/”分割字符串,并保存在数组中
$urlArray = array_filter($urlArray);// 删除空的数组元素
$controllerName = ucfirst($urlArray[0]);// 获取控制器名
array_shift($urlArray);// 获取动作名
$actionName = $urlArray ? $urlArray[0] : $actionName;
$array_shift($urlArray);// 获取URL参数
$param = $urlArray ? $urlArray : array();
}
$controller = 'app\\controllers\\'. $controllerName . 'Controller';
// 判断控制器和操作是否存在
if (!class_exists($controller)) {
exit($controller . '控制器不存在');
}
if (!method_exists($controller, $actionName)) {
exit($actionName . '方法不存在');
}
// 如果控制器和操作名存在,则实例化控制器,
$dispatch = new $controller($controllerName, $actionName);
// $dispatch保存控制器实例化后的对象,我们就可以调用它的方法,
call_user_func_array(array($dispatch, $actionName), $param);
}
public function setReporting()
{// 检测开发环境
if (APP_DEBUG === true) {
error_reporting(E_ALL);
ini_set('display_errors','On');
} else {
error_reporting(E_ALL);
ini_set('display_errors','Off');
ini_set('log_errors', 'On');
}
}
public function stripSlashesDeep($value)
{// 删除敏感字符
$value = is_array($value) ? array_map(array($this, 'stripSlashesDeep'), $value) : stripslashes($value);
return $value;
}
public function removeMagicQuotes()
{// 检测敏感字符并删除
if (get_magic_quotes_gpc()) {
$_GET = isset($_GET) ? $this->stripSlashesDeep($_GET ) : '';
$_POST = isset($_POST) ? $this->stripSlashesDeep($_POST ) : '';
$_COOKIE = isset($_COOKIE) ? $this->stripSlashesDeep($_COOKIE) : '';
$_SESSION = isset($_SESSION) ? $this->stripSlashesDeep($_SESSION) : '';
}
}
// 检测自定义全局变量并移除。因为 register_globals 已经弃用,如果
// 已经弃用的 register_globals 指令被设置为 on,那么局部变量也将
// 在脚本的全局作用域中可用。 例如, $_POST['foo'] 也将以 $foo 的
// 形式存在,这样写是不好的实现,会影响代码中的其他变量。 相关信息,
// 参考: http://php.net/manual/zh/faq.using.php#faq.register-globals
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]);
}
}
}
}
}
public function setDbConfig()
{// 配置数据库信息
if ($this->config['db']) {
define('DB_HOST', $this->config['db']['host']);
define('DB_NAME', $this->config['db']['dbname']);
define('DB_USER', $this->config['db']['username']);
define('DB_PASS', $this->config['db']['password']);
}
}
public function loadClass($className)
{// 自动加载类,可以设置为自动加载一个文件夹下的所以文件
$classMap = $this->classMap();
if (isset($classMap[$className])) {
// 包含内核文件
$file = $classMap[$className];
} elseif (strpos($className, '\\') !== false) {
// 包含应用(application目录)文件
$file = APP_PATH . str_replace('\\', '/', $className) . '.php';
if (!is_file($file)) {
return;
}
} else {
return;
}
include $file; }
protected function classMap()
{// 内核文件命名空间映射关系
return [
'fastphp\base\Controller' => CORE_PATH . '/base/Controller.php',
'fastphp\base\Model' => CORE_PATH . '/base/Model.php',
'fastphp\base\View' => CORE_PATH . '/base/View.php',
'fastphp\db\Db' => CORE_PATH . '/db/Db.php',
'fastphp\db\Sql' => CORE_PATH . '/db/Sql.php',
];
}
}
9.路由方法route():截取URL,并解析出控制器名、方法名和URL参数。
例如:https://my.csdn.net/?ref=toolbar当浏览器访问上面的URL,经过nginx的重写到指定的入口文件,并将参数ref=toolbar传递到调用的文件,route()从全局变量 $_SERVER['REQUEST_URI']中获取到字符串对应调取相应的控制器和方法。
10.基类
在框架中创建MVC基类,包括控制器、模型和视图三个基类,实现程序的调度。Controller 类用assign()方法实现把变量保存到View对象中。这样,在调用$this->render() 后视图文件就能显示这些变量。Model基类涉及到3个类:Model基类本身,它的父类SQL,以及提供数据库连接句柄的Db类。
<?php
namespace fastphp\base;
use fastphp\db\Sql;
class Model extends Sql{
protected $model;
public function __construct()
{
if (!$this->table) {// 获取数据库表名
$this->model = get_class($this);// 获取模型类名称
$this->model = substr($this->model, 0, -5);// 删除类名最后的 Model 字符
$this->table = strtolower($this->model);// 数据库表名与类名一致
}
}
}
<?php
namespace fastphp\db;
use \PDOStatement;
class Sql{
protected $table;// 数据库表名
protected $primary = 'id';// 数据库主键
private $filter = '';// WHERE和ORDER拼装后的条件
private $param = array();// Pdo bindParam()绑定的参数集合
public function where($where = array(), $param = array())
{
if ($where) {
$this->filter .= ' WHERE ';
$this->filter .= implode(' ', $where);
$this->param = $param;
}
return $this;
}
public function order($order = array())
{
if($order) {
$this->filter .= ' ORDER BY ';
$this->filter .= implode(',', $order);
}
return $this;
}
public function fetchAll()// 查询所有
{
$sql = sprintf("select * from `%s` %s", $this->table, $this->filter);
$sth = Db::pdo()->prepare($sql);
$sth = $this->formatParam($sth, $this->param);
$sth->execute();
return $sth->fetchAll();
}
public function fetch()// 查询一条
{
$sql = sprintf("select * from `%s` %s", $this->table, $this->filter);
$sth = Db::pdo()->prepare($sql);
$sth = $this->formatParam($sth, $this->param);
$sth->execute();
return $sth->fetch();
}
public function delete($id)// 根据条件 (id) 删除
{
$sql = sprintf("delete from `%s` where `%s` = :%s", $this->table, $this->primary, $this->primary);
$sth = Db::pdo()->prepare($sql);
$sth = $this->formatParam($sth, [$this->primary => $id]);
$sth->execute();
return $sth->rowCount();
}
public function add($data) // 新增数据
{
$sql = sprintf("insert into `%s` %s", $this->table, $this->formatInsert($data));
$sth = Db::pdo()->prepare($sql);
$sth = $this->formatParam($sth, $data);
$sth = $this->formatParam($sth, $this->param);
$sth->execute();
return $sth->rowCount();
}
public function update($data)// 修改数据
{
$sql = sprintf("update `%s` set %s %s", $this->table, $this->formatUpdate($data), $this->filter);
$sth = Db::pdo()->prepare($sql);
$sth = $this->formatParam($sth, $data);
$sth = $this->formatParam($sth, $this->param);
$sth->execute();
return $sth->rowCount();
}
public function formatParam(PDOStatement $sth, $params = array())
{
foreach ($params as $param => &$value) {
$param = is_int($param) ? $param + 1 : ':' . ltrim($param, ':');
$sth->bindParam($param, $value);
}
return $sth;
}
private function formatInsert($data)
{
$fields = array();
$names = array();
foreach ($data as $key => $value) {
$fields[] = sprintf("`%s`", $key);
$names[] = sprintf(":%s", $key);
}
$field = implode(',', $fields);
$name = implode(',', $names);
return sprintf("(%s) values (%s)", $field, $name);
}
private function formatUpdate($data)
{
$fields = array();
foreach ($data as $key => $value) {
$fields[] = sprintf("`%s` = :%s", $key, $key);
}
return implode(',', $fields);
}
}
Sql类里面有用到Db:pdo()方法,这是我们创建的Db类,它提供一个PDO单例。在fastphp/db/目录下创建Db.php文件,内容:
<?php
namespace fastphp\db;
use PDO;use PDOException;
class Db{
private static $pdo = null;
public static function pdo()
{
if (self::$pdo !== null) {
return self::$pdo;
}
try {
$dsn = sprintf('mysql:host=%s;dbname=%s;charset=utf8', DB_HOST, DB_NAME);
$option = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
return self::$pdo = new PDO($dsn, DB_USER, DB_PASS, $option);
} catch (PDOException $e) {
exit($e->getMessage());
}
}
}