bbc / ipr-resolver
一个依赖解析库。
Requires
- php: >=5.5
Requires (Dev)
- phpunit/phpunit: ^4.0
- squizlabs/php_codesniffer: ^2.0
This package is not auto-updated.
Last update: 2024-09-15 01:35:25 UTC
README
这是一个用于普通PHP对象的简单需求解析系统。
安装
这是一个标准的Composer库,因此适用常规安装规则。
$ composer require bbc/ipr-resolver
此库需要 PHP 5.5 或更高版本,因为它使用了 生成器!
背景
对象通常相互依赖。这很麻烦。以一个独特的广播电台为例;我们有时在我们的页面上放置“品牌”对象(比如“Archers”)。然而,我们已经决定向观众展示该品牌的最新一集是最有用的。但这不是品牌元数据的一部分,所以我们必须单独调用它。我们最终得到这样一些东西
$brand = $this->fetchBrand('The Archers'); $brand->loadLatestEpisode(); $twig->renderBrand($brand);
如果你有一系列品牌并且需要循环遍历每个品牌,用它们的最新剧集来激活它们,这会变得更糟。现在工作很可能是在系列中进行的,并且肯定会使你的代码变得杂乱无章。
foreach ($brands as $brand) { // do this 50 times. $brand->loadLatestEpisode(); }
那么,如果最新剧集也必须进行数据调用以使其完整!这是一个噩梦。我该如何将依赖项注入这个链条呢?!
如果我们能够创建一个品牌对象,并说:“自己解决吧!”那会怎么样呢?是的,我们也这样想。
请允许我隆重推出:解析器。
使用方法
为了标记一个对象有依赖项,你需要实现 BBC\iPlayerRadio\Resolver\HasRequirements
接口,它有一个单独的方法: requires()
。
use BBC\iPlayerRadio\Resolver\HasRequirements; class Brand implements HasRequirements { ... public function requires(array $flags = []) { $episodes = (yield new LatestEpisodesForProgramme($this->getPID(), 3)); $this->latestEpisodes = ($episodes)? $episodes : []; } ... }
如果你以前从未遇到过 协程,这可能看起来有点奇怪!但你可以把它想象成一个懒加载的承诺,yield 会向解析器抛出一个“我需要这个来继续,处理它!”的信息。
这就是你如何获取一个完整的项目的方法
use BBC\iPlayerRadio\Resolver\Resolver; $resolver = new Resolver(); $resolver->addBackend(new EpisodesBackend); $brand1 = new Brand(['id' => 'p865ddf6']); $brand2 = new Brand(['id' => 'pabcdefs']); $resolver->resolve([$brand1, $brand2]); // $brand1 and $brand2 are now fully hydrated with their latest episodes in the $this->latestEpisodes variable.
等等,那个 new EpisodesBackend
是什么?这是解析器知道如何解决需求的方式。解析器后端接收需求并生成需求的结果。
因此,EpisodesBackend 可能看起来像这样
class EpisodesBackend implements BBC\iPlayerRadio\Resolver\ResolverBackend { /** * Returns whether this backend can handle a given Requirement. Requirements * can be absolutely anything, so make sure to verify correctly against it. * * @param mixed $requirement * @return bool */ public function canResolve($requirement) { return $requirement instanceof LatestEpisodesForProgramme; } /** * Given a list of requirements, perform their resolutions. Requirements can * be absolutely anything from strings to full-bore objects. * * @param array $requirements * @return array */ public function doResolve(array $requirements) { $results = []; foreach ($requirements as $req) { if ($req instanceof LatestEpisodesForProgramme) { $results = $this->fetchEpisodesForProgramme($req->id, $req->limit); } } return $results; } }
虽然使用解析器的语法稍微优雅一些,但这对性能有什么帮助呢?我们仍在循环和同步运行每个查询。
好吧,考虑到 fetchLatestEpisodes 正在执行 cURL 请求。我们现在可以将所有这些批量起来,作为一个单一的多 cURL 请求来执行。
public function doResolve(array $requirements) { $results = []; $urls = []; foreach ($requirements as $req) { if ($req instanceof LatestEpisodesForProgramme) { $urls[] = $req->getDataURL(); } } // Now kick off a multi-curl request for all those URLs: $ch = curl_multi_init(); ... return $results; }
使用解析器允许你忽略对象获取数据的具体细节,只需简单地定义它们需要什么,然后让解析器和解析器后端来完成工作!
提示提示想象一下将它与 WebserviceKit 库配合使用,使用解析器后端查找 QueryInterface 实例,然后执行 multiFetch()... ;)
你还可以向解析器传递标志来帮助 requires()
函数确定它们需要做什么
class Brand implements HasRequirements { public function requires(array $flags = []) { if (in_array('WITH_ATTRIBUTION', $flags)) { $attribution = (yield new WithAttribution($this->parent)); } } }
$resolver->resolve($brand, ['WITH_ATTRIBUTION']);
注意:所有 requires()
函数都看到所有标志,所以请保持它们具体以避免问题!
如果一个需求没有得到任何后端的支持(没有一个 canResolve()
函数返回 true),则会抛出一个 BBC\iPlayerRadio\Resolver\UnresolvableRequirementException
异常,其中包含失败的需求,你可以使用 getFailedRequirement()
方法来访问它。
解析后端
解析后端有两个功能;首先是通过 canResolve
函数来声明它们是否理解一个需求,然后接受一个可以解析的需求列表并解析它们(通过 doResolve
)!
编写自己的解析后端有一些规则
- 在
canResolve
中尽可能具体和安全。确保你确实可以解析它 - 保持后端通用。有一个 "CURLResolverBackend",而不是单独的 "EpisodeResolver"、"BrandResolver" 等。解析后端应该基于 "解析策略" 而不是数据模型。越通用越好。
- 传递给
doResolve
的需求可以是无序的,也可以来自多个对象。这样处理它们! - 始终 以与需求相同的顺序返回结果!如果不这样做,会发生奇怪的事情!
这是如何实际工作的
你可能想知道整个 yield
的工作方式。这是 PHP 中的一种功能,称为 生成器。通常人们想到生成器时,会想到“便宜的迭代器”这一面,但生成器还有一个叫做“协程”的方面。
这基本上涉及到利用 PHP 在达到 yield 时暂停函数执行的事实,只有在循环前进时才返回控制权。通过调用在循环外部 yield 的函数,你可以得到一个可以手动推进的对象,这样你就可以有效地将函数挂起,直到你有一个实际值为止。
感觉难以置信吗?这里有一些非常好的链接,帮助我理解它们
- 关于生成器和协程的 原始 PHP RFC
- JavaScript,但解释了理论
- JS 再次,但也很棒
致谢
这个库是基于在 PHPUK 2015 大会上由 Bastian Hofmann 展示的极其相似的技术。感谢 Bastian!