duanqiang3925 2019-08-07 14:09
浏览 215

Laravel模型继承:如何从父实例化实例化正确类型的模型

I want to avoid inheritance as much as possible, but I have a case in existing code that I need to work with.

Consider a model CartItem which is being inherited by different implementation models, such as CartItemTypeX and CartItemTypeY

The three models share the same database table cart_items, and have the exact same structure. One of the columns is named payload and its content can be different for each of the types, which is why we have the different models.

The table also has a column type which is being saved automatically by the following simple logic:

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

So far so good. Each CartItem is properly being saved into the database with its proper type.

Now, we have some routes that accept a CartItem as a parameter. For instance, let's consider the following:

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)
    }
}

I have some additional hurdles to take when tackling this issue. For instance, our optimistic locking mechanism saves the version if of the model in a related model. It does that using a morph relationship, so it has the morph class CartItemTypeX and not CartItem because we never save a CartItem as such.

This is what I have been looking into.

Overriding the newFromBuilder method on CartItem

I tried overriding this method on the class, like this:

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;
}

This seems to be working fine when I'm retrieving the models through the builder (for instance CartItem::whereIsNull('order_id')->get() will return CartItemTypeX models (I just remember I haven't tried testing with different cart types, but I suspect that this would work...)

however, when the CartItem is being injected from the route into my controller, it's still a model of type CartItem and not CartItemTypeX So that didn't do the trick to me.

Just adding a method to get the implementation

So I forgot about delegating this responsibility to Eloquent (which had my preference, as I want to keep my controller and business code as clean and simple as possible)

I then tried applying the same logic as above in a new method adding to the CartItem class:

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;
    }
}

and in my controller, I can then easily do this:

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

That works as well, but my versioning trait I discussed above is failing on me. On retrieving the Model, this trait fetches the related through the morph relation. As it was of type CartItem when being retrieved from the databases, it uses the morph type for CartItem and not CartItemTypeX to retrieve it from the database.

So by just in code instantiating the correct implementation class and setting attributes rawly (is that a word?), we do not have the correct value for version which raises conflicts when the model is being saved.

So back to the first solution?

The first solution I inherited in the code, is something like this:

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

and yes, this works. I get in a CartItem and by doing $cartItem->getImplementation(), Eloquent is retrieving the CartItemTypeX from the database. The trait for versioning (optimistic locking) also works as it is supposed to do, as we are just fetching it freshly from the database...

Oh - and there's that issue again... a fresh retrieve. This implementation is killing performance obviously, as to retrieve a CartItemTypeX, we will now always have two retrieves: One for CartItem because of route parameter injection into the controller method, and a second time when we fetch the implementation.

Surely there must be a better way. Any thoughts or insights would be much appreciated!

  • 写回答

0条回答 默认 最新

    报告相同问题?

    悬赏问题

    • ¥15 执行 virtuoso 命令后,界面没有,cadence 启动不起来
    • ¥50 comfyui下连接animatediff节点生成视频质量非常差的原因
    • ¥20 有关区间dp的问题求解
    • ¥15 多电路系统共用电源的串扰问题
    • ¥15 slam rangenet++配置
    • ¥15 有没有研究水声通信方面的帮我改俩matlab代码
    • ¥15 ubuntu子系统密码忘记
    • ¥15 保护模式-系统加载-段寄存器
    • ¥15 电脑桌面设定一个区域禁止鼠标操作
    • ¥15 求NPF226060磁芯的详细资料