淺談Laravel之探秘SoftDeletes

原文發(fā)表于個(gè)人博客

Introduction

What would happen if delete() is called on a model using SoftDeletes in Laravel framework. This is the question I am asked on stackoverflow, How to create the conditions on the model of eloquent? (Laravel 5.3). So I write down this article.

SoftDeletes Trait

In laravel, we define our own model by extending Illuminate\Database\Eloquent\Model. To delete a model instance softly, we should use Illuminate\Database\Eloquent\SoftDeletes trait in our model. runSoftDelete() is the key function in SoftDeletes trait building a sql query, getting the column which is used to mark whether the record has been deleted or not and then update the column with current timestamps.

    protected function runSoftDelete()
    {
        $query = $this->newQueryWithoutScopes()->where($this->getKeyName(), $this->getKey());

        $this->{$this->getDeletedAtColumn()} = $time = $this->freshTimestamp();

        $query->update([$this->getDeletedAtColumn() => $this->fromDateTime($time)]);
    }

Procedure of Delete()

What happens when we call delete() function on a model?

Since our own model extends the Illuminate\Database\Eloquent\Model, we take a glance at it. Here is the delete() function:

public function delete()
    {
        if (is_null($this->getKeyName())) {
            throw new Exception('No primary key defined on model.');
        }

        if ($this->exists) {
            if ($this->fireModelEvent('deleting') === false) {
                return false;
            }

            // Here, we'll touch the owning models, verifying these timestamps get updated
            // for the models. This will allow any caching to get broken on the parents
            // by the timestamp. Then we will go ahead and delete the model instance.
            $this->touchOwners();

            $this->performDeleteOnModel();

            $this->exists = false;

            // Once the model has been deleted, we will fire off the deleted event so that
            // the developers may hook into post-delete operations. We will then return
            // a boolean true as the delete is presumably successful on the database.
            $this->fireModelEvent('deleted', false);

            return true;
        }
    }

The code is clear. It ensures the model has a primaryKey and the instance exists in database firstly. Then call performDeleteOnModel() function to perform deletion opreation. Attention should be paid!

Here we should know:

An inherited member from a base class is overridden by a member inserted by a Trait. The precedence order is that members from the current class override Trait methods, which in turn override inherited methods.

So the exact function executes when performDeleteOnModel() called is the one with the same name in SoftDeletes trait but not the one in Model class. And now we turn back to the trait:

    protected function performDeleteOnModel()
    {
        if ($this->forceDeleting) {
            return $this->newQueryWithoutScopes()->where($this->getKeyName(), $this->getKey())->forceDelete();
        }

        return $this->runSoftDelete();
    }

Well, it calls runSoftDelete() we have talked about in the beginning. And this is procedure of soft detetion.

Problems on Getting

The asker wants to use different DELETED_AT columns when deleting. It leaves much to be desired to keep soft deletion mechanism working well by overridding getDeletedAtColumn() only. Why the deleted models are still in results even if they have been deleted softly?

When Model class is constructed, it will boot traits by calling their their boot[TraitName] method. Thus here is bootSoftDelete() method.

   protected static function bootTraits()
    {
        foreach (class_uses_recursive(get_called_class()) as $trait) {
            if (method_exists(get_called_class(), $method = 'boot'.class_basename($trait))) {
                forward_static_call([get_called_class(), $method]);
            }
        }
    }

Now let's pour attention on the SoftDeletes trait again.


    public static function bootSoftDeletes()
    {
        static::addGlobalScope(new SoftDeletingScope);
    }

Here the trait registers a SoftDeletingScope class having an apply() method by calling static::addGlobalScope(). The method, which located in Model class stores it into $globalScopes array.

    public static function addGlobalScope(ScopeInterface $scope)
    {
        static::$globalScopes[get_called_class()][get_class($scope)] = $scope;
    }

When a query is built on a model, applyGlobalScopes() method will be called automatically, visiting instances in $globalScopes array one by one and call their apply() method.

    public function applyGlobalScopes($builder)
    {
        foreach ($this->getGlobalScopes() as $scope) {
            $scope->apply($builder, $this);
        }

        return $builder;
    }

We will lift the veil of problem now. In SoftDeletingScope class:

    public function apply(Builder $builder, Model $model)
    {
        $builder->whereNull($model->getQualifiedDeletedAtColumn());

        $this->extend($builder);
    }

It will add a constraint on every query to select those records whose DELETED_AT column is null. And this is the secret of SoftDeletes.

Dynamic DELETED_AT Column

Firstly, I need to reaffirm that I don't recommend such behaviour of using dynamic DELETED_AT column.

In order to solve the asker's problems of dynamic DELETED_AT column, you need to implement your own SoftDeletingScope class with such apply() function:

    public function apply(Builder $builder, Model $model)
    {
        $builder->where(function ($query){
            $query->where('DELETED_AT_COLUMN_1',null)->orWhere('DELETED_AT_COLUMN_2',null);
        });

        $this->extend($builder);
    }

And then overide bootSoftDeletes() with it

    public static function bootSoftDeletes()
    {
        static::addGlobalScope(new YourOwnSoftDeletingScope);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • **2014真題Directions:Read the following text. Choose the be...
    又是夜半驚坐起閱讀 11,005評(píng)論 0 23
  • 前幾天梁崗老師在談到手機(jī)問題的時(shí)候,講到了他接觸的一個(gè)學(xué)生案例:因?yàn)槿狈Π踩?,所以帶手機(jī)入校,但最終經(jīng)過...
    郝說郝道閱讀 344評(píng)論 0 2
  • 本文參加#我的軍訓(xùn)我來說#活動(dòng),本人承諾,文章內(nèi)容為原創(chuàng),且未在其他平臺(tái)發(fā)表過。 入學(xué)前對(duì)軍訓(xùn)的向往,軍訓(xùn)時(shí)...
    李玉寬閱讀 385評(píng)論 1 1
  • 雨慢慢停了,蕭瑟地秋風(fēng)吹過,吹散了滿地金黃地楓葉,點(diǎn)點(diǎn)成淚,飄去遠(yuǎn)方,化作-片云。心丟掉,隨蕭然秋風(fēng)去追卻無處可尋...
    雨中的風(fēng)箏閱讀 335評(píng)論 0 0

友情鏈接更多精彩內(nèi)容