核心要点
在应用程序开发中,我们尝试创建独立的模块,以便在未来的项目中重用代码。但是,创建提供有用功能的完全独立的模块很困难;除非正确管理它们的依赖关系,否则它们可能会导致维护噩梦。这就是依赖注入派上用场的地方,因为它使我们能够注入代码正常运行所需的依赖项,而无需将它们硬编码到模块中。Pimple是一个简单的依赖注入容器,它利用PHP的闭包以可管理的方式定义依赖项。在本文中,我们将探讨硬编码依赖项的问题,依赖注入如何解决这些问题,以及如何使用Pimple来使利用依赖注入的代码更易于维护。
具体依赖项的问题
在编写应用程序时,我们会使用许多PHP类。一个类可能需要调用一个或多个其他类的方法来提供预期的功能,因此我们说第一个类依赖于其他类。例如:
<?php class A { public function a1() { $b = new B(); $b->b1(); } }
类A依赖于类B。如果类B不可用,则上述代码将无法工作。此外,每次我们在类中硬编码对象的创建时,我们都会对该类产生具体的依赖关系。具体依赖关系是编写可测试代码的障碍。更好的方法是向类A提供类B的对象。这些对象可以通过A的构造函数或setter方法提供。在我们进一步讨论之前,让我们来看一个更现实的场景。
如今,在社交网络网站上共享内容非常普遍,大多数网站都在其网站上直接显示其社交资料提要。假设我们有一个名为SocialFeeds的类,它从Twitter、Facebook、Google 等社交网站生成提要。创建单独的类来处理这些服务中的每一个。在这里,我们将研究与Twitter交互的类TwitterService。SocialFeeds类使用TwitterService请求Twitter提要。TwitterService与数据库交互以检索访问API的特定用户令牌。令牌传递给OAuth类,该类使用提供的令牌检索提要并将其返回给SocialFeeds类。
<?php class A { public function a1() { $b = new B(); $b->b1(); } }
<?php class SocialFeeds { public function getSocialFeeds() { $twService = new TwitterService(); echo $twService->getTweets(); } }
<?php class TwitterService { public function getTweets() { $db = new DB(); $query = "Query to get user token from database"; $token = $db->getQueryResults($query); $oauth = new OAuth(); return $oauth->requestTwitterFeed($token); } }
<?php class OAuth { public function requestTwitterFeed($token) { // Retrieve and return twitter feed using the token } }
很明显,SocialFeeds依赖于TwitterService。但是TwitterService依赖于DB和OAuth,因此SocialFeeds间接依赖于DB和OAuth。那么问题是什么呢?SocialFeeds依赖于三个类的具体实现,因此不可能在没有其他类的真实实现的情况下单独测试SocialFeeds。或者,假设我们想使用不同的数据库或不同的OAuth提供程序。在这种情况下,我们必须在整个代码中用新类替换现有类。
修复具体依赖项
解决这些依赖项问题的方案很简单,即在必要时动态提供对象,而无需使用具体实现。有两种类型的技术可以注入依赖项:基于构造函数的依赖注入和基于设置器的注入。
基于构造函数的注入
使用基于构造函数的依赖注入,依赖对象是在外部创建的,并作为参数传递给类的构造函数。我们可以将这些对象分配给类变量,并在类内任何地方使用。SocialFeeds类的基于构造函数的注入如下所示:
<?php class DB { public function getQueryResults($query) { // Get results from database and return token } }
TwitterService的实例作为对象传递给构造函数。SocialFeeds仍然依赖于TwitterService,但现在我们可以自由地提供不同版本的Twitter服务提供程序,甚至可以提供用于测试目的的模拟对象。关于TwitterService,DB和OAuth类也以类似的方式定义。
<?php class SocialFeeds { public $twService; public function __construct($twService) { $this->twService = $twService; } public function getSocialFeeds() { echo $this->twService->getTweets(); } }
基于设置器的注入
使用基于设置器的注入,对象是通过setter方法而不是构造函数提供的。以下是SocialFeeds类的基于设置器的依赖注入实现:
<?php $db = new DB(); $oauth = new OAuth(); $twService = new TwitterService($db, $oauth); $socialFeeds = new SocialFeeds($twService); $socialFeeds->getSocialFeeds();
现在包括DB和OAuth的初始化代码如下所示:
<?php class SocialFeeds { public $twService; public function getSocialFeeds() { echo $this->twService->getTweets(); } public function setTwitterService($twService) { $this->twService = $twService; } }
构造函数与设置器注入
选择基于构造函数的注入还是基于设置器的注入取决于您。当需要所有依赖项才能实例化类时,基于构造函数的注入是合适的。当并非每次都需要依赖项时,基于设置器的注入是合适的。
优点
缺点
了解了依赖注入和各种注入技术后,是时候看看Pimple以及它如何融入其中了。
Pimple在DI中的作用
当我们已经可以使用前面提到的技术注入依赖项时,您可能想知道为什么需要Pimple。要回答这个问题,我们需要看看DRY原则。
不要重复自己(DRY)是软件开发的一个原则,旨在减少各种信息的重复,这在多层架构中特别有用。DRY原则的陈述是“每个知识片段都必须在一个系统中具有单个、明确、权威的表示”——维基百科
考虑基于构造函数的注入示例。每次我们想要SocialFeed类的对象时,我们都必须重复实例化和传递其依赖项的整个设置过程。根据DRY,应避免此类代码以防止维护出现问题。Pimple充当定义此类依赖项以避免重复的容器。让我们来看一个简单的例子,看看Pimple是如何工作的。
<?php class A { public function a1() { $b = new B(); $b->b1(); } }
创建Pimple的实例充当存储依赖项的容器。它实现SPL ArrayAccess接口,因此使用它与使用数组非常相似。首先,我们定义了一个键,该键保存我们想要的某个任意类的名称。然后,我们定义了一个闭包来返回指定类的实例,该实例充当服务。请注意,将向$c传递容器的实例,因此我们可以根据需要引用其他已定义的键;每个已定义的参数或对象都可通过$c变量在闭包中使用。现在,每当我们想要类的实例时,我们都可以引用键来检索对象。让我们将SocialFeeds示例转换为Pimple。Pimple官方网站上的示例显示了基于构造函数的注入,因此在这里我们将说明基于设置器的注入。请记住,为了使用Pimple,我们不需要修改前面定义的任何setter方法或代码——我们只是封装了逻辑。
<?php class SocialFeeds { public function getSocialFeeds() { $twService = new TwitterService(); echo $twService->getTweets(); } }
DB和OAuth类都是独立的模块,因此我们直接在闭包内返回它们的新的实例。然后,我们使用基于设置器的注入向TwitterService类添加依赖项。我们已经将DB和OAuth类添加到容器中,因此我们可以使用$c['db']和$c['oauth']直接在函数内访问它们。现在,依赖项作为服务封装在容器内。每当我们想要使用不同的DB类或不同的OAuth服务时,我们只需替换容器语句中的类,一切都会完美运行。使用Pimple,您只需要在一个地方添加新的依赖项。
高级Pimple用法
在上述场景中,Pimple会在每次请求时从闭包返回每个类的新的实例。在某些情况下,我们需要使用相同的对象而无需每次都初始化新的实例,例如连接到数据库就是一个完美的例子。Pimple提供了使用共享对象返回相同实例的能力,这样做需要我们通过share()方法指定闭包,如下所示:
<?php class A { public function a1() { $b = new B(); $b->b1(); } }
此外,到目前为止,我们已经在Pimple容器中的单个位置定义了所有依赖项。但是,考虑一下我们需要具有其依赖项的服务,但配置方式与原始服务略有不同的情况。例如,假设我们需要访问ORM来实现TwitterService类的某些功能。我们不能更改现有的闭包,因为它会强制所有现有功能使用ORM。Pimple提供extend()方法来动态修改现有闭包,而不会影响原始实现。考虑以下代码:
<?php class SocialFeeds { public function getSocialFeeds() { $twService = new TwitterService(); echo $twService->getTweets(); } }
现在,我们能够在特殊情况下使用tweet_service的不同扩展版本。第一个参数是服务的名称,第二个参数是一个函数,该函数可以访问对象实例和容器。实际上,extend()是动态添加依赖项以适应不同情况的强大方法,但请确保将服务的扩展版本限制在最低限度,因为它会增加重复代码的数量。
总结
管理依赖项是Web应用程序开发中最重要和最困难的任务之一。我们可以使用构造函数和setter方法的依赖注入来有效地管理它们。但是,依赖注入本身也有一些麻烦,Pimple通过提供一个轻量级容器来以DRY的方式创建和存储对象依赖项来解决这些问题。请随时在下面的评论中分享您在项目中管理依赖项的经验,以及您对Pimple作为依赖注入容器的看法。
关于使用Pimple进行依赖注入的常见问题解答 (FAQ)
Pimple是一个简单的PHP依赖注入容器,允许您管理和集中应用程序中的服务。它用于PHP,使代码更灵活、更可重用和更易于测试。通过使用Pimple,您可以在一个地方实例化对象,然后将它们注入到应用程序的不同部分,从而减少对全局状态的需求,并使您的代码更易于维护和测试。
Pimple通过在容器中存储服务定义来工作。这些定义是可以调用(函数或方法)的,它们返回服务的实例。当您从容器访问服务时,Pimple会执行服务定义以创建服务对象。这允许您以集中方式管理服务,并在整个应用程序中共享服务。
可以使用Composer(PHP的依赖项管理工具)安装Pimple。您可以在系统上全局安装Composer,然后通过运行命令composer require pimple/pimple
在项目中引入Pimple。
在Pimple中,您可以通过将可调用对象分配给容器中的键来定义服务。可调用对象应返回服务的实例。例如,您可以像这样为邮件发送器类定义服务:
$container['mailer'] = function ($c) { return new Mailer($c['smtp']); };
在此示例中,邮件发送器服务定义为Mailer类的新的实例,其中smtp服务作为依赖项注入。
您可以使用带有服务键的数组表示法来访问Pimple中的服务。例如,您可以像这样访问邮件发送器服务:$mailer = $container['mailer'];
。当您访问服务时,Pimple会执行服务定义并返回服务对象。
默认情况下,Pimple每次访问服务时都会返回服务的新的实例。如果您想共享服务并每次返回相同的实例,可以使用share()
方法。例如,您可以像这样共享邮件发送器服务:$container['mailer'] = $container->share(function ($c) { return new Mailer($c['smtp']); });
。
是的,您可以使用extend()
方法扩展Pimple中的服务。这允许您在定义服务后修改它。例如,您可以像这样扩展邮件发送器服务以添加其他配置:
$container['mailer'] = $container->extend('mailer', function ($mailer, $c) { $mailer->setFrom($c['email.from']); return $mailer; });
在此示例中,setFrom()
方法使用email.from
服务作为参数在邮件发送器服务上调用。
在Pimple中,您可以使用protect()
方法保护参数(不应被视为服务的参数)。这允许您在容器中存储值,而不会将它们视为服务定义。例如,您可以像这样保护配置值:$container['config.value'] = $container->protect(function () { return 'value'; });
。
您可以通过创建PimpleContainer类的新的实例并在其中定义服务来在一个项目中使用Pimple。然后,您可以在应用程序中需要的地方从容器访问服务。这允许您以集中方式管理服务并将它们注入到应用程序的不同部分。
Pimple为PHP开发提供了许多好处。它使您的代码更灵活,因为它允许您以集中方式管理服务。它使您的代码更易于重用,因为它允许您在整个应用程序中共享服务。它使您的代码更易于测试,因为它允许您注入模拟服务进行测试。通过使用Pimple,您可以提高代码质量,并使其更易于维护和测试。
以上是PHP主|用丘疹注入依赖的详细内容。更多信息请关注PHP中文网其他相关文章!