hyperf icon indicating copy to clipboard operation
hyperf copied to clipboard

[FEATURE] 是否可能实现上下文绑定

Open huangdijia opened this issue 2 years ago • 25 comments

场景:

use Guzzle\ClientInterface;

class Foo
{
    public function __constract(private ClientInterface $client)
    {
    }
}

如果我直接绑定

return [
    Guzzle\ClientInterface => function() { return new Guzzle\Client();}
];

那可能导致的情况是污染了整个容器,其他绑定了相同的依赖都会被影响到。面对这种情况目前我想到的解决方案是

  • 继承 ClientInterface
interface ClientInterface extends \Guzzle\ClientInterface {}
  • 继承 Client
class Client extends \GuzzleClient implements ClientInterface {}

再 Foo 的构造方法声明项目内的 ClientInterface 和绑定项目内的 Client,整个过程会比较繁琐

有没有可能实现类似 Laravel 的上下文绑定

类似:

$container->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$container->when([VideoController::class, UploadController::class])
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

huangdijia avatar Apr 28 '22 06:04 huangdijia

没有,这个不建议放到 Container 里,增加难度,直接实现一个工具来处理就行了

limingxinleo avatar Apr 29 '22 05:04 limingxinleo

没有,这个不建议放到 Container 里,增加难度,直接实现一个工具来处理就行了

这个主要是在依赖注入,这个得动到 Container 吧,不然想不到什么好方案

huangdijia avatar Apr 29 '22 05:04 huangdijia

就是不要动 Container

比如增加一个 #[When] 的注解

然后实例一个 Proxy 类注入到 Container 里,就跟 LazyLoad 差不多

limingxinleo avatar Apr 29 '22 05:04 limingxinleo

这是注解注入的方式,我指的是构造方法那块,主要是 Container::make() 需要改动才能实现

huangdijia avatar Apr 29 '22 05:04 huangdijia

构造函数直接走 Factory 方式就行了

limingxinleo avatar Apr 29 '22 06:04 limingxinleo

写一个中间件,生成对应的映射关系,然后读一下 when, give

然后直接生成 Factory 就行了

limingxinleo avatar Apr 29 '22 06:04 limingxinleo

Factory 读不到 当前的 class 和 when 对比吧,如果不走注解的话

huangdijia avatar Apr 29 '22 07:04 huangdijia

除非在这块多传一个参数,把 $definition 也传过来

/**
     * Resolve a factory definition to a value.
     *
     * @param FactoryDefinition $definition object that defines how the value should be obtained
     * @param array $parameters optional parameters to use to build the entry
     * @throws InvalidDefinitionException if the definition cannot be resolved
     * @return mixed value obtained from the definition
     */
    public function resolve(DefinitionInterface $definition, array $parameters = [])
    {
        $callable = null;
        try {
            $callable = $definition->getFactory();
            if (! method_exists($callable, '__invoke')) {
                throw new NotCallableException();
            }
            if (is_string($callable)) {
                $callable = $this->container->get($callable);
                $object = $callable($this->container);
            } else {
                $object = call($callable, [$this->container]);
            }

            return $object;
        } catch (NotCallableException $e) {
            // Custom error message to help debugging
            if (is_string($callable) && class_exists($callable) && method_exists($callable, '__invoke')) {
                throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $definition->getName(), $e->getMessage()));
            }

            throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s', $definition->getName(), $e->getMessage()));
        }
    }

huangdijia avatar Apr 29 '22 07:04 huangdijia

没必要这么麻烦,Container 主要功能肯定是不会再加了。当然你可以自己继承换掉他

limingxinleo avatar Apr 29 '22 07:04 limingxinleo

上面是 vendor/hyperf/di/src/Resolver/FactoryResolver.php 的代码,在 __invoke() 多传一个 $definition 就可以

huangdijia avatar Apr 29 '22 07:04 huangdijia

算了,这也不是什么好的思路

huangdijia avatar Apr 29 '22 07:04 huangdijia

按照设计的做法是会分别给 PhotoController 和 UploadController 增加一个 Factory 来完成类创建

huangzhhui avatar May 23 '22 07:05 huangzhhui

按照设计的做法是会分别给 PhotoController 和 UploadController 增加一个 Factory 来完成类创建

同一个 Factory?还是各自独立?

huangdijia avatar May 23 '22 08:05 huangdijia

按照设计的做法是会分别给 PhotoController 和 UploadController 增加一个 Factory 来完成类创建

同一个 Factory?还是各自独立?

各自独立

class PhotoControllerFactory
{
    public function __invoke(ContainerInterface $container) {
        $filesystem = Storage::disk('local');
        return new PhotoController($filesystem);
    }
}

// dependencies.php

PhotoController::class => PhotoControllerFactory::class

huangzhhui avatar May 23 '22 08:05 huangzhhui

按照设计的做法是会分别给 PhotoController 和 UploadController 增加一个 Factory 来完成类创建

同一个 Factory?还是各自独立?

各自独立


class PhotoControllerFactory

{

    public function __invoke(ContainerInterface $container) {

        $filesystem = Storage::disk('local');

        return new PhotoController($filesystem);

    }

}



// dependencies.php



PhotoController::class => PhotoControllerFactory::class

这个跟“上下文”没有关系了,跟我最初的需求相悖了🤣

huangdijia avatar May 23 '22 13:05 huangdijia

其实本质上是一样的,稍微封装一下,最终也是为了产出一个 Instance

huangzhhui avatar May 31 '22 07:05 huangzhhui

比较理想的是这样:

// dependencies.php
Filesystem::class => function ($container, $concrete) {
    match($concrete) {
        PhotoController::class => Storage::disk('local'),
        VideoController::class, UploadController::class => Storage::disk('s3'),
        default => Storage::disk('local'),
    }
},

这个要解决的问题,就是容器里不能直接绑定 Filesystem::class 为单例,而是 $concrete.Filesystem::class。

huangdijia avatar May 31 '22 07:05 huangdijia

再给一个思路吧

#[AutoController]
class PhotoController
{
    #[Inject('storage.s3')]
    protected Filesystem $filesystem;

}

// dependencies.php
'storage.s3' => function () { return Storage::disk('s3'); }

huangzhhui avatar May 31 '22 08:05 huangzhhui

是很好的解决方案,但是还是强差人意了点🤣

huangdijia avatar May 31 '22 12:05 huangdijia

@huangdijia 我觉得小哥这个思路挺好的。。。

按照 Laravel 的解决办法,太隐晦了,看起来好像浑然天成,但代码重构可能就问题很大了。。

每次都要查看当前这个类,有没有 被 when

limingxinleo avatar Jun 01 '22 01:06 limingxinleo

小哥的方案是挺不错的,不过貌似只能在 PHP8 环境才能用。我看了一下 Laravel 对 when 的实现,还是很精妙的。

huangdijia avatar Jun 01 '22 02:06 huangdijia

小哥的方案是挺不错的,不过貌似只能在 PHP8 环境才能用。我看了一下 Laravel 对 when 的实现,还是很精妙的。

非 PHP 8 也能用呀,这是现在就能用的方案,把注解换成 PHP 7 的注释方案就行了

huangzhhui avatar Jun 01 '22 09:06 huangzhhui

小哥的方案是挺不错的,不过貌似只能在 PHP8 环境才能用。我看了一下 Laravel 对 when 的实现,还是很精妙的。

when 的方案我认为并不好,隐藏了很多不该隐藏的内容,比如在 PhotoController 里发现注入进来的 filesystem 跟预期不一致就很麻烦,因为你需要去 dependencies 里面去一个个查,当 when 条件复杂时更麻烦,这种预期不一致会造成很多的困扰,当 filesystem 类不是一个 interface 而是一个 class 的时候,这个问题会更明显

huangzhhui avatar Jun 01 '22 09:06 huangzhhui

小哥的方案是挺不错的,不过貌似只能在 PHP8 环境才能用。我看了一下 Laravel 对 when 的实现,还是很精妙的。

when 的方案我认为并不好,隐藏了很多不该隐藏的内容,比如在 PhotoController 里发现注入进来的 filesystem 跟预期不一致就很麻烦,因为你需要去 dependencies 里面去一个个查,当 when 条件复杂时更麻烦,这种预期不一致会造成很多的困扰,当 filesystem 类不是一个 interface 而是一个 class 的时候,这个问题会更明显

when的原理其实跟hyperf的di原理差不多,如果不关注到配置或者扩展包的ConfigProvider,也是有一样的困扰。

huangdijia avatar Jun 01 '22 10:06 huangdijia

不太一样,when 是根据用的地方不一样导致注入的内容不一样,我说的方案是用的地方注入的是什么那就一定是什么,行为和预期是一致的

huangzhhui avatar Jun 10 '22 06:06 huangzhhui

做成了单独的组件 https://github.com/friendsofhyperf/di-plus

huangdijia avatar Aug 27 '23 09:08 huangdijia