使用容器解决构造函数依赖项的 Laravel 依赖注入问题

3
我希望能够通过容器解析具有构造函数依赖的字符串类实例。当我解析类时,我想传递构造函数依赖项。
一个我测试过的简单示例:Foo是我要解析的类,$id是构造函数参数。
class Foo
{  
  public function __construct(public $id)
  {}
}

在服务容器中绑定:

$this->app->bind('foo', function($app, $id) {
  return new Foo($id);
});

并从容器中解决它:

$foo = App::makeWith('foo', ['id' => 1]);

$foo is then resolved as

Foo 
{
   +id: ["id" => 1],
}

这表明公共属性$id被设置为一个数组。
然而,如果我不想把$id设置为数组,而是整数呢?
那么我需要按照以下方式进行绑定:
App::bind('foo', function($app, $arg) {
  return new Foo($arg['id']);
});

这感觉非常不专业。这是否滥用了服务容器?
有人可能会认为,解析服务容器时传递构造函数参数并不是一个好主意,而是应该使其无状态或使用 setter。但如果在 Foo 中的许多方法将使用,比如说 $id,那么将 $id 作为构造函数传递是最方便和最佳实践的,对吧?

2
如果你确定了你想要那些特定的$id=1的位置,你可以使用上下文绑定和when()->needs()->give()来实现。 - Mohsen Nazari
2个回答

3

由于用户mrhn已经回答了解决方案,因此我只是想解释一下为什么在您的情况下会得到一个array而不是一个int

首先,始终指定您想要的类型提示:

class Foo
{  
  public function __construct(public int $id) {}
}

请注意,我已经为$id指定了类型提示int

其次,您得到一个数组,因为您有一个自定义的“实例化器”(一个closure),您的错误是$id不是$id而是$parameters

请参见源代码,它执行以下操作:$concrete($this, $this->getLastParameterOverride()); 因此,在您的代码中(如下所示),您将第一个参数($app)作为$this传递,将第二个参数($id)作为$this->getLastParameterOverride(),而这个方法将返回数组,因此是['id' => 1]而不是1

$this->app->bind('foo', function($app, $id) {
  return new Foo($id);
});

因此,就像其他用户所说的那样,始终引用具体类,因此app(Foo::class, ['id' => 1]);将解决您的问题(我的方法不太繁琐,但完全相同)。

2
你正在进行自定义绑定,当然在那里你必须自己解析参数。但为什么不让开箱即用的绑定来处理它呢?例如,删除您当前的绑定并使用类绑定。
app()->makeWith(Foo::class, ['id' => 1]);

只需要以下内容,已在Laravel 8项目上进行了测试和运行。

其次,容器设计和参数一直是有缺陷的。从学术界的Java / C#背景转向职业生涯中的Laravel时,我经常面临与您相同的问题。

我认为的解决方案是避免这种方法,而是通常使用流畅的服务设计来解决它。

class Foo {
    private $id;

    public function setId($id) {
        $this->id = $id;
    }
}

使用setter方法设置id,这样你就可以轻松地实例化容器,而不必调用类进行一些工作。

resolve(Foo::class)->setId($model->id);

这可能看起来有些冗余,但我认为这段代码比这个好看得多。这种方法还可以缓解一些问题,如果构造函数做了太多工作,像Laravel这样的框架需要实例化类来完成某些事情(例如路由需要实例化控制器)。

$foo = App::makeWith('foo', ['id' => 1]);

1
快速提醒给阅读此文的任何人,你也可以使用app(WhateverClass::class, ['param1' => 123, 'param2' => 321])或者resolve(WhateverClass::class, ['param1' => 123, 'param2' => 321])resolve是以相同参数和顺序调用app的别名)。没有必要写那么多东西来使其工作,但这只是另一种写法而已。 - matiaslauriti
在Laravel 8中,如果你使用App::makeWith('foo', ['id' => 1]),构造函数内参数$id的值将是数组['id' => 1]而不是1 - Mohsen Nazari
1
@MohsenNazari 这是不正确的,请阅读我的回答(参见源代码)。App::makeWith 是一种别名,它会调用 resolve,而我在回答中解释了 resolve 是别名为 app,所以你是错误的。 - matiaslauriti
@matiaslauriti 你说得对,虽然只有细微的差别,但却产生了很大的影响 :) - Mohsen Nazari
顺便说一句,如果你使用App、app()或resolve(),它们之间并没有太大的区别,只是个人偏好而已。总的来说,我更喜欢使用resolve,因为我避免使用静态内容。 - mrhn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接