MVC学习笔记
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.入口文件
主要分为两类,一类为项目的入口文件,规定程序访问时的统一入口;另一类是每一模块的入口文件,可以实现对模块的统一管理。入口文件主要实现程序的一些初始化设置,例如一些基础类、配置文件、数据库和模式等。例如:
run();// 实例化框架类通常为程序中的bootstrap.php文件
7.配置文件:
配置文件分为两种,一种为程序的全局配置,例如数据库、默认控制器名和操作名等;另一种为局部配置,这种模块配置只有当访问到相关文件时,才会被对应的加载。全局的配置文件一般为命名为config.php内容如下:
8.框架类:
入口文件中调用了配置文件初始化了环境配置。定义了加载的控制器以及操作,所以在后续的调用中会根据配置文件的结果实例化框架的内容,实例化时会接受config中的相关参数(例如控制层、模型层和视图层),随后在入口文件中调用run函数进行相关的实例化。run函数的主要功能如下:
1)类自动加载
2)环境检查
3)过滤敏感字符
4)移除全局变量的老用法
5)路由处理
代码:
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类。
table) {// 获取数据库表名 $this->model = get_class($this);// 获取模型类名称 $this->model = substr($this->model, 0, -5);// 删除类名最后的 Model 字符 $this->table = strtolower($this->model);// 数据库表名与类名一致 } } } 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文件,内容:
PDO::FETCH_ASSOC); return self::$pdo = new PDO($dsn, DB_USER, DB_PASS, $option); } catch (PDOException $e) { exit($e->getMessage()); } } }