主要内容:
MVC的主要思想
(model模型、view视图、controller控制 - 程序员主要是在折腾控制)三种主要设计模式:
a-“依赖注入”,通过class去构建Model、view,然后通过controller去控制
b- 服务容器:将对外部的依赖放到容器中,让依赖更加简单、抽象。
c- Facade门面技术:在服务容器和控制器之间生成一个静态过渡类,让后续的控制器访问进一步抽象。《盗梦空间》—— 不得不服老外在编程技术上的积累和精进。国内还有太多路要走。
后面的层层嵌套、深入,类似《盗梦空间》,开始觉得还精巧,到后面把握不住火候的话,就感觉玩大了,兜不住了 —— 这个时候就需要不断地提升认知。
每一层class的延伸,包括facade门面技术,都类似从一层梦境进入另外一层梦境。越来越抽象,越来越牵一发而动全身。
. 1. MVC的主要思想
- M: Model, 模型, 数据库的操作
- V: View, 视图, 页面, html
- C: Controller, 控制器
<?php
// -----模型部分: 当前页面要显示的数据
$pdo = new PDO('mysql:host=localhost;dbname=phpedu', 'root', 'root');
$users = $pdo->query('select * from users limit 10')->fetchAll(PDO::FETCH_ASSOC);
?>
<!-- 视图部分 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户列表</title>
</head>
<body>
<table border="1" width="60%">
<caption>用户表</caption>
<tr>
<th>id</th>
<th>name</th>
<th>email</th>
</tr>
<?php foreach ($users as $user): ?>
<tr>
<td><?=$user['id']?></td>
<td><?=$user['name']?></td>
<td><?=$user['email']?></td>
</tr>
<?php endforeach ?>
</table>
</body>
</html>
不同的项目切入点不同。
- 仿站: V - M - C(模仿别人的网站,自然是从view开始)
- 自主: M - V - C(但自主研发,则是从数据开始)
几种设计模式
① 依赖注入
② 服务容器
③ Facade
2. “依赖注入”,通过class去构建Model、view,然后通过controller去控制
- 第一步,先建立model和view文件,里面包含了model和view的class
- Model.php
<?php
namespace mvc_demo;
use PDO;
// 模型类: 数据库操作
class Model
{
// 获取数据
public function getData()
{
return (new PDO('mysql:host=localhost;dbname=phpedu', 'root', 'root'))
->query('select * from users limit 10')->fetchAll(PDO::FETCH_ASSOC);
// 通过链式方式得到数据
}
}
// 测试模型
// print_r((new Model)->getData());
- View.php文件
<?php
namespace mvc_demo;
// 视图: 数据展示
class View
{
// 数据展示
public function fetch($data)
{
// 表格方式展示,使用字符串拼接实现table的html代码
// $table .= 相当于 $table = $table. + ...用来拼接语句)
$table = '<table>';
$table .= '<caption>用户信息表</caption>';
$table .= '<tr><th>ID</th><th>用户名</th><th>邮箱</th></tr>';
// 遍历用户表
foreach ($data as $user){
$table .= '<tr>';
$table .= '<td>'.$user['id'].'</td>';
$table .= '<td>'.$user['name'].'</td>';
$table .= '<td>'.$user['email'].'</td>';
$table .= '</tr>';
}
$table .= '</table>';
//这块的table跟之前有一些区别,去掉了php等标识,可以了解下。
return $table;
}
}
echo '<style>
table {border-collapse: collapse; border: 1px solid;text-align: center; width: 500px;height: 150px;width: 600px;}
caption {font-size: 1.2rem; margin-bottom: 10px;}
tr:first-of-type { background-color:yellow;}
td,th {border: 1px solid; padding:5px}
</style>';
// 视图测试
// require 'Model.php';
// echo (new View)->fetch((new Model)->getData());
3. 第二步,通过控制器来加载MV
3-1. 第一种,直接类内进行model、view的实例化
- Model, View 类的实例都是在Controller内完成的。会带来“代码耦合度过高”的问题
- 控制器知道的太多了, 造成了当前的控制器严重依赖外部的某些对象
- 这种现象就是大家常常听说过的: 代码的耦合度过高
- 需要使用外部对象的 “依赖注入”解决”代码的耦合度过高的问题”
<?php
// 控制器1
namespace mvc_demo;
// 加载模型类
require 'Model.php';
// 加载视图类
require 'View.php';
class Controller1
{
// 获取数据,并展示出来
public function index()
{
// 1. 获取数据
$model = new Model();
$data = $model->getData();
// 2. 渲染模板
$view = new View();
return $view->fetch($data);
}
}
// 客户端调用(测试)
// 创建控制器实例/对象
$controller = new Controller1();
echo $controller->index();
//
3-2. 第二种:外部实例化Model、View,然后以参数形式注入到Controller中
- 将类/对象依赖的外部的对象,以控制器方法的参数形式注入到当前的控制器中
- 此时, 控制器依赖的外部对象的实例化过程,就可以在控制器类的外部完成,一般在客户端完成
- 但controller这个类中不同的方法要去调用m、v的时候需要反复以方法传参的方式来进行。这边不够好。
<?php
// 控制器2: 使用依赖注入解决类/对象之间的耦合度过高的问题
namespace mvc_demo;
// 加载模型类
require 'Model.php';
// 加载视图类
require 'View.php';
class Controller2
{
// 获取数据,并展示出来
public function index($model, $view)
{
// 1. 获取数据
$data = $model->getData();
// 2. 渲染模板(也就是数据展示)
return $view->fetch($data);
}
/* 问题又来了: 控制器依赖的外部对象,尽管已经通过依赖注入进行了当前类中,
但是没有办法实现外部对象的复用。例如下面调用的时候又需要重新new?*/
public function edit($model)
{
}
}
// 客户端调用(测试)
// 创建控制器实例/对象
$model = new Model();
$view = new View();
$controller = new Controller2();
echo $controller->index($model, $view);
$controller->edit($model);
3-3. 通过构造方法将外部对象初始化,实现了外部对象在当前类的共享/复用
- 这种直接相当于将m、v通过类构造传参的方式让类内所有的方法都可以使用了,相对就方便了很多。
- 可以看出,从controller1到controller3的过程是逐渐抽象化、提升使用方便度的过程。
<?php
// 控制器3: 实现外部依赖注入的对象在类内部的共享/复用
namespace mvc_demo;
// 加载模型类
require 'Model.php';
// 加载视图类
require 'View.php';
class Controller3
{
// 外部依赖的对象
private $model = null;//后面的null也可以不写
private $view = null;
// 通过构造方法将外部对象初始化,实现了外部对象在当前类的共享/复用
public function __construct($model, $view)
{
$this->model = $model;
$this->view = $view;
}
// 获取数据,并展示出来
public function index()
{
// 1. 获取数据
$data = $this->model->getData();
// 2. 渲染模板
return $this->view->fetch($data);
}
// 外部对象的复用
public function edit()
{
// $this->model;
}
}
// 客户端调用(测试)
// 创建控制器实例/对象
$model = new Model();
$view = new View();
// 将外部对象的注入点从普通方法,改到了构造方法中
$controller = new Controller3($model, $view);
echo $controller->index();//之前的参数是方法级别的,现在则提升到了类级别。
$controller->edit();
// o:这种情况下class内的每个方法就要相对简单,因为无法在括号中传参。
// 当然也有可能方法本身就应该相对简单,一个方法一个非常独立的返回。当然这默认所有的类中的参数方法都是可以使用的了。
3-4. 通过服务容器来接管对外部对象的依赖
- 上面的3是依赖外部对象,但如果外部对象很多,例如m、v等,这种情况下可以用容器,对象容器或服务容器,来进行接管。
<?php
// 控制器4: 将当前依赖的外部对象,放到一个"服务容器"中进行统一管理
namespace mvc_demo;
use Closure; // 后面使用闭包类型的时候前面就不用加/了。
// 加载模型类
require 'Model.php';
// 加载视图类
require 'View.php';
// -----------------------------------------
// 服务容器
class Container1
{
// 1. 对象容器
protected $instances = [];
// 2. 向对象容器中添加对象
// 参数1: 是外部对象在当前对象容器数组中的键名/别名
// 参数2: 是当前需要绑定到容器的对象的实例化过程(函数)
public function bind($alias, Closure $process)
//第二个参数是一个函数,使用闭包类型
{
$this->instances[$alias] = $process;
}
// 3. 从对象容器中取出对象, 调用它
public function make($alias, $params=[] ) {
return call_user_func_array($this->instances[$alias], []);
}
}
// 将外部对象: Model, View的实例绑定到服务容器中
$container = new Container1;
// 绑定模型类实例绑定到服务容器中
$container->bind('model', function(){
return new Model();
});
// 绑定视图类实例绑定到服务容器中
$container->bind('view', function(){
return new View();
});
// ------------------------------------------
class Controller4
{
// 获取数据,并展示出来
public function index(Container1 $container)
{
// 1. 获取数据
$data = $container->make('model')->getData();
// 2. 渲染模板
return $container->make('view')->fetch($data);
}
}
// 客户端调用(测试)
$controller = new Controller4();
echo $controller->index($container);
// 为什么要用服务容器?
// 将当前类对许多外部对象的依赖, 转为对一个服务容器的依赖
// 将外部对象的依赖,使用服务容器进行了接管
// 这样下来后面的地方就简化了很多。
3-5. Facade门面技术: 将对服务容器中的对象的访问进行静态接管
<?php
// 控制器5: Facade门面技术, 静态接管服务容器中的成员的访问
namespace mvc_demo;
use Closure;
require 'Model.php';
require 'View.php';
// -----------------------------------------
// 服务容器
class Container2
{
// 1. 对象容器
protected $instances = [];
public function bind($alias, Closure $process)
{
$this->instances[$alias] = $process;
}
// 3. 从对象容器中取出对象, 调用它
public function make($alias, $params=[] ) {
return call_user_func_array($this->instances[$alias], []);
}
}
// 将外部对象: Model, View的实例绑定到服务容器中
$container = new Container2;
// 绑定模型类实例绑定到服务容器中
$container->bind('model', function(){
return new Model();
});
// 绑定视图类实例绑定到服务容器中
$container->bind('view', function(){
return new View();
});
// ------------------------------------------
// 在服务容器与工作的控制器之间再添加一个中间层: Facade
class Facade
{
// 服务容器
protected static $container = null;
// 初始化方法: 就是给当前的Facade类中的$container属性赋值
// 理解为Facade的构造方法(但不是)
// 将外部的服务容器注入到当前的facade中
public static function initialize(Container2 $container)
{
static::$container = $container;
// 给本类中的静态属性赋值,其实就是将前面的容器初始化。
}
}
// 模型类成员访问静态化(给成员套一个静态访问的马甲)
class UserModel extends Facade
{
public static function getData()
{
return static::$container->make('model')->getData();
}
}
// 视图类成员访问静态化(给成员套一个静态访问的马甲)
class UserView extends Facade
{
public static function fetch($data)
{
return static::$container->make('view')->fetch($data);
}
}
// ------------------------------------------
class Controller5
{
// 构造方法,初始化facade
public function __construct(Container2 $container)
{
Facade::initialize($container);
}
// 用Facade方式类成员
public function index()
{
// 1. 获取数据
$data = UserModel::getData();
// 2. 渲染模板
return UserView::fetch($data);
}
}
// 客户端调用(测试)
$controller = new Controller5($container);
echo $controller->index();
4. 作业实战
- 按照老师的方法自己走了一边,并且用Facade的方法实现了。
- 但对中间各种class(model、view、container、Facade、controller(o - 能否归纳为 MV-CF-C模式,中间的C-F分别为container和Facade))之间的嵌套关系还是比较懵。估计要后面不断看代码才行。
- 自己的z1model.php,其中gettdata多加t是为了试验是否可以乱改名字等。
<?php
namespace mvc;
use PDO;
class dataModel{
public function gettData(){
return (new PDO('mysql:host=localhost;dbname=liangtest','liang','123456'))
->query('SELECT * FROM `shao` LIMIT 8')->fetchAll(PDO::FETCH_ASSOC);
}
}
print_r((new dataModel)->gettData());
/*
8月4日作业
1. 将课堂视频至少看二遍以上,细细品服务容器与facade带来的好处,并写出自己的总结
2. 试着用mvc实现一个简单的业务逻辑(可仿课堂案例,但不要全照抄)
3. 可选: 如何优化Facade的调用过程
*/
- z2view.php部分
<?php
namespace mvc;
class shaoView{
public function fetch($items){
$table = '<table>';
$table .= '<caption>内容列表</caption>';
$table .= '<tr>
<th>id</th>
<th>date</th>
<th>title</th>
<th>tags</th>
</tr>';
foreach ($items as $item){
$table .= '<tr>';
$table .= '<td>'.$item['id'].'</td>';
$table .= '<td>'.$item['date'].'</td>';
$table .= '<td>'.$item['title'].'</td>';
$table .= '<td>'.$item['label'].'</td>';
$table .= '</tr>';
}
$table .= '/<table>';
return $table;
}
}
echo '<style>
table {border-collapse: collapse; border: 1px solid;text-align: center; width: 500px;height: 150px;width: 600px;}
caption {font-size: 1.2rem; margin-bottom: 10px;}
tr:first-of-type { background-color:yellow;}
td,th {border: 1px solid; padding:5px}
</style>';
require 'z1model.php';
echo (new shaoView)->fetch((new dataModel)->gettData());
- z3controller部分
<?php
namespace mvc;
use Closure;
require 'z2view.php';
class objContainer{
protected $insts = [];
public function bind($name, Closure $process){
$this->insts[$name] = $process;
}
public function make($name,$params=[]){
return call_user_func_array($this->insts[$name],[]);
}
}
$container = new objContainer;
$container->bind('dataModel',function(){
return new dataModel();
});
$container->bind('shaoView',function(){
return new shaoView();
});
class Facade{
protected static $container =null;
public static function initialize(objContainer $container){
static::$container = $container;
}
}
class dataamodel extends Facade{
public static function gettData(){
return static::$container->make('dataModel')->gettData();
}
}
class shaooView extends Facade{
public static function fetch($data){
return static::$container->make('shaoView')->fetch($data);
}
}
class Controller{
public function __construct(objContainer $container){
Facade::initialize($container);
}
public function index(){
$data = dataamodel::gettData();
return shaooView::fetch($data);
}
}
$controller = new Controller($container);
echo $controller->index();
最终比较幸运,数据都能读取出来。