hyperf
hyperf copied to clipboard
[FEATURE] 是否可能实现上下文绑定
场景:
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');
});
没有,这个不建议放到 Container 里,增加难度,直接实现一个工具来处理就行了
没有,这个不建议放到 Container 里,增加难度,直接实现一个工具来处理就行了
这个主要是在依赖注入,这个得动到 Container 吧,不然想不到什么好方案
就是不要动 Container
比如增加一个 #[When] 的注解
然后实例一个 Proxy 类注入到 Container 里,就跟 LazyLoad 差不多
这是注解注入的方式,我指的是构造方法那块,主要是 Container::make() 需要改动才能实现
构造函数直接走 Factory 方式就行了
写一个中间件,生成对应的映射关系,然后读一下 when, give
然后直接生成 Factory 就行了
Factory 读不到 当前的 class 和 when 对比吧,如果不走注解的话
除非在这块多传一个参数,把 $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()));
}
}
没必要这么麻烦,Container 主要功能肯定是不会再加了。当然你可以自己继承换掉他
上面是 vendor/hyperf/di/src/Resolver/FactoryResolver.php 的代码,在 __invoke() 多传一个 $definition 就可以
算了,这也不是什么好的思路
按照设计的做法是会分别给 PhotoController 和 UploadController 增加一个 Factory 来完成类创建
按照设计的做法是会分别给 PhotoController 和 UploadController 增加一个 Factory 来完成类创建
同一个 Factory?还是各自独立?
按照设计的做法是会分别给 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
按照设计的做法是会分别给 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
这个跟“上下文”没有关系了,跟我最初的需求相悖了🤣
其实本质上是一样的,稍微封装一下,最终也是为了产出一个 Instance
比较理想的是这样:
// 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。
再给一个思路吧
#[AutoController]
class PhotoController
{
#[Inject('storage.s3')]
protected Filesystem $filesystem;
}
// dependencies.php
'storage.s3' => function () { return Storage::disk('s3'); }
是很好的解决方案,但是还是强差人意了点🤣
@huangdijia 我觉得小哥这个思路挺好的。。。
按照 Laravel 的解决办法,太隐晦了,看起来好像浑然天成,但代码重构可能就问题很大了。。
每次都要查看当前这个类,有没有 被 when
小哥的方案是挺不错的,不过貌似只能在 PHP8 环境才能用。我看了一下 Laravel 对 when 的实现,还是很精妙的。
小哥的方案是挺不错的,不过貌似只能在 PHP8 环境才能用。我看了一下 Laravel 对 when 的实现,还是很精妙的。
非 PHP 8 也能用呀,这是现在就能用的方案,把注解换成 PHP 7 的注释方案就行了
小哥的方案是挺不错的,不过貌似只能在 PHP8 环境才能用。我看了一下 Laravel 对 when 的实现,还是很精妙的。
when 的方案我认为并不好,隐藏了很多不该隐藏的内容,比如在 PhotoController 里发现注入进来的 filesystem 跟预期不一致就很麻烦,因为你需要去 dependencies 里面去一个个查,当 when 条件复杂时更麻烦,这种预期不一致会造成很多的困扰,当 filesystem 类不是一个 interface 而是一个 class 的时候,这个问题会更明显
小哥的方案是挺不错的,不过貌似只能在 PHP8 环境才能用。我看了一下 Laravel 对 when 的实现,还是很精妙的。
when 的方案我认为并不好,隐藏了很多不该隐藏的内容,比如在 PhotoController 里发现注入进来的 filesystem 跟预期不一致就很麻烦,因为你需要去 dependencies 里面去一个个查,当 when 条件复杂时更麻烦,这种预期不一致会造成很多的困扰,当 filesystem 类不是一个 interface 而是一个 class 的时候,这个问题会更明显
when的原理其实跟hyperf的di原理差不多,如果不关注到配置或者扩展包的ConfigProvider,也是有一样的困扰。
不太一样,when 是根据用的地方不一样导致注入的内容不一样,我说的方案是用的地方注入的是什么那就一定是什么,行为和预期是一致的
做成了单独的组件 https://github.com/friendsofhyperf/di-plus