13-mvc框架原理与实现方式

news/2024/4/26 2:55:28/文章来源:https://blog.csdn.net/q506174602/article/details/129187525

1、mvc原理

# mvc 与框架## 1.mvc 是什么1. m:model,模型(即数据来源),主要是针对数据库操作
2. v:view,视图,html 页面。视图由一个一个模板构成(模板是视图的一个具体展现或载体,视图是模板的一个抽象)
3. c:controller,控制器,用于mv之间的数据交互## 2.最简单的 mvc
就是一个可以显示数据库内容的模板## 3.分层后的mvc### 2.1 控制器(以下三个)1. 接受请求: 路由
2. 选择模型: CURD
3. 加载视图: 模板### 2.2 模型(功能是操作数据库)1. 查询构造器
2. 模型操作## 2.3 视图(以下两个)1. 模板赋值
2. 渲染视图

2、mvc的极简实现方式(一个页面)

<?php
//!数据库查询(model)
//第一步:连接数据库
$db = new PDO('mysql:dbname=phpedu','root','root');// 第二步:对数据库进行查询
$stmt = $db->prepare('SELECT * FROM `staff` LIMIT ?');// 第三步:数据绑定,获取指定的数据,确定获取的数据条数
$stmt->bindValue(1,5,PDO::PARAM_INT);// 第四步:执行上述操作,如果不进行数据绑定就需要在execute里面加入参数
$stmt->execute();// 第五步:把查询到的数据集保存到一个变量里备用
$staffs = $stmt->fetchAll(PDO::FETCH_ASSOC);
?><!-- 视图(view) -->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>员工列表</title>
</head>
<body><!-- 第六步:foreach渲染数据 --><h3>员工列表</h3><?php foreach ($staffs as $staff) :extract($staff) ?><li><?=$id?>:<?=$name?> , <?=$sex ? '女':'男'?>(<?=$email?>)</li><?php endforeach?>
</body>
</html>

上述代码中,模型model和视图view都已经存在了,而控制器controller实际上已经有了,但是我们看不到,因为我们上面的业务逻辑太简单,直接把数据写进去了。没有通过controller让model和view进行数据交互。

3、分层的mvc 

先写控制器controller,控制器写完写模型model,模型写完最后再写视图view。

 其中,core里面包含模型类(Model.php),视图类(View.php),控制器类(controller.php)

 

 Model.php

<?php// 模型类// 命名空间遵循psr-4
// 类名与文件名同名
// 当前类的命名空间与当前类所在的路径应该是一一对应的
namespace core;use PDO;// 视图,控制器和模型不能直接使用,因为这是底层的业务逻辑
// 是框架源码的一部分,不要让用户直接用,因为框架一旦更新
// 新的框架源码会把core目录下的所有代码全部覆盖
// 所以视图,控制器和模型里面的内容和类尽量把它们转为抽象的
// 或者干脆写个接口,让用户来进行实例化
// 模型抽象化(每个用户一张表),只允许通过子类使用
abstract class Model
{// 要把连接对象写成属性,因为这个对象它会在当前的模型类中多个方法中使用protected $db = null;// 1.连接数据库// 在实例化时能够自动连接,可以写在构造函数里public function __construct($dsn,$username,$password){$this->db = new PDO($dsn,$username,$password);}// 2.内置一些基本的底层操作,供用户的自定义模型用// 自定义模型:与某一个或某一张数据表相关的类// 2.1获取全部数据public function select($num){$sql = 'SELECT *FROM `staff` LIMIT ?;';$stmt = $this->db->prepare($sql);$stmt->bindParam(1,$num,PDO::PARAM_INT); // !这种方法可以确保当出错时我们知道错误在哪里// if($stmt->execute()){//     return $stmt->fetchAll(PDO::FETCH_ASSOC);// }else{//     print_r($stmt->errorInfo());// }// !也可以用简化版,因为出错概率较低$stmt->execute();return $stmt->fetchAll(PDO::FETCH_ASSOC);}// 2.2获取某个数据(id)public function getOne($id){$sql = 'SELECT *FROM `user` WHERE `id` = ?;';$stmt = $this->db->prepare($sql);$stmt->bindParam(1,$id,PDO::PARAM_INT); $stmt->execute();return $stmt->fetch(PDO::FETCH_ASSOC);}
}

 View.php

<?php// 视图类namespace core;class view
{// 1.模板变量容器protected array $data = [];// 2.模板赋值public function assign(string $key,$value){$this->data[$key] = $value;}// 3.渲染视图// 渲染与传参可以同步完成// $path告诉数据显示在哪个页面中public function render(string $path,array $data = []){if($data){foreach($data as $key=>$value){$this->assign($key,$value);}}// 将模板变量数组展开为独立的变量,以方便传入到模板中使用extract($this->data);// 渲染/加载模板文件file_exists($path) ? include $path : die('模板不存在');}
}

Controller.php

<?php// 控制器类namespace core;abstract class Controller
{// 1.模型对象protected Model $model;// 2.视图对象protected View $view;// 3.实例化,初始化上面的模型对象,视图对象public function __construct(Model $model,View $view){$this->model = $model;$this->view = $view;}
}

autoload.php(自动加载器)

<?php// 注册类的自动加载器方法
spl_autoload_register(function($class){// require str_replace('\\','/',$class) . 'php';// 为了系统的兼容性,可以使用require str_replace('\\',DIRECTORY_SEPARATOR,$class) . '.php';// 这是一个可以代替 composer 的方法,参数是function,传入了一个类名,// 然后用str_replace这个函数,把类里面的命名空间,也就是反斜线替换成路径符,// 然后在后面加扩展名 .php 转变为类就行了// 这样就实现了了一个类的自动加载了(相当于require了一个命名空间+类名)
});

如果想要把这些类一个一个加载到项目中去,则需要针对不同的业务类型创建不同的控制器和模型,所以要创建一个controller文件夹

 

 StaffController.php(用户自定义控制器)

虽然不是控制器基类,但是要求必须继承自控制器基类(父类)

<?php// 自定义控制器,必须继承自控制器基类(超类/父类)namespace controller;use core\Controller;
use core\Model;
use core\View;
use model\StaffModel;class StaffController extends Controller
{public function __construct(Model $model,View $view){// 里面这样写就冗余了// $this->model = $model;// $this->view = $view;//子类的构造函数直接调用父类的构造方法就可以了parent::__construct($model,$view);}// 自定义方法:默认方法// index():列出所有数据public function index($num = 10){// 1.选择模型:获取数据$staff = $this->model->getAll($num);// 2.加载视图// 路径约定:view/控制器/方法名.php// key值可以理解为变量名,相当于把$staff接收到的值赋给了名为staffs的变量$this->view->render('view/staff/index.php',['staffs'=>$staff]);}
}

创建类文件夹,里面都是自定义模型类 

 

 StaffModel.php(用户自定义模型类)

<?php
// 自定义模型namespace model;use core\Model;class StaffModel extends Model
{public function __construct($dsn,$username,$password){parent::__construct($dsn,$username,$password);}// 获取全部数据public function getAll($num){return $this->select($num);}
}

创建视图文件夹,在视图里再创建文件夹,staff文件夹对应是当前的控制器,里面的文件对应当前的方法。

 

 staff>index.php

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>员工列表</title>
</head>
<body><!-- 第六步:foreach渲染数据 --><h3>员工列表</h3><?php foreach ($staffs as $staff) :extract($staff) ?><li><?=$id?>:<?=$name?> , <?=$sex ? '女':'男'?>(<?=$email?>)</li><?php endforeach?>
</body>
</html>

在mvc2文件夹下创建测试文件index.php

 mvc2>index.php

<?php
namespace mvc2;use controller\StaffController;
use model\StaffModel;
use core\view;
// 入口文件:测试// 1.类的自动加载器
// 自动加载器主要用来加载间接用到的类,但是直接用到的类还是需要用use
require __DIR__ . '/core/autoload.php';// 路由解析// 2.实例化控制器
$model = new StaffModel('mysql:dbname=phpedu','root','root');
$view = new View();
// 实例化控制器对象
$staff = new StaffController($model,$view);// 3.调用控制器中的方法
$staff->index(3);

上述运行结果:

 4、路由原理及应用

<?php// 路由的本质:是从url中解析出控制器,控制方法,以及方法的参数// 1.controller:控制器类名
// 2.method:控制器中的某个方法名
// 3.parameter:参数列表,以数组形式// 以上三种数据在url中的展示方式有两种
// 1.queryString:查询字符串
// 2.PATH_INFO:路径信息// phpedu.io/one/two/demo1.php?查询字符串,以键值对方式,和&分开
// !c:controller,m:method,p:parameter
// phpedu.io/one/two/demo1.php?c=hello&m=method&p=aaa
// 得到这个查询字符串以后,我们可以通过一些方法,将该字符串解析成数组,从而得到控制器,方法和参数//! 在我们的脚本名称 `phpedu.io/one/two/demo1.php` 和查询字符串 `c=hello&m=method&p=aaa`之间
// !如果又出现路径,我们用 PATH_INFO 表示 /user/index/100/200
// phpedu.io/one/two/demo1.php   PATH_INFO   ?c=hello&m=method&p=aaa
// phpedu.io/one/two/demo1.php/user/index/100/200?c=hello&m=method&p=aaa// /user/index/100/200:PATH_INFO
function p($data)
{echo is_array($data) ? sprintf('<pre>%s</pre>',print_r($data,true)) : $data;
}
p([1,2,3]);
p('Hello');
echo '<hr>';
// !QueryString: 查询字符串
// 超全局数组$_SERVER的QUERY_STRING键可以返回当前的查询字符串
p($_SERVER['QUERY_STRING']);
// 将查询字符串解析到数组里面
parse_str($_SERVER['QUERY_STRING'],$request);
p($request);// 人为认定
// c controller, m:method,name:parameter
$controller = array_shift($request);
$method = array_shift($request);
$params = array_shift($request);// 控制器类:测试专用
class HelloController
{public function world($name){return 'Hello ,' . $name;}
}// 生成控制器类名
$controller = ucfirst($controller) . 'Controller';
echo (new $controller)->$method($params);
// 一般用回调的方式来调用
// echo call_user_func_array([new $controller(),$method],[$params]);
echo call_user_func([new $controller(),$method],$params);
echo '<hr>';// ! 2.PATH_INFO:查询字符串与脚本之间的路径信息
//* http://phpedu.io/0824/router.php/hello/world/admin?c=hello&m=world&name=peter
//* PATH_INFO:/hello/world/admin
p($_SERVER['PATH_INFO']);
p(explode('/',$_SERVER['PATH_INFO']));
// 但这样之后发现索引0对应的值为空
// p(array_filter(explode('/',$_SERVER['PATH_INFO'])));
// 去除空字符也可以这样
$request = explode('/',trim($_SERVER['PATH_INFO'],'/'));$controller = array_shift($request);
$method = array_shift($request);
$params = array_shift($request);$controller = ucfirst($controller) . 'Controller';
echo call_user_func([new $controller(),$method],$params);
// 也可以 echo call_user_func_array([new $controller(),$method],[$params]);// !推荐使用PATH_INFO
// 通过url重写功能,可以将脚本的扩展名php隐藏,也可以在末尾自定义一个扩展名
//* 隐藏后的地址具有欺骗性:http://phpedu.io/0824/router/hello/world/admin.html/*** 总结:* 1.$_SERVER['QUERY_STRING']以键值对方式返回当前字符串,返回值是数组,需要用parse_str转换成字符串* 2.1.$_SERVER['PATH_INFO']返回带有/的字符串,返回值是字符串,需要用explode切割转换成数组*/

上述运行结果:

 

在mvc3文件夹下创建测试文件index.php 

<?php
namespace mvc3;use model\UserModel;
use core\view;
use core\Router;
// 入口文件:测试// 1.类的自动加载器
require __DIR__ . '/core/autoload.php';// 路由解析
$request = Router::parse();$controller = array_shift($request);
$method = array_shift($request);
$params = array_shift($request);// 生成控制器名称
$controller ='controller\\' . ucfirst($controller) . 'Controller';// echo $controller;
// die;// 2.实例化控制器
$model = new UserModel('mysql:dbname=phpedu','root','root');
$view = new View();
// 实例化控制器对象
$user = new $controller($model,$view);// 3.调用控制器中的方法
call_user_func_array([$user,'get'],$params);

 mvc3>core>router.php

<?phpnamespace core;// 路由器类
class Router
{public static function parse(): array{// 默认控制器,实际项目,应该来自配置文件,而不是在写死$controller = 'Index';$action = 'index';// 参数列表$params = [];// 判断是否存在pathinfoif (array_key_exists('PATH_INFO', $_SERVER) && $_SERVER['PATH_INFO'] !== '/') {// 为什么要判断 $_SERVER['PATH_INFO'] !== '/' ?// 因为: admin.php/ 时,$_SERVER['PATH_INFO'] = '/', 导致解析控制器失败$pathinfo = array_filter(explode('/', $_SERVER['PATH_INFO']));// dump($pathinfo);// 考虑到index.php/ 情况, 这时pathinfo为空数组if (count($pathinfo) >= 2) {$controller = array_shift($pathinfo);$action = array_shift($pathinfo);$params = $pathinfo;// $params = array_shift($pathinfo);} else {$controller = array_shift($pathinfo);}}// 查看控制器,方法,参数// dump($controller, $action, $params);// 将这些数据返回出去return [$controller, $action, $params];}
}

mvc3>model>UserModel

<?php
// 自定义模型namespace model;use core\Model;class UserModel extends Model
{public function __construct($dsn,$username,$password){parent::__construct($dsn,$username,$password);}// 获取全部数据public function get($id){return $this->getOne($id);}
}

mvc3>view>user>get

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>员工信息</title>
</head><body><!-- 第六步:foreach渲染数据 --><h3>员工信息</h3><?php if (is_array($user)) : ?><?php foreach ($user as $key => $value) : ?><!-- 如果不想拿 --><?php// if($value = null){//     echo '该用户不存在';// }if ($key === 'password' || $key === 'register_time') {continue;}?><li>[<?= $key ?>] => <?= $value ?></li><?php endforeach ?><?php endif ?><?php if (is_array($user) == null) : ?><h3>此用户不存在</h3><?php endif ?>
</body></html>

 上述运行结果:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_74814.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

锁相环的组成和原理及应用

一.锁相环的基本组成 许多电子设备要正常工作&#xff0c;通常需要外部的输入信号与内部的振荡信号同步&#xff0c;利用锁相环路就可以实现这个目的。 锁相环路是一种反馈控制电路&#xff0c;简称锁相环(PLL)。锁相环的特点是&#xff1a;利用外部输入的参考信号控制环路内…

阶段八:服务框架高级(第五章:服务异步通信-高级篇(RabbitMQ高级))

阶段八&#xff1a;服务框架高级&#xff08;第五章&#xff1a;服务异步通信-高级篇&#xff08;RabbitMQ高级&#xff09;&#xff09;Day-第五章&#xff1a;服务异步通信-高级篇&#xff08;RabbitMQ高级&#xff09;0.学习目标1.消息可靠性1.1.生产者消息确认1.1.1.修改配…

400G光模块知识大全

400G光模块是目前高速传输领域中的一种先进产品&#xff0c;被广泛应用于高性能数据中心、通信网络、大规模计算、云计算等领域。本文将从400G光模块的定义、技术、产品型号、应用场景以及未来发展方向进行详细介绍。一、什么是400G光模块&#xff1f;400G光模块是指传输速率达…

Android zygote进程启动流程

zygote启动过程中涉及到以下模块&#xff1a; app_processzygote USAPsocketFileDescriptor (FD) AndroidRuntimeAppRuntime &#xff08;定义于app_process模块&#xff0c;继承自AndroidRuntime。&#xff09; init进程启动zygote进程&#xff1a; #init.zygote32_64.rc s…

前端开发_快应用开发

目录快应用官网真机调试组件组件嵌套问题tab组件list组件web组件css 样式问题[1]选择器[2]盒模型[3]样式布局-弹性布局[4-1]样式切换 - 类名的动态切换[4-2] 样式切换 - 行内样式动态切换[5]background[6]overflow[7]border-radius[8]盒子阴影[9] 单位系统接口[1] 检查某app是否…

redis五种数据结构

redis五种数据结构1. redis 其他相关1.1 redis 的安装1.2 redis 的持久化1.3 redis 配置文件2. redis 常见命令2.1 key2.2 设置 key 的生存时间或过期时间3. redis的5种常见的数据结构3.1 String3.2 list3.3 hash3.4 set3.5 zset&#xff08;SortedSet&#xff08;有序集合&…

2023年“网络安全”赛项浙江省金华市选拔赛 任务书

2023年“网络安全”赛项浙江省金华市选拔赛 任务书 任务书 一、竞赛时间 共计3小时。 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段单兵模式系统渗透测试 任务一 Windows操作系统渗透测试 任务二 Linux操作系统渗透测试 任务三 网页渗透 任务四 Linux系统…

《爆肝整理》保姆级系列教程python接口自动化(二十四)--unittest断言——中(详解)

简介 上一篇通过简单的案例给小伙伴们介绍了一下unittest断言&#xff0c;这篇我们将通过结合和围绕实际的工作来进行unittest的断言。这里以获取城市天气预报的接口为例&#xff0c;设计了 2 个用例&#xff0c;一个是查询北京的天气&#xff0c;一个是查询 南京为例&#xf…

继电器的工作原理、构成和功能介绍

随着电力应用的不断发展&#xff0c;电气设备已经深入到我们的日常生活中&#xff0c;电气自动化技术大量使用在电力系统和生产型企业中&#xff0c;人们在享受电带来方便的同时要注意用电保护。继电器就是为了保护电路而生的&#xff0c;可以提高电路可靠性&#xff0c;保障用…

RabbitMQ实现死信队列

目录死信队列是什么怎样实现一个死信队列说明实现过程导入依赖添加配置编写mq配置类添加业务队列的消费者添加死信队列的消费者添加消息发送者添加消息测试类测试死信队列的应用场景总结死信队列是什么 “死信”是RabbitMQ中的一种消息机制&#xff0c;当你在消费消息时&#…

wafw00f 防火墙探测

kali机器自带防火墙探测工具wafw00&#xff0c;它可以通过发送正常以及不正常甚至包含恶意代码的HTTP请求&#xff0c;来探测网站是否存在防火墙&#xff0c;并识别防火墙的厂商及类型。安装&#xff1a;git clone https://github.com/EnableSecurity/wafw00f.git python setup…

Windows如何查看某个端口被占用的情况?

在工作中&#xff0c;有时会发现端口被占用的情况&#xff0c;导致软件报错或者服务无法启动等问题。在不知道具体哪个进程占用该端口号的情况下&#xff0c;我们可以用下面方法来查找。 举例&#xff1a;我现在发现8090端口被占用了&#xff0c;我现在需要找到并杀掉该进程。…

TCP状态转换

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 TCP状态转换专栏&#xff1a;《Linux从小白到大神》《网络编程》 TCP状态转换示意图如下 针对上面的示…

高并发之读多写少的场景设计(用户中心)

用户中心是一个典型的读多写少系统&#xff0c;可以说我们大部分的系统都属于这种类型&#xff0c;而这类系统通过缓存就能获得很好的性能提升。并且在流量增大后&#xff0c;用户中心通常是系统改造中第一个要优化的模块&#xff0c;因为它常常和多个系统重度耦合&#xff0c;…

消息队列介绍和RabbitMQ的安装

1.消息队列 1.1 MQ的相关概念 1.1.1 什么是MQ MQ(message queue)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&#xff0c;只不过队列中存放的内容是message 而已&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递消息。在…

高阶人生从在职读研开始——中国社科院与美国杜兰大学金融管理硕士

说到学历&#xff0c;好多人都不太在意&#xff0c;感觉学历没什么用。等升职学历被卡时&#xff0c;等你想考公学历达不到时&#xff0c;当你想跳槽更大的平台时&#xff0c;学历会显得尤其重要。当机会来临时&#xff0c;我们应该做好全足的准备&#xff0c;而不是眼瞅着机会…

SpringBoot相关操作

01-今日内容 Spring概述、快速入门SpringBoot配置SpringBoot整合 02-SpringBoot概述 SpringBoot提供了一种快速使用Spring的方式&#xff0c;基于约定优于配置的思想&#xff0c;可以让开发人员不必在配置与逻辑业务之间进行思维的切换&#xff0c;全身心的投入到逻辑业务的…

金融信创步入快车道,应“需”而生的监控易运维方案为国产化助力

在我国“28N”信创三步走战略中&#xff0c;金融信创赫然名列其中&#xff0c;成为最早践行信创理论与实操的行业之一。截止到目前&#xff0c;金融信创渗透率业已仅次于党政部门&#xff0c;位列“8”大重点行业之首。超快的发展速度&#xff0c;让金融信创较早的步入“买方市…

浅谈模型评估选择及重要性

作者&#xff1a;王同学 来源&#xff1a;投稿 编辑&#xff1a;学姐 模型评估作为机器学习领域一项不可分割的部分&#xff0c;却常常被大家忽略&#xff0c;其实在机器学习领域中重要的不仅仅是模型结构和参数量&#xff0c;对模型的评估也是至关重要的&#xff0c;只有选择那…

前端开发:JS的节流与防抖

前言 在前端实际开发中&#xff0c;有关JS原生的节流和防抖处理也是很重要的点&#xff0c;关于底层和原理的掌握使用&#xff0c;尤其是在性能优化方面甚为重要。作为前端开发的进阶内容&#xff0c;在实际开发过程中节流和防抖通常都是项目优化的必要手段&#xff0c;而且也是…