Laravel在trait构造函数中如何传递参数

5

我有一个TimezoneTrait,它被用在User模型中。我还有一个UserRepositoryInterface,它通过服务提供者加载,并在所有类中良好地工作,因此绑定应该没问题:

public function register()
{
    $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
}

public function provides()
{
    return [
        UserRepositoryInterface::class,
    ];
}

现在我遇到的问题是,我必须在我的 trait 中使用该存储库,所以我自然而然地做了这个:

private $userRepository;

public function __construct(UserRepository $userRepository)
{
    $this->userRepository = $userRepository;
}

但是转储显示存储库为null。特质不能注入依赖项吗?

声明 protected $userRepository; 可能会起作用。 - Jagruti Metaliya
1
没有,它什么也没做。 - Norgul
据我所知,特质无法与它们被 use 的类一起注入。但是,没有什么可以阻止你在注入另一个类的类中使用该特质。 - Andrei
1个回答

9
在 trait 中定义 __constructor 实际上是错误的,或者说是不好的设计。构造函数应该特定于它们所属的类,而不是 trait。另一个问题是,在 Model 类中导入 trait,这意味着您应该特别遵循有关在模型中加载 trait的规则。
在模型的引导阶段,它会递归地搜索类中导入的 trait,并自动调用使用 boot{TraitNameHere} 命名约定的方法(静态方式)。这证明了模型中的 trait 不涉及 Laravel 的依赖注入周期。
要实现这一点,您可以使用 Laravel 全局帮助程序,将存储在容器中的实例加载到静态属性中,如门面 App::make(DefinedKeyHere)。然后将分配的实例存储在静态属性中,以使其保留到运行时结束,并且因为召回方法是 static
trait TimezoneTrait
{
    protected static $userRepository;

    protected static function bootTimezoneTrait()
    {
        static::$userRepository = \App::make(UserRepositoryInterface::class);
    }
}

如果您当前正在尝试避免使用全局助手,监听模型引导事件也是有帮助的。在EventServiceProvider内的示例:

Event::listen('eloquent.booting:*', function (Model $model) {
    $model->setUserRepository($this->app[UserRepositoryInterface::class]);
});

那么这个特征就是:
trait TimezoneTrait
{
    protected static $userRepository;

    public function static setUserRepository(UserRepositoryInterface $userRepository)
    {
        static::$userRepository = $userRepository;
    }
}

请注意,我将setUserRepository定义为静态的,但您也可以将其定义为非静态的。

稍微扩展一下有关模型事件的内容,每当模型执行其相关操作时,它都会触发几个事件。

Laravel 5.5的示例事件:

public function getObservableEvents()
{
    return array_merge(
        [
            'creating', 'created', 'updating', 'updated',
            'deleting', 'deleted', 'saving', 'saved',
            'restoring', 'restored',
        ],
        $this->observables
    );
}

当其实例化(也被反序列化)时,还会触发另外两个默认事件,即bootingbooted。使用的方法是触发事件,注意事件名称。

protected function fireModelEvent($event, $halt = true)
{
    // ...

    return ! empty($result) ? $result : static::$dispatcher->{$method}(
        "eloquent.{$event}: ".static::class, $this
    );
}

启动和构造之间有什么区别?构造函数只在类被实例化时调用,但是启动是在所有服务提供者都注册后最初调用的。这是否意味着无论类是否被实例化,都会调用启动? - Norgul
那是不同的“启动”,你所引用的是应用程序“引导”,但这只是发生在模型上的“启动”机制。一旦实例化了一个模型,这个“启动”就像上面所述,递归地调用所有 traits 方法,应用静态事件和任何其他操作。一旦“启动”,它将存储保留值,告诉我们“启动”阶段已经完成。因此,每当您重新实例化它——比如通过new ModelModel::create等方式——这些消耗内存的阶段就会被避免。 - Chay22

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