几天前,我正在修复一个不稳定的测试,结果发现我需要工厂中的一些 且 值。 Laravel 包装了 FakerPHP,我们通常通过 帮助程序访问它。 FakerPHP 附带了 和 等修饰符,但您一次只能使用一个,因此您不能执行 ,而这正是我所需要的。 唯一 有效的 fake() valid() unique() fake()->unique()->valid() 这让我想到,如果我们想创建自己的修饰符怎么办?例如, 或任何其他修饰符。我们如何 框架? uniqueAndValid() 扩展 把想法大声说出来 我将抛弃我的思路。 在采用任何过度设计的解决方案之前,我总是想检查是否有更简单的选项,并了解我正在处理的问题。所以,让我们看一下 助手: fake() function fake($locale = null) { if (app()->bound('config')) { $locale ??= app('config')->get('app.faker_locale'); } $locale ??= 'en_US'; $abstract = \Faker\Generator::class.':'.$locale; if (! app()->bound($abstract)) { app()->singleton($abstract, fn () => \Faker\Factory::create($locale)); } return app()->make($abstract); } 阅读代码后,我们可以看到 Laravel 将单例绑定到容器。但是,如果我们检查抽象,它是一个不实现任何接口的常规类,并且对象是通过工厂创建的。这使事情变得复杂。为什么? 因为如果它是个接口,我们只需创建一个新类来扩展基类 ,添加一些新功能,然后将其重新绑定到容器即可。但我们没有这种奢侈。 \Faker\Generator 这里涉及到一个工厂。这意味着它不是一个简单的实例化;有一些逻辑正在运行。在这种情况下,工厂会添加一些提供程序(PhoneNumber、Text、UserAgent 等)。因此,即使我们尝试重新绑定,我们也必须使用工厂,它将返回原始的 。 \Faker\Generator 解决方案🤔?有人可能会想,“是什么阻止我们创建自己的工厂来返回第 1 点中概述的新生成器?”好吧,没什么,我们可以这样做,但我们不会!我们使用框架有几个原因,其中之一是更新。如果 FakerPHP 添加了新的提供商或进行了重大升级,会发生什么?Laravel 将调整代码,没有进行任何更改的人不会注意到任何事情。但是,我们会被排除在外,我们的代码甚至可能会中断(最有可能)。所以,是的,我们不想走那么远。 那么我们该怎么办? 现在我们已经探索了基本选项,我们可以开始考虑更高级的选项,比如设计模式。我们不需要确切的实现,只需要一些熟悉我们问题的东西。这就是为什么我总是说了解它们是件好事。在这种情况下,我们可以通过添加新功能同时保留旧功能来“装饰” 类。听起来不错?让我们看看怎么做! Generator 首先,让我们创建一个新类, : FakerGenerator <?php namespace App\Support; use Closure; use Faker\Generator; use Illuminate\Support\Traits\ForwardsCalls; class FakerGenerator { use ForwardsCalls; public function __construct(private readonly Generator $generator) { } public function uniqueAndValid(Closure $validator = null): UniqueAndValidGenerator { return new UniqueAndValidGenerator($this->generator, $validator); } public function __call($method, $parameters): mixed { return $this->forwardCallTo($this->generator, $method, $parameters); } } 这将是我们的“装饰器”(有点)。它是一个简单的类,它需要基础 作为依赖项,并引入了一个新的修饰符 。它还使用 Laravel 的 特性,这允许它代理对基础对象的调用。 Generator uniqueAndValid() ForwardsCalls 此特征有两种方法: 和 。当您想要在装饰对象上链接方法时,请使用后者。在我们的例子中,我们将始终进行一次调用。 forwardCallTo forwardDecoratedCallTo 我们还需要实现 ,这是自定义修饰符,但这不是本文的重点。如果你对实现感兴趣,这个类基本上是 FakerPHP 附带的 和 的混合体,你可以 找到它。 UniqueAndValidGenerator ValidGenerator UniqueGenerator 在这里 现在,让我们在 中 框架: AppServiceProvider 扩展 <?php namespace App\Providers; use Closure; use Faker\Generator; use App\Support\FakerGenerator; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->extend( $this->fakerAbstractName(), fn (Generator $base) => new FakerGenerator($base) ); } private function fakerAbstractName(): string { // This is important, it matches the name bound by the fake() helper return Generator::class . ':' . app('config')->get('app.faker_locale'); } } 方法检查与给定名称匹配的抽象是否已绑定到容器。如果是,它将使用闭包的结果覆盖其值,请看一下: extend() // Laravel: src/Illuminate/Container/Container.php public function extend($abstract, Closure $closure) { $abstract = $this->getAlias($abstract); if (isset($this->instances[$abstract])) { // You are interested here $this->instances[$abstract] = $closure($this->instances[$abstract], $this); $this->rebound($abstract); } else { $this->extenders[$abstract][] = $closure; if ($this->resolved($abstract)) { $this->rebound($abstract); } } } 这就是我们定义 方法的原因,该方法生成与容器中 辅助程序绑定相同的名称。 fakerAbstractName() fake() 如果您错过了,请重新检查上面的代码,我留下了评论。 现在,每次我们调用 时,都会返回一个 实例,并且我们将可以访问我们引入的自定义修饰符。每次我们调用 类中不存在的调用时,都会触发 ,并且它会使用 方法将其代理到基本 。 fake() FakerGenerator FakerGenerator __call() forwardCallTo() Generator 就是这样!我终于可以做 ,而且效果非常好! fake()->uniqueAndValid()->randomElement() 在结束之前,我想指出这不是一个纯粹的装饰器模式。然而,模式不是神圣的文本;调整它们以满足您的需求并解决问题。 结论 框架非常有用,Laravel 具有许多内置功能。但是,它们无法覆盖项目中的所有边缘情况,有时,您可能会陷入困境。当这种情况发生时,您可以随时扩展框架。我们已经看到了它是多么简单,我希望您理解了主要思想,这不仅适用于这个 Faker 示例。 总是从简单开始,寻找问题最简单的解决方案。复杂性会在必要时出现,因此如果基本继承可以解决问题,则无需实现装饰器或其他任何东西。当您扩展框架时,请确保不要走得太远,否则得不偿失。您不想最终独自维护框架的一部分。