首页 > 解决方案 > Laravel 模型继承:如何从父级实例化正确类型的模型

问题描述

我想尽可能地避免继承,但我在现有代码中有一个需要处理的案例。

考虑一个CartItem被不同实现模型继承的模型,例如CartItemTypeXCartItemTypeY

这三个模型共享同一个数据库表cart_items,并且具有完全相同的结构。其中一列已命名payload,并且每种类型的内容可能不同,这就是我们有不同模型的原因。

该表还有一列type通过以下简单逻辑自动保存:

static::creating(function (Model $model) {
    $model->type = $model->getMorphClass();
});

到目前为止,一切都很好。每个 CartItem 都以其正确的类型正确保存到数据库中。

现在,我们有一些接受 aCartItem作为参数的路由。例如,让我们考虑以下内容:

class SomeController {
    public function update(CartItemRequest $request, CartItem $cartItem) {
        // Our logic goes here...
        // We would like $cartItem to automatically be of the implementation type (CartItemTypeX for instance)
    }
}

在解决这个问题时,我还有一些额外的障碍要克服。例如,我们的乐观锁定机制将模型的版本 if 保存在相关模型中。它使用 morph 关系来做到这一点,所以它有 morph 类CartItemTypeX,而不是CartItem因为我们从不保存 a这样的CartItem类。

这是我一直在研究的。

重写newFromBuilder方法CartItem

我尝试在类上覆盖此方法,如下所示:

public function newFromBuilder($attributes = [], $connection = null)
{
    $classname = Relation::getMorphedModel($attributes->type);
    $instance = new $classname();
    $instance->exists = true;
    $instance->setRawAttributes((array) $attributes, true);
    $instance->setConnection($connection ?: $this->getConnectionName());

    $instance->fireModelEvent('retrieved', false);
    return $instance;
}

当我通过构建器检索模型时,这似乎工作正常(例如CartItem::whereIsNull('order_id')->get()将返回CartItemTypeX模型(我只记得我没有尝试使用不同的购物车类型进行测试,但我怀疑这会工作......)

但是,当CartItem从路由注入到我的控制器中时,它仍然是类型的模型,CartItem而不是CartItemTypeX 所以这对我没有用。

只需添加一个方法来获取实现

所以我忘了把这个责任委托给 Eloquent(这是我的偏好,因为我想让我的控制器和业务代码尽可能干净和简单)

然后我尝试在添加到类的新方法中应用与上述相同的逻辑CartItem

class CartItem {
    public function getImplementation() {
        $classname = Relation::getMorphedModel($this->type);
        $instance = new $classname();
        $instance->exists = $this->exists;
        $instance->setRawAttributes((array) $this->attributes, true);
        $instance->setConnection($this->connection ?: $this->getConnectionName());
        return $instance;
    }
}

然后在我的控制器中,我可以轻松地做到这一点:

class MyController {
    public function view(Request $request, CartItem $cartItem) {
        $cartItem = $cartItem->getImplementation();
        /* ... */
    }
}

这也有效,但我上面讨论的版本控制特性对我来说是失败的。在检索模型时,此特征通过变形关系获取相关的。由于它是CartItem从数据库中检索的类型,它使用变形类型CartItem而不是CartItemTypeX从数据库中检索它。

因此,仅通过在代码中实例化正确的实现类并原始设置属性(这是一个词吗?),我们没有正确的值,version在保存模型时会引发冲突。

那么回到第一个解决方案?

我在代码中继承的第一个解决方案是这样的:

class CartItem {
    public function getImplementation() {
        $classname = Relation::getMorphedModel($this->type);
        return $classname::find($this->id);
    }
}

是的,这行得通。我进入CartItem并通过执行$cartItem->getImplementation(),Eloquent 正在CartItemTypeX从数据库中检索 。版本控制(乐观锁定)的特性也可以正常工作,因为我们只是从数据库中重新获取它......

哦 - 又是那个问题......一个新的检索。这个实现显然是在扼杀性能,对于检索 CartItemTypeX,我们现在总是有两次检索:一次是CartItem因为路由参数注入到控制器方法中,第二次是当我们获取实现时。

肯定有更好的方法。任何想法或见解将不胜感激!

标签: phplaravelinheritanceeloquent

解决方案


推荐阅读