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

news2024/11/18 7:41:08

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

<?php

namespace core;

// 路由器类
class Router
{
    public static function parse(): array
    {
        // 默认控制器,实际项目,应该来自配置文件,而不是在写死
        $controller = 'Index';
        $action = 'index';
        // 参数列表
        $params = [];

        // 判断是否存在pathinfo
        if (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.coloradmin.cn/o/375011.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java+Swing+Mysql实现超市管理系统

一、系统介绍1.开发环境操作系统&#xff1a;Win10开发工具 &#xff1a;IDEA2018JDK版本&#xff1a;jdk1.8数据库&#xff1a;Mysql8.02.技术选型JavaSwingMysql3.功能模块4.系统功能1.系统登录登出管理员可以登录、退出系统2.商品信息管理管理员可以对商品信息进行查询、添加…

MapReduce小试牛刀

部署完hadoop单机版后&#xff0c;试下mapreduce是怎么分析处理数据的 Word Count Word Count 就是"词语统计"&#xff0c;这是 MapReduce 工作程序中最经典的一种。它的主要任务是对一个文本文件中的词语作归纳统计&#xff0c;统计出每个出现过的词语一共出现的次…

【云原生kubernetes】k8s 常用调度策略使用详解

一、前言 通过之前的学习&#xff0c;我们了解到k8s集群中最小工作单位是pod&#xff0c;对于k8s集群来说&#xff0c;一个pod的完整生命周期是由一系列调度策略来控制&#xff0c;这些调度策略具体是怎么工作的呢&#xff1f;本文将详细讨论下这个问题。 二、k8s调度策略简介…

K8S---Pod进阶资源限制以及探针

目录 一、Pod 进阶 1、资源限制 2、Pod 和 容器 的资源请求和限制&#xff1a; 3、CPU 资源单位 4、内存 资源单位 5、实例操作 5.1 示例1 5.2 示例2 6、重启策略&#xff08;restartPolicy&#xff09; 6.1 示例1 二、健康检查&#xff1a;又称为探针&#xff08;P…

华为OD机试题,用 Java 解【机器人走迷宫】问题

最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…

电动针阀流量控制和电气比例阀压力控制在液氮低温控制中的应用

摘要&#xff1a;为了解决室温至液氮温区温控系统中需要昂贵的低温电动阀门进行液氮介质流量调节的问题&#xff0c;本文提供了三种不同精度的液氮温区内的低温温度控制解决方案。解决方案的技术核心是通过采用电动针阀和电气比例阀在室温环境下来快速调节外部气源流量或压力大…

Nginx网站服务——编译安装、基于授权和客户端访问控制

文章目录一、Nginx概述1.1、Nginx的特点1.2、Nginx编译安装1.3、Nginx运行控制1.4、Nginx和Apache的区别二、编译安装Nginx服务的操作步骤2.1、关闭防火墙&#xff0c;将安装nginx所需软件包传到/opt目录下2.2、安装依赖包2.3、创建运行用户、组&#xff08;Nginx 服务程序默认…

【2023全网最全教程】从0到1开发自动化测试框架(建议收藏)

一、序言 随着项目版本的快速迭代、APP测试有以下几个特点&#xff1a; 首先&#xff0c;功能点多且细&#xff0c;测试工作量大&#xff0c;容易遗漏&#xff1b;其次&#xff0c;代码模块常改动&#xff0c;回归测试很频繁&#xff0c;测试重复低效&#xff1b;最后&#x…

Java 多线程 --- 多线程的相关概念

Java 多线程 --- 多线程的相关概念Race Condition 问题并发编程的性质 --- 原子性, 可见性, 有序性上下文切换 (Context Switch)线程的一些故障 --- 死锁, 活锁, 饥饿死锁 (Deadlock)活锁(Livelock)死锁和活锁的区别饥饿(Starvation)背景: 操作系统 — 线程/进程 同步 Race Co…

Windows操作系统的体系结构、运行环境和运行状态

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来重新审视一下Windows这个我们熟悉的不能再熟悉的系统。说Windows操作系统的运行环境和运行状态&#xff0c;首先要介绍一下Windows操作系统的体系结构&#xff0c;然后再要说到最重要的两个概念:核…

【架构师】零基础到精通——微服务体系

博客昵称&#xff1a;架构师Cool 最喜欢的座右铭&#xff1a;一以贯之的努力&#xff0c;不得懈怠的人生。 作者简介&#xff1a;一名Coder&#xff0c;软件设计师/鸿蒙高级工程师认证&#xff0c;在备战高级架构师/系统分析师&#xff0c;欢迎关注小弟&#xff01; 博主小留言…

Iterator和Genertator

一、Iterator迭代器和for of原理 * 遍历器&#xff08;Iterator&#xff09;是一种机制(接口)&#xff1a;为各种不同的数据结构提供统一的访问机制&#xff0c;任何数据结构只要部署Iterator接口&#xff0c;就可以完成遍历操作「for of循环」&#xff0c;依次处理该数据结构的…

【C++入门(下篇)】C++引用,内联函数,auto关键字的学习

前言&#xff1a; 在上一期我们进行了C的初步认识&#xff0c;了解了一下基本的概念还学习了包括&#xff1a;命名空间&#xff0c;输入输出以及缺省参数等相关的知识。今天我们将进一步对C入门知识进行学习&#xff0c;主要还需要大家掌握我们接下来要学习的——引用&#xf…

基于SpringCloud的可靠消息最终一致性06:轮询事务消息

上一节把可靠消息最终一致性的正常逻辑代码顺序执行了一次,并且对于同一个事务消息,在正常情况下它要被发送至少两次。 这是因为在发送消息之前,TransactionMessageService就已经把消息保存到了数据库中。而在首次消费完消息后,TransactionMessageListener并没有从数据库中…

冯诺依曼体系结构与操作系统的概念及理解

一、 冯诺依曼体系结构1、概念2、内存的作用3、硬件原理解释软件行为二、操作系统的概念及基本作用1、概念2、设计操作系统的目的3、操作系统的主要作用4、什么是管理5、管理的目的6、操作系统如何为我们服务一、 冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们…

只需四步,手把手教你打造专属数字人

伴随ChatGPT的问世&#xff0c;在技术与商业运作上都日渐发展成熟的数字人产业正持续升温。去年9月&#xff0c;北京市发布了国内首个数字人产业专项支持政策&#xff0c;提出将依托国家文化专网将数字人纳入文化数据服务平台。以数字人、ChatGPT为代表的互联网3.0创新应用产业…

【2023】OAK智能深度相机用户实际应用项目(附开源代码)

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手…

Linux环境内存管理——分配内存和释放内存

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来重新审视一下Windows程序员如何学习Linux环境内存管理。由于很多程序在Windows环境下开发好后&#xff0c;还要部署到Linux服务器上去&#xff0c;所以作为Windows程序员有必要学习Linux环境的内存…

IntelliJ IDEA 实用插件推荐(包含使用教程)

IntelliJ IDEA 实用插件推荐 背景&#xff1a;电脑重装了&#xff0c;重新下载了最新版的IntelliJ IDEA&#xff0c;感觉默认模式有点枯燥&#xff0c;于是决定从网上下载一些实用美观的插件优化自己以后吃饭的工具&#xff0c;现在推荐的都是目前还能用的&#xff08;亲身实践…

【java】Java 内存模型

文章目录前言什么是 Java 内存模型为什么需要 Java 内存模型顺序一致性内存模型Happens-Before 规则总结前言 在并发编程中&#xff0c;当多个线程同时访问同一个共享的可变变量时&#xff0c;会产生不确定的结果&#xff0c;所以要编写线程安全的代码&#xff0c;其本质上是对…