php 容器 门面 依赖注入,MVC实现和依赖注入、服务容器和门面的探讨

一、MVC设计模式工作原理及简单实现M: Model(模型层),最bottom一层,是核心数据层,程序需要操作的数据或信息.

V:View (视图层),最top一层,直接面向最终用户,视图层提供操作页面给用户,被誉为程序的外壳.

C:Controller(控制层),是middile层, 它负责根据用户从”视图层”输入的指令,选取”数据层”中的数据,然后对其进行相应的操作,产生最终结果。

72f00cc838c49d77759df5cdc84ec2e2.png

其实日常开发中无论是仿站还是自己开发都基本按这个思路。如进行仿站一般都是从View开始,然后Model,最后想着Controller从Model数据库中读取数据展示到View中给客户浏览。而自己开发则是先考虑数据库Model的设计,怎么展示给客户View和如何Controller将数据库中数据传递给View。

下面就是简单的实现:

//Model.php(模型)

classModel{

functiongetData(){

$dsn="mysql:host=localhost;dbname=test;charset=UTF8";

try{

$pdo=newPDO($dsn,'root','root');

$sql="SELECT * from user";

return$pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);

}catch(PDOException$e){

echo'数据库连接错误:'.$e->getMessage();

exit();

}

}

}

//View.php(视图)

classView{

functionfetch(array $data){

$html="

$html.="ID号帐号密码

";

foreach($dataas$user):

$html.="

{$user['id']}";

$html.="

{$user['uname']}";

$html.="

{$user['pwd']}";

endforeach;

$html.="

";

return$html;

}

}

//Controller.php(控制器)

require_once'Model.php';

require_once'View.php';

classController

{

// 参数注入当前方法中,仅给当前方法使用

functionindex(Model$model,View$view)

{

return$view->fetch($model->getData());

}

}

// 测试输出代码

$model=newModel();

$view=newView();

echo(newController())->index($model,$view);

二、依赖注入(参数注入)

在MVC模式中控制器Controller类中依赖模型类Model实例对象和视图类View实例对象function index(Model $model, View $view),此时我们可以在 当前成员方法中使用参数注入的方式,解决在当前类中使用其它类实例对象的问题,参数注入其实就是参数传递,它可以传递普通变量,也可是对象、闭包。

若是其它类的实例对象需要在当前类的众多成员方法中使用时( 共享或复用 ),若仍然采用将对象参数注入到成员方法中,将要多处写参数注入,此时建议方案就是能完 构造函数的参数注入 方式,将其它类的实例对象赋值给类的成员变量,这样当前类的所有成员方法都可访问,从而实现了其它类的共享或复用。

// 类的共享或复用

classController

{

private$model=null;

private$view=null;

// 参数注入构造函数中,赋值给类的成员变量,便于类的成员方法访问(共享或复用)

function__construct(Model$model,View$view)

{

$this->model=$model;

$this->view=$view;

}

functionindex()

{

return$this->view->fetch($this->model->getData());

}

}

$model=newModel();

$view=newView();

echo(newController($model,$view))->index();

补充: 以前看框架时,说优势之一就是使用依赖注入,后来经老师演示,原来就是参数传递,不得不说,好多文档说明故意提高了理解的门槛,看来理解基础再来学框架是最正确的学习路径。

三、服务容器(Container)

如果当前类依赖的对象较多,或者项目中有许多对象和类需要在项目中反复调用,可以将当前依赖的外部对象放到一个”服务容器”中进行统一管理。如TP6框架的Container类,服务容器最基本要有三个成员:对象容器(对象数组)、绑定对象方法和取对象方法。

// 如果当前类依赖的对象较多,可以将当前依赖的外部对象放到一个"服务容器"中进行统一管理

// 服务容器: 一个自动产生类/对象的工厂

classContainer

{

// 1、对象容器(对象数组)

protected$instance=[];

/**

* 2、绑定对象:对象容器中添加对象

* $abstract :类标识, 接口, 是外部对象在当前容器数组中的键名/别名 alias *

* $concrete : 要绑定的类, 闭包或者实例, 传入一个对象或者一个闭包,前者需要我们先实例化对象才能往对象容器中添加, 闭包优势:我们使用这个对象的时候,才实例化对象

*/

functionbind($abstract,Closure$concrete)

{

$this->instance[$abstract]=$concrete;

}

//3.从对象容器中取出对象, 调用

functionmake($abstract,$args=[])

{

returncall_user_func_array($this->instance[$abstract],$args);

}

}

此时依赖注入就不是一个个类了,而是包含这些类的服务容器类Container,其实质仍然是引用类的实例对象,只不过这些实例对象通过bind方法保存到容器类中对象数组中,使用时使用make方法取出即可。

// Controller2.php

require_once'Model.php';

require_once'View.php';

require_once'../Container.php';

// 类的共享或复用

classController

{

private$model=null;

private$view=null;

// 参数注入构造函数中,赋值给类的成员变量,便于类的成员方法访问(共享或复用)

function__construct(Container$container)

{

$this->model=$container->make('model');

$this->view=$container->make('view');

}

functionindex()

{

return$this->view->fetch($this->model->getData());

}

}

$container=newContainer();

$container->bind('model',function(){

returnnewModel();

});

$container->bind('view',function(){

returnnewView();

});

echo(newController($container))->index();

四、self、parent和static探讨

本来要讲Facade(门面)技术的,但它静态实现依赖static的后期静态绑定,自PHP 5.3.0起,PHP增加了一个叫做后期静态绑定的功能,用于在继承范围内引用静态调用的类。在群里也咨询老师了,老师的解释是后期静态绑定,是在继承上下文中,self只能和声明类进行绑定,并不能和调用类进行绑定。,同时也查阅了PHP官方的解释,最终梳理成以下几个理解点:

1、self、parent和static到底代表谁?

首先三者都是相当于魔术变量,在实际编译时会指向具体的类,都不能在类外调用,只能在类中使用。至于老师说的声明类和调用类,如何区分声明类和调用类就不好界定。通过代码测试,我的发现它们区别就是 查找类中成员范围起点不同 。

self:从当前类中开始查找成员,若有(重写时)则结束查找,没有(未重写时)则依次向父类查找成员,直到找到为止。如下面代码中self::who();就是先从B类中查找成员,刚好有就输出B

parent:直接跳转当前类中成员,即使重写也忽略,从父类开始查找成员,后面查找同self,唯一不同就是起点。如下面代码中parent::who();虽然B类已经定义who方法,但它直接从父类A开始查找,找到who方法,输出为A。

static:若是static访问成员,则是从明确指定类名开始查找方法。如下面代码中parent::foo();self::foo();按parent和self规则其实最终找到就是A类中foo方法,但它是static访问成员,所以它要用明确指明类来替换,而下面代码是C::test()导致这种查找,因为C类中没有tes方法,它从父类B中找到,此时明确调用类是C不是B,这点最容易误导。

下面是基于PHP官方区分self、parent和static的代码进行修改版,可以认清三者的查找成员时起点:

classA{

publicstaticfunctionfoo(){

static::who();

}

publicstaticfunctionwho(){

echo __CLASS__."
";

}

}

classBextendsA{

publicstaticfunctiontest(){

// 指明具体类

A::foo();

// 调用static后期绑定静态成员

parent::foo();

self::foo();

// parent和self区别

parent::who();

self::who();

}

publicstaticfunctionwho(){

echo __CLASS__."
";

}

}

classCextendsB{

publicstaticfunctionwho(){

echo __CLASS__."
";

}

}

C::test();

e96d7bce0a19d0d1ecf6e933296c8138.png

补充: 正如图片中所说,static也同self和parent,只是指明 查找成员起点不同 而已。若上面代码中C类没有重写who方法,即注释掉C类中who方法,此时由于C类没有who方法,C类调用父类B中who方法,此时输出是:A B B A B。

2、转发调用与非转发调用转发调用:指的是通过以下几种方式进行的静态调用 :self::,parent::,static::以及 forward_static_call()。

非转发调用:明确指定类名的静态调用 。例如Foo::foo();

上面是某网文对两种调用的解释,其中第一个是官方的解释,但我认为它们只是表面认识。经测试 两种调用都是针对是否重写继承父类的成员 来说的。若是重写了父类的成员,则明确指定类名或self::访问都直接访问当前类中成员,终止了向上查找,是非转发调用。而没有重写父类的成员则无论是指定类名,还是上面几种都是转发调用,上面代码中C::test()虽然指定类名了,但它转发调用B类中test方法。

3、后期静态绑定

官方解释说:存储了在上一个“非转发调用”(non-forwarding call)的类名。其实我觉得关键词是两个”非转发调用”和”类名”,上面代码中C::test()输出static::who();是从C类开始查找who成员方法,结果找到了,所以此时”非转发调用的类名”是C,若是C类中没有重写who方法(即注释掉C类的who方法),在B类中找到who成员方法,此时”非转发调用的类名”是B。所以 “非转发调用的类名”是由指定类名开始的,由继承关系的类组成的队列。下面是官方代码的解析过程,可以具体理解下三者作用。

*C::test();//非转发调用 ,进入test()调用后,“上一次非转发调用”存储的类名为C

*

*//当前的“上一次非转发调用”存储的类名为C

*publicstaticfunctiontest(){

*A::foo();//非转发调用, 进入foo()调用后,“上一次非转发调用”存储的类名为A,然后实际执行代码A::foo(), 转 0-0

*parent::foo();//转发调用, 进入foo()调用后,“上一次非转发调用”存储的类名为C, 此处的parent解析为A ,转1-0

*self::foo();//转发调用, 进入foo()调用后,“上一次非转发调用”存储的类名为C, 此处self解析为B, 转2-0

*}

*

*0-0

*//当前的“上一次非转发调用”存储的类名为A

*publicstaticfunctionfoo(){

*static::who();//转发调用, 因为当前的“上一次非转发调用”存储的类名为A, 故实际执行代码A::who(),即static代表A,进入who()调用后,“上一次非转发调用”存储的类名依然为A,因此打印 “A”

*}

*

*1-0

*//当前的“上一次非转发调用”存储的类名为C

*publicstaticfunctionfoo(){

*static::who();//转发调用, 因为当前的“上一次非转发调用”存储的类名为C, 故实际执行代码C::who(),即static代表C,进入who()调用后,“上一次非转发调用”存储的类名依然为C,因此打印 “C”

*}

*

*2-0

*//当前的“上一次非转发调用”存储的类名为C

*publicstaticfunctionfoo(){

*static::who();//转发调用, 因为当前的“上一次非转发调用”存储的类名为C, 故实际执行代码C::who(),即static代表C,进入who()调用后,“上一次非转发调用”存储的类名依然为C,因此打印 “C”

*}

4、后期静态绑定解决单例继承问题classA

{

protectedstatic$_instance=null;

staticpublicfunctiongetInstance()

{

if(self::$_instance===null){

self::$_instance=newself();

}

returnself::$_instance;

}

}

classBextendsA

{

}

classCextendsA{

}

$a=A::getInstance();

$b=B::getInstance();

$c=C::getInstance();

var_dump($a);

var_dump($b);

var_dump($c);

f8d3dc7b12701fb21a6807c2a186a40e.png

上面单例继承问题可通过static::解决,即getInstance()方法中self都替换成static就可以。在TP6框架的服务容器Container类中也是通过static::解决单例继承问题的。

//TP6的Container.php中实例方法

publicstaticfunctiongetInstance()

{

if(is_null(static::$instance)){

static::$instance=newstatic;

}

if(static::$instanceinstanceofClosure){

return(static::$instance)();

}

returnstatic::$instance;

}

五、Facade(门面)

门面类Facade其实相当于中间层,先对想静态调用的类定义继承于门面类的子类,然后在该子类中定义相应的静态方法,实质是再调用对应类的非静态方法。为了相同类名,我参考了TP6框架的做法,将门面类统一命名空间为Facade。下面演示是基于服务容器类的门面类,单独类需要门面类似定义即可。

// Facade.php

namespaceFacade;

require_once'Container.php';

// 门面类

classFacade

{

protectedstatic$container=null;

staticfunctioninstance(\Container $container)

{

static::$container=$container;

}

}

// 需要静态调用的类继承门面类,为了统一,和原类名相同

classModelextendsFacade

{

staticfunctiongetData()

{

returnstatic::$container->make('model')->getData();

}

}

classViewextendsFacade

{

staticfunctionfetch($data)

{

returnstatic::$container->make('view')->fetch($data);

}

}

上面已经定义了基于服务容器类Container的门面类,将服务容器的对象数组成员赋值给Facade的静态成员,从而被所有继承Facade类的子类共享。

// Controller3.php

require_once'Facade.php';

require_once'Model.php';

require_once'View.php';

useFacade\Facade;

useFacade\Model;

useFacade\View;

// 类的共享或复用

classController

{

function__construct(Container$container)

{

Facade::instance($container);

}

functionindex()

{

returnView::fetch(Model::getData());

}

}

$container=newContainer();

$container->bind('model',function(){

returnnew\Model();

});

$container->bind('view',function(){

returnnew\View();

});

echo(newController($container))->index();

补充: 在我的Githubhttps://github.com/woxiaoyao81/phpcn13或Giteehttps://gitee.com/freegroup81/phpcn13可以下载到参考TP6运行模式的《PHP从零开始写MVC》压缩包源码,我是在从TP官网论坛下载到的,可以作为本博文的进阶练习。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值