依赖注入与pimple

在应用程序开发中,我们尝试创建独立的模块,以便我们可以在将来的项目中重用代码。但是,很难创建完全独立的模块来提供有用的功能。它们的依赖关系可能导致维护方面的噩梦,除非对其进行适当的管理。这就是依赖关系注入派上用场的地方,因为它使我们能够注入代码正常运行所需的依赖关系,而无需将其硬编码到模块中。

Pimple是一个简单的依赖项注入容器,它利用PHP的闭包以可管理的方式定义依赖项。在本文中,我们将研究硬编码依赖关系的问题,依赖关系注入如何解决它们,以及如何使用Pimple保持利用依赖关系注入的代码的可维护性。

具体依赖问题

在编写应用程序时,我们使用许多PHP类。一个类可能需要调用一个或多个其他类的方法来提供预期的功能,因此我们说第一类依赖于其他类。例如:

<?php
class A
{
    public function a1() {
        $b = new B();
        $b->b1();
    }
}

class A取决于class B。如果class B不可用,则以上代码将不起作用。

而且,每当我们在一个类内对对象的创建进行硬编码时,我们都会对该类进行具体的依赖。具体的依赖关系是编写可测试代码的障碍。更好的方法是为class A提供class B的对象。这些对象可以通过提供A的构造函数或setter方法。

在继续之前,让我们看一个更现实的场景。

 

如今,在社交网站上共享内容非常普遍,并且大多数网站都在其网站上直接显示其社交资料。假设我们有一个名为SocialFeeds的类,该类从诸如Weibo,Wechat,等社交网站生成提要。将创建单独的类来与这些服务中的每一个一起使用。在这里,我们将看一下与Weibo交互的类WeiboService

SocialFeeds类要求使用Weibo的提供WeiboService。WeiboService与数据库进行交互以检索特定的用户token以访问API。token传递给OAuth类,该类使用提供的token检索提要,并将其返回给SocialFeeds类。

<?php
class SocialFeeds
{
    public function getSocialFeeds() {
        $twService = new WeiboService();
        echo $twService->getTweets();
    }
}
<?php
class WeiboService
{
    public function getTweets() {
        $db = new DB();
        $query = "Query to get user token from database";
        $token = $db->getQueryResults($query);

        $oauth = new OAuth();
        return $oauth->requestWeiboFeed($token);
    }
}
<?php
class OAuth
{
    public function requestWeiboFeed($token) {
        // Retrieve and return Weibo feed using the token    		
    }
}
<?php
class DB
{
    public function getQueryResults($query) {
        // Get results from database and return token
    }
}

显然,这SocialFeeds取决于WeiboService。但是,WeiboService取决于DBOAuthSocialFeeds依赖于两个DBOAuth间接的影响。

那么会有什么问题呢?SocialFeeds取决于三个类的具体实现,因此如果SocialFeeds没有其他类的实际实现,则不可能作为一个单独的单元进行测试。或者,假设我们要使用其他数据库或其他OAuth提供程序。在这种情况下,我们在每次代码中每次出现时都必须用新类替换现有类。

修复依赖性

解决这些依赖性问题的方法很简单,就是在需要时不使用具体实现就动态地提供对象。注入依赖关系有两种技术:基于构造函数的依赖关系注入和基于setter的注入。

基于Constructor的注入

通过基于Constructor的依赖注入,可以在外部创建依赖对象,并将其作为参数传递给类的构造函数。我们可以将这些对象分配给类变量,并在类中的任何地方使用。SocialFeeds该类的基于Constructor的注入如下所示:

<?php
class SocialFeeds
{
    public $wbService;

    public function __construct($wbService) {
        $this->wbService = $wbService;
    }

    public function getSocialFeeds() {
        echo $this->wbService->getTweets();
    }
}

WeiboService的实例作为对象传递给构造函数。SocialFeeds仍然依赖WeiboService,但是现在我们可以自由地提供不同版本的Weibo,甚至可以提供模拟对象用于测试目的。在DBOAuth类对于类似的规定WeiboService

<?php
$db = new DB();
$oauth = new OAuth();
$wbService = new WeiboService($db, $oauth);
$socialFeeds = new SocialFeeds($wbService);
$socialFeeds->getSocialFeeds();

基于Setter的注入

对于基于setter的注入,对象是通过setter方法而不是Constructor提供的。这是SocialFeeds该类的基于setter的依赖项注入的实现:

<?php
class SocialFeeds
{
    public $wbService;

    public function getSocialFeeds() {
        echo $this->wbService->getTweets();
    }

    public function setWeiboService($wbService) {
        $this->wbService = $wbService;
    }
}

初始化代码包括DBOAuth

<?php
$db = new DB();
$oauth = new OAuth();
$wbService = new WeiboService();
$wbService->setDB($db);
$wbService->setOAuth($oauth);

$socialFeeds = new SocialFeed();
$socialFeeds->setWeiboService($wbService);
$socialFeeds->getSocialFeeds();

 Constructor vs  Setter 

由你决定是在Constructor还是基于setter的注入之间进行选择。当实例化类需要所有依赖项时,基于Constructor的注入是合适的。当不需要在每种情况下都依赖时,基于Setter的注入是合适的。

优点

  • Constructor–只需查看类的构造函数即可识别类的所有依赖关系
  • Setter –添加新的依赖项就像添加新的setter方法一样简单,这不会破坏现有代码

缺点

  • Constructor–添加新的依赖关系会增加构造函数的参数;现有代码需要在我们的整个应用程序中进行更新,以提供新的依赖关系
  • Setter-我们必须手动搜索必要的依赖项,因为它们在任何地方都没有指定

有了依赖注入和各种注入技术的知识,现在该看看Pimple并了解它的适用范围。

Pimple在依赖注入的使用

您可能想知道为什么当我们可以使用前面提到的技术注入依赖项时,为什么需要Pimple。要回答这个问题,我们需要参考DRY原理。

不要自己重复 Don’t Repeat Yourself (DRY)是软件开发的原则,旨在减少各种信息的重复,在多层体系结构中尤其有用。DRY原则表示为“系统中的每条知识都必须具有单一,明确,权威的表示形式

考虑基于构造函数的注入示例。每次我们想要一个SocialFeed类的对象时,我们都必须重复实例化和传递其依赖关系的整个设置过程。根据DRY,应避免使用此类代码以防止维护麻烦。Pimple充当定义此类依赖项以避免重复的容器。

让我们看一个简单的例子,看看Pimple是如何工作的。

<?php
$container = new Pimple();
$container['class_name'] = 'Test';
$container['object_name'] = function ($c) {
    return new $c['class_name']();
};

$testClass = $container['object_name'];

Pimple创建的实例以用作存储依赖项的容器。它实现了SPL ArrayAccess接口,因此使用它与使用数组非常相似。首先,我们定义了一个键,其中包含我们想要的任意类的名称。然后,我们定义了一个闭包以返回指定类的实例,该实例充当服务。注意,$c将传递一个容器的实例,因此我们可以根据需要引用其他定义的键。每个定义的参数或对象都可以通过$c变量在闭包中使用。现在,只要我们想要该类的实例,就可以引用该键以检索该对象。

让我们将SocialFeeds示例转换为Pimple。Pimple官方网站上的示例显示了基于构造函数的注入,因此这里我们将说明基于setter的注入。请记住,使用Pimple不需要修改我们先前定义的任何setter方法或代码,我们只需封装逻辑即可。

<?php
$container = new Pimple();
$container['oauth'] = function($c) {
	return new OAuth();
};
$container['db'] = function($c) {
	return new DB();
};
$container['tweet_service'] = function($c) {
	$wbService = new WeiboService();
	$wbService->setDB($c['db']);
	$wbService->setOauth($c['oauth']);
	return $wbService;
};
$container['social_feeds'] = function($c) {
	$socialFeeds = new SocialFeeds();
	$socialFeeds->setweiboService($c['tweet_service']);
	return $socialFeeds;
};

$socialFeeds = $container['social_feeds'];
$socialFeeds->getSocialFeeds();

这两个DBOAuth类是独立的模块,所以我们直接封内返回他们的新实例。然后,我们WeiboService使用基于setter的注入将依赖项添加到类中。我们已经将DBOAuth类添加到了容器中,因此我们可以使用$c['db']和直接在函数内部访问它们$c['oauth']

现在,依赖项作为服务封装在容器内。每当我们想要使用不同的DB类或不同的OAuth服务时,我们只需替换容器语句中的类即可,一切将正常运行。使用Pimple,您只需要在一个地方添加新的依赖项。

Pimple进阶用法

在上述情况下,只要有一个请求,Pimple都会从闭包中返回每个类的新实例。在某些情况下,我们需要使用同一对象而无需每次都初始化新实例,例如,连接数据库是一个很好的例子。

Pimple提供了使用共享对象返回相同实例的功能,这样做需要我们通过share()如下所示的方法指定闭包:

<?php
$container['db'] = $container->share(function ($c) {
    return new DB();
});

同样,到目前为止,我们已经在Pimple容器内的单个位置定义了所有依赖项。但是考虑一下这样一种情况,我们需要具有依赖项但配置方式与原始配置略有不同的服务。例如,假设我们需要访问WeiboService该类的某些功能的ORM 。我们无法更改现有的闭包,因为它将强制所有现有的功能使用ORM。

Pimple提供了一种extend()在不影响原始实现的情况下动态修改现有闭包的方法。代码:

<?php
$container['tweet_service'] = $container->extend('tweet_service', function($twSservice, $c) {
	$wbService->setDB(new ORM());
	return $wbService;
});

现在,我们可以tweet_service在特殊情况下使用不同的扩展版本。第一个参数是服务的名称,第二个参数是用于访问对象实例和容器的函数。

确实,这extend()是一种动态添加依赖项以适应不同情况的有效方法,但是请确保将服务的扩展版本限制在最低限度,因为这会增加重复代码的数量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值