第十七节:PHP依赖注入

PHP面向对象编程基础

PHP依赖注入,将会用到很多魔术方法,这只是一个聪明的方法来解耦(去耦合)你的代码。依赖注入是种实现控制反转用于解决依赖性设计模式。一个依赖关系指的是可被利用的一种对象(即服务提供端) 。依赖注入是将所依赖的传递给将使用的从属对象(即客户端)。该服务是将会变成客户端的状态的一部分。传递服务给客户端,而非允许客户端来建立或寻找服务,是本设计模式的基本要求,基于接口编程。

PHP依赖注入-实例演示

PHP依赖注入-实例演示

PHP依赖注入-实例演示

我们将以一个例子为例,我有一个地址库,我想显示一个单一的地址,以及属于我的Web应用程序内的谷歌地图的地址,在Jiexi/App创建一个Address.php文件,这个类将有单个地址的属性,所以让我们开始创建这些属性,protected的street,city属性,创建一个单一Find的public方法,传id参数,我只要取一个地址,我将使用该地址来填充属性,接着返回整个对象,所以我就return this,现在我不想在这里建立一个ORM(Object Relational Mapping 对象关系映射)。这当然不是最好的办法,这只是为了演示依赖注入,我们稍后会介绍,代码如下:

Address.php

好的,现在我们所有的地址被存到一个存储库里面,在该存储库可以是任何东西。让我们先创建一个接口来定义我们可以用来获取和存储地址的方法。所以在你的Repository文件夹中,我将创建一个新对象接口(interface),叫它做AddressRepositoryInterface.php,就像我们之前说过,这个对象接口将纯粹起到合约的作用,所以它会告诉所有实现这个接口的类,不要忘记你必须定义这个和那个方法,让我们保持简单,我们将创建一个Find的单一公共方法将接受一个id作为参数:

AddressRepositoryInterface.php

回到Repositories文件夹,创建一个新的类实现AddressRepositoryInterface,这个类叫addresses非常简单就包含一个php数组,我们叫AddressesArrayRepository类,我们要实现AddressRepositoryInterface,首先我们调用一个受保护的属性叫Addresses,当然,为了坚持我们的合约,我们需要实现一个Find方法,像对象接口,我们应该用一个ID作为一个参数。我们还要创建__construct构造函数,还有protected方法叫getAddresses,这只会返回一个数组,这个数组将会包含我们存储库中的每个地址。直接复制已经写好的地址:

AddressArrayRepository.php

现在我们需要创建AddressArrayRepository.php把AddressArrayRepository方法用在我们的Address类中:
Address.php

在这里引用accesssible特征是非常不错的,把use\jiexi\App\Traits\Accessible添加到address类的protected上面,让我们保护自己免受大规模的分配:

接着我们遍历这些属性:

在改进上述这些代码之前,看是否正常工作,在index.php添加代码:

我们所做的就是用这个$addressModel ,并启动在address的Find方法,现在这个Find方法又接受了addressRepository,这等于我们在这里创建的addressArrayRepository。它通过传递的id找到地址,并设置街道和城市,返回自己。

这代码看起来很好,但是两个地方错误:

$this->addressRepository = new \Jiexi\App\Repositories\AddressArrayRepository();通过在我们的addressModel中创建一个新的addressArrayRepository来看到,我们将这个模型紧密地耦合到AddressArrayRepository。换句话说,我们让addressModel知道使用哪个存储库。想一想,如果知道的话,当然不知道,因为我们创建了一个接口,它只需要知道它可以访问一个Find方法,这是在这里接口定义的。所以在将来我们应该可以把它换成地址数据库存储库或地址JSON存储库,或者其它别的什么。

而这与addressModel无关,所以不行,我们不应该在这里实例化这个addressRepository,但那我们怎么解决呢?如果我们将addressRepository传递给构造函数呢?会有帮助吗?回到Address.php做修改:

所以让我们确保我们有一个addressRepository实际传递给这个模型,回到index.php设置一个$addressRepository变量,在把$addressRepository 这个变量传给它,这个就是依赖注入,而不是实例化类中的外部类,只需将它们传递给构造函数即可,但没有类需要知道使用哪个实现:

让我们检查是否刷新index.php:

让我们进一步去学习依赖注入,使用依赖注入,可以在此构造函数(__construct)中键入提示我们在此处接收到的地址存储库,我们需要做的就是在我们的变量前加上addressArrayRepository,现在只要我们使用addressArrayRepository的一个实例是可以的。但是如果我们要传递一个不同类型的类,比如说一个postJSONRepository:

Address.php

index.php

报错:
Catchable fatal error: Argument 1 passed to Jiexi\App\Address::__construct() must be an instance of Jiexi\App\AddressArrayRepository, instance of Jiexi\App\Repositories\PostJsonRepository given, called in D:\xampp\htdocs\phpoop\dependency_injection\index.php on line 7 and defined in D:\xampp\htdocs\phpoop\dependency_injection\Jiexi\App\Address.php on line 14

让我们改回来$addressRepository = new \Jiexi\App\Repositories\AddressJsonRepository();但现在我们有一个不同的问题,如果我们要创建实现地址库的另一个接口,让我们看会发生什么?创建一个新类AddressJsonRepository,添加Find方法:

返回到index.php我们将实例化一个AddressJsonRepository:

还是报错,因为AddressArrayRepository,而相反,我们正在获取AddressJsonRepository,所以再次紧密耦合,在我们的地址模型构造函数的内部,我们正在寻找一个AddressArrayRepository,这是我们实现一个接口,所以我们在这里address.php的$this->addressRepository = $addressRepository;删除了耦合,但是我们又在function __construct(AddressArrayRepository $addressRepository )构造函数哪里添加。所以等于说没有任何作用,幸运的是,我们没有为接口的实现类型提示,我们可以只为接口本身输入提示,我们将删除构造函数的AddressArrayRepository,在这里输入addressRepositoryInterface:

address.php

你需要一定的接口实现,你只是说好,你可以用任何实现该接口,上述方法是可以实现的,无论你使用AddressArrayRepository或者AddressJsonRepository。现在我们已经成功地从地址模型中解耦了这个存储库。我们可以在改进,我想我们可以把这个代码放在这里,把它移到我们的仓库,在Find方法,我们实际上传递了Address类本身的一个实例,所以我们可以用下面方法是这个工作:

修改AddressRepositoryInterface.php,首先让我们检查AddressRepositoryInterface的Find方法的,第一个参数是$id,第二参数想接受一个\Jiexi\App\Address $address:

同样把$id, \Jiexi\App\Address $address复制AddressArrayRepository.php和AddressJsonRepository的Find方法,在这里我们来看看这个地址,并确保我们设置这些属性,这很简单,因为我们需要做的就是在这里做一些foreach语句,只要把存储在这里的地址,然后循环通过该地址内的每个键,这将是街道和城市,然后我们只需要我们通过的地址变量($address),我们将设置一个名称为key的属性为值的值。然后删除return $this->addresses[$id];返回return $address:

AddressJsonRepository.php

修改AddressArrayRepository.php的Find方法

现在将这个功能从地址类移动到AddressArrayRepository类有什么好处?再一次,这与我们掌握的知识有关,如果我们这样构造我们的代码,地址模型不需要知道它的属性是如何填充的。你看到数组仓库可以用这种方式来填充它,但是也许AddressJsonRepository需要一个完全不同的方式来填充这些属性。

回到index.php,首先输出谷歌地图,访问maps.google.com,分享位置信息:

PHP依赖注入-额外知识点

PHP依赖注入-额外知识点

PHP依赖注入-额外知识点

在软件工程中,依赖注入是种实现控制反转用于解决依赖性设计模式。一个依赖关系指的是可被利用的一种对象(即服务提供端) 。依赖注入是将所依赖的传递给将使用的从属对象(即客户端)。该服务是将会变成客户端的状态的一部分。传递服务给客户端,而非允许客户端来建立或寻找服务,是本设计模式的基本要求。

假设你今天正使用 PHP 建立一个大型专案,这个时候会有许多的类别,并且互相依赖着,下面是一个登入系统的依赖注入范例。首先这个登入系统分成三个部分,一个是验证,一个是处理工作阶段,另一个是驱动这两个的「主程式」。

这是一个没有使用依赖注入的传统案例

这一个程式用来进行实作用途,一旦要求登入,这个主程式就会建立「验证」和「工作阶段」两个类别用以实作。

验证:在这里和数据库连线,并且检查帐号密码是否正确。

工作阶段:这个类别用来将使用者登入的阶段保存起来,让伺服器清楚地知道使用者登入了。

问题:当我们实作「主程式」的时候,并不知道主程式呼叫了什么(除非我们查看主程式的原始码),像下面这样实作就会意识到这个问题。

使用依赖注入的案例

依赖注入的观念就是将所有东西先在「外面」准备好,然后再带入「内部」的程式中,如此一来你就能够在检视程式码的时候,一目了然地知道这个程式依赖着哪些类别。

主要程式:现在让我们从头来过,这次「主程式」不自己实作类别,而是接受在外部建立好的类别。

然后实作这一个登入系统像这样:

这即是一个简易的依赖注入案例,但还有一些依赖的问题尚未解决,而这又牵扯到了依赖注入容器(DI Container)。

Last modified: 2019年2月3日

Previous Story

PHP面向对象编程基础

第十六节:php traits

php traits:PHP的单继承语言而准备的一种代码复用机制,Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method.

...