dakujem/wire-genie

自动装配工具 & 依赖提供者。拥有魔力的Wire。

3.0 2020-12-23 14:29 UTC

README

Tests Coverage Status

自动装配工具 & 依赖提供者 用于PSR-11服务容器。拥有魔力的Wire。

💿 composer require dakujem/wire-genie

📒 变更日志

是什么?

一个超级强大的call_user_func?是的!还有更多。

Wire Genie使用您的PSR-11服务容器来“神奇地”提供参数(依赖)。

允许您

  • 调用任何可调用项
  • 构造任何对象

...并具有高度控制参数的能力。💪

用法

$container = new Any\Psr11\Container([
    Thing::class => new Thing(),
    MyService::class => new MyService(),
]);

$callable = function (MyService $service, Thing $thing){ ... };

class Something {
    public function __construct(MyService $service, Thing $thing) { ... }
}

$g = new Dakujem\Wire\Genie($container);

// Magic! The dependencies are resolved from the container.
$value  = $g->invoke($callable);
$object = $g->construct(Something::class);

这只是基础,过程可自定义且更强大。

对于每个参数,您可以

  • 重写类型提示并连接显式依赖项(重写类型提示)
  • 按需构建缺失的服务(也解决级联依赖)
  • 跳过连接(视为无法解析)
  • 重写值(绕过容器)
// override type-hint(s)
$callable = function (
    #[Wire(MyService::class)] AnInterface $service,
    #[Wire(Thing::class)] $thing
){ ... };
$value  = $g->invoke($callable);

// construct object(s) if not present in the container
$callable = function (
    #[Hot] Something $some,
    #[Make(Thing::class)] $thing
){ ... };
$value  = $g->invoke($callable);

// provide arguments for scalar-type, no-type and otherwise unresolvable parameters
$callable = function (string $question, MyService $service, int $answer){ ... };
$g->invoke(
    $callable,
    'The Ultimate Question of Life, the Universe, and Everything.',
     42,
);
$g->invoke(
    $callable,
    answer: 42,
    question: 'The Ultimate Question of Life, the Universe, and Everything.',
);

// skip wiring for a parameter...
$callable = function (#[Skip] MyService $service){ ... };
$g->invoke($callable, new MyService(...)); // ...and provide your own argument(s)

工作原理

有两种主要方法

Genie::invoke(  callable $target, ...$pool );
Genie::construct( string $target, ...$pool );

...其中变长参数$pool是用于无法解析的参数的值列表。

解析算法工作如下。如果任何步骤成功,则跳过其余步骤。
对于每个参数...

  1. 如果参数名与池中的命名参数匹配,则使用它。
  2. 如果存在#[Skip]提示,则跳过步骤3-6,并将参数视为无法解析。
  3. 如果存在#[Wire(Identifier::class)]提示(属性),则使用容器解析所提示的标识符。
  4. 使用容器解析类型提示的标识符。
  5. 如果存在#[Hot]提示,则尝试创建类型提示的类。解决级联依赖。
  6. 如果存在#[Make(Name::class)]提示,则尝试创建所提示的类。解决级联依赖。
  7. 当参数无法解析时,尝试填充池中的一个参数。
  8. 如果定义了默认参数值,则使用它。
  9. 如果参数是可空的,则使用null
  10. 彻底失败。

提示/属性

如您所见,算法使用原生属性作为提示来控制连接。

#[Wire(Identifier::class)]告诉Genie尝试从容器中连接注册为Identifier的服务
#[Wire('identifier')]告诉Genie尝试从容器中连接具有'identifier'标识符的服务
#[Hot]告诉Genie尝试创建类型提示的类(也适用于联合类型)
#[Make(Service::class, 42, 'argument')]告诉Genie尝试使用42'argument'作为构建参数池来创建Service
#Skip告诉Genie根本不使用容器

HotMake是递归的,它们的构造函数依赖项也将从容器中解析或即时创建。

它可以用作什么?

  • 中间件/管道分发器
  • 异步作业执行
    • 在作业从队列反序列化后提供依赖项
  • 通用工厂,创建具有不同依赖项的实例
  • 方法依赖注入
    • 对于控制器,其中依赖项在运行时连接

示例

一句忠告

动态从服务容器中获取服务可能解决某些实现中的边缘情况,在这些情况下,依赖注入的样板代码无法避免或以其他方式减少。

这也是在编译时未知依赖的调用程序的唯一调用方式。

然而,通常在构建应用程序的服务容器时,你希望连接你的依赖。

免责声明 🤚

不恰当地使用此包可能会破坏已建立的IoC原则,并将依赖注入容器降级为服务定位器,因此请谨慎使用该包。

请记住,将服务注入到工作类中总是比在工作类内部获取服务(这被称为“控制反转”,“IoC”)更好的。

集成

与其他许多第三方库一样,你应该考虑将使用Wire Genie的代码包装在一个辅助类中,其中包含以下方法

/**
 * Invokes a callable resolving its type-hinted arguments,
 * filling in the unresolved arguments from the static argument pool.
 * Returns the callable's return value.
 * Also allows to create objects passing in a class name.
 */ 
public function call(callable|string $target, ...$pool): mixed
{
    return Genie::employ($this->container)($target, ...$pool);
}

这添加了一个微小的层,以便您稍后决定调整依赖项连接的方式。

静态配置

Genie::provide()可以用来配置一个具有固定服务列表的可调用对象,而无需使用反射。

$factory = function( Dependency $dep1, OtherDependency $dep2 ): MyObject {
    return new MyObject($dep1, $dep2);
};
$object = $g->provide( Dependency::class, OtherDependency::class )->invoke($factory);

限制对服务的访问

您可以通过使用过滤代理Limiter来限制通过Genie可访问的服务

$repoGenie = new Dakujem\Wire\Genie(
    new Dakujem\Wire\Limiter($container, [
        RepositoryInterface::class,
        // you may whitelist multiple classes or interfaces
    ])
);

代理使用instanceof类型操作符,如果请求的服务不匹配白名单中的任何类或接口名称,则抛出异常。

定制

可以将自定义的策略插入到Genie中,默认的AttributeBasedStrategy允许自定义解析机制,从而提供最大可配置性。

兼容性

框架无关。可以使用任何PSR-11容器。

神奇的灯泡

// If we happen to find a magical lamp...
$lamp = new Dakujem\Wire\Lamp($container);

// we can rub it, and a genie might come out!
$genie = $lamp->rub();

// My wish number one is...
$genie->construct(Palace::class);

飞毯

我们已经有一个灯泡 🪔 和一个精灵 🧞 ... 那么?

安装

💿 composer require dakujem/wire-genie

从未听说过Composer?去获取它!

测试

使用以下命令运行单元测试

$ composer test
或者
$ php vendor/phpunit/phpunit/phpunit tests

贡献

欢迎提供想法、功能请求和其他贡献。请发送PR或创建一个问题。

现在去,做一些连接吧!