earc/component-di

eArc - 显式架构框架 - 组件依赖注入组件

1.0-BETA 2019-07-20 11:47 UTC

This package is auto-updated.

Last update: 2024-09-20 23:03:39 UTC


README

下一代依赖注入技术已到来,为您追求高质量软件提供支持。

earc/component-di 是基于 earc/di 构建的,使您的应用程序组件更加明确,并让您有权按类限制依赖访问,以提高组件解耦。

为什么你应该使用它?

earc/di 是一个真正出色的、轻量级的、易于使用的依赖注入系统,甚至可以与 symfony 集成。

earc/components-di 是 earc/di++。您决定类是否可以从其他组件中访问。如果您意外引入了新的依赖项,它会抛出错误,从而帮助您保持架构设计的清晰。

实际案例:假设您作为软件工程师维护着几个主要由您的前辈编写的网店。今天您的任务是编写这些网店中的一个的新订单导出。目前您需要将运费传递给一个对象。您有一个订单,它有一个购物车,并且有一个 CartService::getShippingCost() 方法。太好了,这很简单!

它通过了代码审查、单元测试、集成测试、客户测试。但惊喜的是,您刚刚引入了一个新错误。如果客户确认订单,店主更改了运费,然后订单就会以新的运费导出 - 但客户确认的是旧的费用。

也许您很幸运,时间窗口足够小,以至于这个错误永远不会真正发生。但面对现实,您已经编写了有缺陷的代码,而且更糟糕的是,它通过了所有质量保证措施!

简而言之,如果编写 CartService::class 的工程师有一个工具可以告诉依赖注入系统这个服务只属于购物车组件及其后代,那么您就不可能引入这个错误。

需要付出多大努力?

几乎不需要。

首先定义您的组件。这些只是直接或间接从 RootComponent::class 继承的空类。

对于您的每个类,您决定它是一个 publicprotectedprivate 服务,或者根本不是服务。对于每个决策都有一个单独的接口。

您决定您的类属于哪个组件。您在名为 getComponent 的方法中返回组件类的名称。

最后但同样重要的是,您不再写

$this->firstService = di_get(FirstService::class);
$this->secondService = di_get(SecondService::class);
$this->parameter = di_param('some.parameter.key')

而是写

di_comp(static::class)
    ->get($this->firstService, FirstService::class)
    ->get($this->secondService, SecondService::class)
    ->param($this->parameter, 'some.parameter.key');

这可能听起来很多,但实际上每个类最多只需额外花费一分钟。

安装

composer require earc/component-di

引导

use eArc\ComponentDI\ComponentDI;

ComponentDI::init();

使用

定义组件

让我们继续上面的商店例子。您有 products、一个 cart、一个 wishlist、一个 financial service provider、一个checkout、一个 shop operator page tool、一个 third party inventory controll interface、一个 product import 和一个 order export,为了简化列表。

您的组件定义可能看起来像这样

class ProductComponent extends RootComponent {}
class CartComponent extends ProductComponent {}
class WishListComponent extends CartComponent {}

class FinancialServiceProviderComponent extends RootComponent {}
class CheckoutComponent extends FinancialServiceProvider {}

class PagesComponent extends RootComponent {}

class ThirdPartyInventoryControllInterfaceComponent extends RootComponent {}
class ProductImportComponent extends ThirdPartyInventoryControllInterfaceComponent {}
class OrderExportComponent extends ThirdPartyInventoryControllInterfaceComponent {}

您的页面组件需要安排产品。这没问题。也许产品组件有一个公开的服务来提供数据。

这并不是在建立墙,这是在封装。它使您能够将单体应用程序划分为具有一定类型API的责任,而不会将其拆分成微服务。

组件接口标志

要成为组件生物圈的一部分,一个类必须实现以下组件接口之一:PublicServiceInterfaceProtectedServiceInterfacePrivateServiceInterfaceNoServiceInterface。这将为类标记所需的可见性。

为了控制组件,接口强制执行一个getComponent方法。

class CartService implements ProtectedServiceInterface
{
    ... 
    public static function getComponent(): string
    {
        return CartComponent::class; 
    }
}

由于CartService::class是一个受保护的Service,它仅对购物车组件和心愿单组件的类可见。

组件解析器

di_comp返回属于传递的类名的组件解析器对象。

重要:始终使用di_comp(static::class)而不是di_comp(self::class)di_comp(ClassName::class)。如果类被扩展,只有晚静态绑定static::class才能确保PHP传递正确的参数。

组件解析器对象有三个方法get()make()param(),它们都返回组件解析器对象以允许方法链,第一个参数始终是传递调用结果的变量。

依赖注入和组件解析器

组件解析器对象的方法get()make()分别替换了di_*函数di_getdi_make。两者都会对传递的类名(或装饰器,如果已装饰)与当前组件进行可见性检查。如果可见性检查失败,则抛出AccessDeniedException。如果通过,则将di_getdi_make调用的结果写入通过引用传递的$object参数。

示例:使用earc/di,你写

__construct() {
    $this->firstService = di_get(FirstService::class);
    $this->secondService = di_get(SecondService::class);
}

相同地使用earc/component-di

__construct() {
    di_comp(static::class)
        ->get($this->firstService, FirstService::class)
        ->get($this->secondService, SecondService::class);
}

你可以通过以下方式检查访问权限

di_comp_has_access(static::class, Service::class)

参数注入和组件解析器

组件解析器对象的方法param()不像get()make()那样限制访问,它丰富了它。每个组件的类名都映射到一个键。你可以通过以下方式检查它

di_comp_key(YourComponent::class)

此键带有前缀。首先是对当前组件的查找,如果没有找到父级的参数,以此类推。最后,不带前缀查找参数。

如果找到参数,则将其写入通过引用传递的$parameter参数。

示例:使用earc/di,你写

__construct() {
   $this->parameter = di_get('serivce.param');
}

相同地使用earc/component-di

__construct() {
    di_comp(static::class)
        ->param($this->parameter, 'serivce.param');
}

假设此段代码来自购物车组件的类。那么第一次参数查找将是cartcomponent.service.param,第二次将是productcomponent.service.param,最后将是service.param

装饰、标记、模拟

其他用于装饰、标记和模拟的di_*函数保持不变,且没有任何限制可以使用。

di_clear_cache也没有限制。但应谨慎使用di_has - 有时候,一个

di_has(Service::class) && di_comp_has_access(static::class, Service::class)

更接近真相。

异常

  • 所有抛出的异常都继承自eArc\DI\Exceptions\BaseException

  • AccessDeniedException扩展自eArc\DI\Exceptions\InvalidArgumentException

  • 如果get()make()的可见性检查失败,则抛出AccessDeniedException

  • 如果使用param()进行参数查找未找到任何参数结果,则抛出eArc\DI\Exceptions\NotFoundException

  • 如果对未实现任何组件标志接口的类调用di_comp,则抛出NoComponentException

  • 如果用不实现ComponentResolverInterface的类作为参数调用ComponentDi::init(),则抛出eArc\DI\Exceptions\InvalidArgumentException

对earc/component-di进行黑客攻击

您可以将自己的组件解析器注册为ComponentDi::init()的参数。之后,di_comp返回新的组件解析器。只需扩展现有的ComponentResolver::class或实现ComponentResolverInterface来调整逻辑以满足您的需求。

发布

发布 v1.0 (alpha)

基于对 earc/di (发布 v2.0) 的完全重写进行的完全重写

发布 v0.0

初始发布

#待办事项

  • 编写测试