tueena-lib/依赖注入

一个小巧的php 7库,用于启用依赖注入。

v4.0.0 2017-08-12 14:24 UTC

This package is not auto-updated.

Last update: 2024-09-29 03:04:40 UTC


README

此包提供了两个类,用于实现php 7应用程序的依赖注入。

您通过告诉服务定位器标识服务的接口名称,以及将要实例化以创建服务的类名或工厂方法(闭包),将服务注册到服务定位器中。

服务构造器或工厂方法所需的其他服务将自动注入。

所有服务仅在需要时且仅构建一次。

库的第二个类是静态的 Injector 类。它提供了将服务注入构造函数、方法、静态方法、闭包、函数和调用方法的方法。

功能和设计决策

  • 库非常小巧:两个类和一个接口(小于200行代码)。因此,您可以轻松理解整个库,并将其复制到您的源代码中,以适应您的需求或删除依赖而不会导致问题。
  • 测试驱动开发。代码覆盖率:100%。
  • ServiceLocator 是不可变的。
  • 每个类都可以注册为服务(当然不需要接口或基类)。
  • 服务通过接口名称标识。
  • 服务实现由类名或工厂方法(闭包)定义。
  • 服务构造器或工厂所需的服务将自动注入。
  • 服务按需构建。
  • 每个服务定位器中每个已注册服务的服务实例只有一个。
  • 已注册的服务不能被覆盖。
  • 尝试从尚未注册的 ServiceLocator 获取服务将抛出异常。
  • 需要显式服务注册:没有“自动装配”。
  • 出于设计考虑,不支持通过注解、通过设置器或接口进行注入。
  • Injector 是一个静态类(它不包含任何状态)。
  • 使用注入器可以构建类、调用方法、静态方法、函数、闭包并调用类。

使用方法

将服务注册到 ServiceLocator

use tueenaLib\dependencyInjection\ServiceLocator;

// The ServiceLocator is immutable. So the register*() methods will return new instances of
// the ServiceLocator on each call.
$serviceLocator = (new ServiceLocator)
  // Define a concrete class.
  // The constructor of the class may require other services as parameters.
  // They will be injected automatically, if they are registered.
  ->registerClass(IConfiguration::class, Configuration::class)
  // Or define a factory function. The factory function may require other
  // services as well.
  ->registerFactory(ISomeApi::class, function (IConfiguration $configuration) { return new SomeApi($configuration->getApiKey()); })
;

// The ServiceLocator provides two more methods, but you probably will never use them.
// Use the injector instead.
if ($serviceLocator->has(MyMailer::class))
	$myMailer = $serviceLocator->get(MyMailer::class);
// The get() method throws an exception, if the service is not registered.

使用注入器将服务注入到所有可调用的对象中。

use tueenaLib\dependencyInjection\Injector;

$myObject = Injector::invokeConstructor($serviceLocator, MyClass::class);
$result = Injector::invokeMethod($serviceLocator, $anObject, 'aMethod');
$result = Injector::invokeStaticMethod($serviceLocator, MyClass::class, 'aStaticMethod');
$result = Injector::invokeInvokeMethod($serviceLocator, $anObject);
$result = Injector::invokeFunction($serviceLocator, 'namespace\\myFunction');
$result = Injector::invokeClosure($serviceLocator, function (MyMailer $mailer) { $mailer->sendSomeMessage(); });

实际上,在您的应用程序中,您不太会与 ServiceLocatorInjector 交互。实际上,您不想这样做。

这里是一个用例示例:假设您在命名空间 myApp\core 中的类中有所有应用程序无关的业务逻辑。现在您有几个使用该核心的应用程序:一个REST API、一个管理工具、一些命令行工具。假设,REST API需要一个 OrderInteractor。它管理订单实体,并需要一个知道如何将订单持久化的对象。

您可以编写一个脚本 core/init.php,它返回一个服务定位器

return function () {

	$serviceLocator = (new ServiceLocator)
		->registerFactory(IConfiguration::class, function () { return new Configuration(__DIR__ . '/configuration/...'); })
		->registerClass(IMySqlConnection::class, MySqlConnection::class)

		->registerClass(IOrderStorage::class, OrderMySqlStorage::class)
		->registerClass(IOrderInteractor::class, OrderInteractor::class)
	;
	return $serviceLocator;
};

您将导入它到您的应用程序中

// applications/restApi/init.php

$coreInitializer = include __DIR__ . '/../core/init.php';
$coreServiceLocator = $coreInitializer();

// Add application specific services.
$applicationServiceLocator = $coreServiceLocator
	->registerClass(IWebSecurityPolicy::class, WebSecurityPolicy::class)
;

// some kind of routing...
$router = new Router;
$request = new Request($_GET, ...);
$controllerClassName = $router->resolveRequest();

$controller = Injector::invokeConstructor($applicationServiceLocator, $controllerClassName);
$result = $controller->execute($request);

如您所见,您不需要在应用程序中处理数据库连接。但是,您仍然可以使用数据库来存储应用程序特定的数据。

现在您的REST API控制器可能看起来像这样

public function __construct(IWebSecurityPolicy $securityPolicy, IOrderInteractor $orderInteractor)
{
	$this->securityPolicy = $securityPolicy;
	$this->orderInteractor = $orderInteractor;
}

public function execute(HttpRequest $httpRequest)
{
	if ($this->securityPolicy->isIpAddressBlacklistedToOrder($request->getIpAddress()))
		...
	$processNewOrderRequest = self::createProcessNewOrderRequestFromHttpRequest($httpRequest);
	$this->orderInteractor->processNewOrder($processNewOrderRequest);
}

OrderInteractor 可能看起来像这样

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

public function processNewOrder(ProcessNewOrderRequest $request)
{
	$order = self::createOrderFromRequest($request);
	$this->storage->saveNewOrder($order);
	// ...
}

正如您所看到的,将您的代码绑定到tueena-lib的所有内容都放在了每个模块的一个文件中。在模块的入口点或前端控制器中的引导文件中。所有其他文件和类都与tueena-lib完全独立。没有注解,没有对Injector的调用,没有全局服务定位器实例。

最佳实践和注意事项

  • 请记住,对Injector的每次调用和对ServiceLocator的每次使用都会将您的软件绑定到这个库。您希望避免这种情况。最好不要传递服务定位器。在入口控制器(如)内或附近定义服务(每个模块)。同时,仅在入口控制器或那里使用Injector。不要将其传播开来。
  • 另外,传递服务定位器也会使依赖关系变得模糊。您只能在方法签名中看到IServiceLocator作为依赖项,但看不到真正需要的那些服务。
  • 考虑一下这种“神奇”依赖注入的缺点。也许您可以使用带有工厂的手动依赖注入来连接应用程序,这样就不必在每次应用程序请求时创建所有对象和数据库连接以及API连接。为什么我们还需要使用这个库在上面的例子中将配置传递给数据库连接对象?当然,代码更少。但这是更好的代码吗?
  • 在服务已经构建后注册服务到服务定位器会导致问题。已构建的实例不会被复制到由register*方法返回的新服务定位器实例中。

许可证

MIT

要求

php >= 7.0.0

安装

如果您使用Composer

composer require tueena-lib/dependency-injector

否则,只需下载两个类和一个接口并使用它们。

联系方式

巴斯蒂安·芬斯克 bastian.fenske@tueena.org