everon / factory
Everon 工厂组件
Requires
- php: ^8
- everon/collection: ^3
- everon/utils: ^3
Requires (Dev)
- mockery/mockery: ^1
- phpunit/phpunit: ^9
README
处理依赖注入和实例化的库。允许生成易于测试的代码。
版本
- 使用 v1.x 和 v2.x 与 PHP 7.2+
- 使用 v3.x 与 PHP 8+
功能
- 单行、懒加载依赖注入(通过设置器或构造器)
- 基于特质,简单易用和实现,无需处理配置文件,纯 PHP
- Factory/FactoryWorker 提供完全控制和概览,了解每个对象的创建方式
- FactoryWorker 允许自定义实现 Factory 方法
- 当依赖项应该重用(通过依赖容器)或从头创建时(例如,Logger 与值对象)时,拥有完全控制权
- 由于回调和懒加载,最小化文件/内存访问/使用
- 直观的接口:清晰、小巧且简单 API
- 约定优于配置
- 干净代码
工作原理
每个实例化都应在 FactoryWorker
类内部发生。它的作用类似于 AbstractFactory
。它位于 Everon 工厂组件 和使用它的客户端代码之间,因此没有直接耦合。由于注入成本低,依赖项设置发生在一处,因此易于实现和管理。
这使得测试变得容易,因为所有内容都可以轻松模拟/存根/伪造。对于简单的值对象类,例如 Collection
,可以使用 new
操作符。例如 new Collection()
。
对于特定模块或应用程序,FactoryWorker
的 registerBeforeWork
方法是设置依赖树的地方之一。或者,您可以使用 根组合模式 并在应用程序之外处理整个依赖图。
简单的依赖注入
要使用依赖注入,请将其注册到 Dependency Container
并使用一行特质注入。
例如,这将自动通过设置器注入将预定义的 Logger
实例注入到 Foo
类中。
class Foo { use Dependency\Setter\Logger; }
注册到依赖容器
使用 register
方法注册名为 Logger
的依赖项。
$Container->register('Logger', function () use ($FactoryWorker) { return $FactoryWorker->buildLogger(); });
定义特质和接口
这是 Logger
依赖特质的一个例子,它在所有使用 Dependency\Setter\Logger
特质的类之间重复使用。唯一要记住的是,特质的名称应与依赖项注册到 Dependency Container
时的名称相同。
trait Logger { /** * @var LoggerInterface */ protected $Logger; /** * @inheritdoc */ public function getLogger() { return $this->Logger; } /** * @inheritdoc */ public function setLogger(LoggerInterface $Logger) { $this->Logger = $Logger; } }
额外功能:您还可以为所有注入 Logger
实例的类定义和分配 LoggerAwareInterface
。
interface LoggerAwareInterface { /** * @return LoggerInterface */ public function getLogger(); /** * @param Logger LoggerInterface */ public function setLogger(LoggerInterface $Logger); }
定义设置器注入特质。唯一要求是名称以 Dependency\Setter\<dependency name>
结尾。您可以在实现 LoggerAwareInterface 的每个类中重用已定义的 Dependency\Logger
特质。
namespace Application\Modules\Logger\Dependency\Setter; use Application\Modules\Logger\Dependency; trait Logger { use Dependency\Logger; }
注册到工厂
使用 registerWorkerCallback
注册返回所需 FactoryWorker 实例的回调。
$Factory->registerWorkerCallback('ApplicationFactoryWorker', function() use ($Factory) { return $Factory->buildWorker(Application::class); });
使用 FactoryWorker 构建
使用 FactoryWorker
类构建您的依赖项。
class ApplicationFactoryWorker extends AbstractWorker implements FactoryWorkerInterface { /** * @inheritdoc */ protected function registerBeforeWork() { $this->getFactory()->registerWorkerCallback('ApplicationFactoryWorker', function () { return $this->getFactory()->buildWorker(self::class); }); } /** * @return Logger */ public function buildLogger() { $Logger = new Logger(); $this->getFactory()->injectDependencies(Logger::class, $Logger); return $Logger; } /** * @param LoggerInterface $Logger * @param string $anotherArgument * @param array $data * * @return ApplicationInterface */ public function buildApplication(LoggerInterface $Logger) { $Application = new Application($Logger); $this->getFactory()->injectDependencies(Application::class, $Application); return $Application; } /** * @param LoggerInterface $Logger * * @return UserManagerInterface */ public function buildUserManager(LoggerInterface $Logger) { $UserManager = new UserManager($Logger); $this->getFactory()->injectDependencies(UserManager::class, $UserManager); return $UserManager; } }
使用依赖容器解析
使用 resolve
接收之前使用 register
或 propose
定义的定义的依赖项。因此,您可以通过构造函数注入将相同的实例传递给另一个类。
$Container->register('Logger', function () use ($FactoryWorker) { return $FactoryWorker->buildLogger(); }); $Container->register('UserManager', function () use ($FactoryWorker, $Container) { $Logger = $Container->resolve('Logger'); return $FactoryWorker->buildUserManager($Logger); }); $Container->register('Application', function () use ($FactoryWorker, $Container) { $Logger = $Container->resolve('Logger'); return $FactoryWorker->buildApplication($UserManager, $Logger); });
现在 Application
和 UserManager
将共享同一个 Logger
类的实例。
$Application->getLogger()->log('It works'); $UserManager->getLogger()->log('It works, too');
如果你在构造函数中没有做任何工作,你不应该这样做,并且你稍后只需要 Logger
的功能,那么只需将 Logger
作为基础设施类型依赖项使用,并通过单行代码通过setter注入它,会更简单。最终结果是一样的。
每个所需的类都将通过相同的 Logger
实例进行注入,该实例已在 Dependency Container
中注册,并在 Factory
中的 FactoryWorker
组装。
确保测试就绪代码(TM)
编写使用 Everon Factory
进行依赖注入和实例化的类的测试可以消除处理依赖问题的麻烦,因为一切都是如此容易模拟。
依赖容器、工厂和工厂工作者
实例化一个新的 Dependency Container
并将其分配给 Factory
。使用 Factory
获取你特定的 FactoryWorker
实例。
最好的是,使用 FactoryWorker
实例化的类根本不知道 Dependency Container
。
当然,它们可以放在单独的文件中,根据应用程序类型和所需的依赖项进行划分。
以下是一个示例,展示了在整个应用程序中,每个类都使用相同的 Logger
实例,这些类需要 Logger
依赖项。
$Container = new Dependency\Container(); $Factory = new Factory($Container); $Factory->registerWorkerCallback('ApplicationFactoryWorker', function() use ($Factory) { return $Factory->buildWorker(Application::class); }); $FactoryWorker = $Factory->getWorkerByName('ApplicationFactoryWorker'); $Container->register('Application', function () use ($FactoryWorker, $Container) { $UserManager = $Container->resolve('UserManager'); $Logger = $Container->resolve('Logger'); return $FactoryWorker->buildApplication($UserManager, $Logger); }); $Container->register('UserManager', function () use ($FactoryWorker) { $Logger = $FactoryWorker->getFactory()->getDependencyContainer()->resolve('Logger'); return $FactoryWorker->buildUserManager($UserRepository, $Logger); }); $Container->register('Logger', function () use ($FactoryWorker) { return $FactoryWorker->buildLogger(); }); //.. //.. Instantiate your application, and proceed as usual //.. $Application = $Container->resolve('Application'); $Application ->bootstrap() ->run();
最佳依赖注入方式是什么?
对于类所做事情的一部分依赖项,请使用构造函数,对于基础设施类型依赖项,请使用setter/getter。一般来说,Logger
或 FactoryWorker
可以是基础设施类型依赖项的好例子。
测试驱动
查看 测试 以获取有关 具有特质依赖项的更多示例。
示例
查看 Everon Criteria Builder 以了解如何通过示例使用 Everon Factory。