如何在 Laravel 中靈活的使用 Trait

eightone發表於2019-02-26

@這是小豪的第九篇文章

好久沒有更新文章了,說好了周更結果還是被自己對時間的安排打敗了。。。。

今天給大家介紹的是在 Laravel 中如何靈活的使用 Trait,說起 Trait ,我一開始不知道是什麼樣的存在,有個模糊的印象是:複用。一直以來對複用的理解和使用就是:寫在一個公共類中,哪裡需要哪裡呼叫,目的就是少寫些程式碼,哈哈。

廢話不多說,現在開始。

前言

大家可能經常看到以下幾種情況:

class Post extends Model
{
    protected static function boot()
    {
        parent::boot();

        static::saving(function ($post){
            $post->creator_id = $post->creator_id ?? \auth()->id();
        });
    }
}   

// 或者

class Video extends Model
{
    protected static function boot()
    {
        parent::boot();

        static::saving(function ($post){
            $post->creator_id = $post->creator_id ?? \auth()->id();
        });
    }
}   

// 或者直接在控制器中指定 creator_id

可以看到,這些程式碼明顯是重複的,可是到底怎麼分離出去達到複用的效果呢。

這樣?

public function hasCreator($model)
{
    $model->creator_id = $model->creator_id ?? \auth()->id();
}

// 封裝一個上述公共方法,然後在模型中呼叫,或者在控制器中呼叫。

從上面的示例中發現這些操作都不是很好,不夠優雅,哈哈。現在我們來看看 laravelTrait 是如何定義和使用的:

// 定義

trait HasCreator
{
    public static function bootHasCreator()
    {
        static::saving(function ($model) {
            $model->creator_id = $model->creator_id ?? \auth()->id();
        });
    }
}

// 呼叫
class Post extends Model
{
    use HasCreator;
}

// 可以了,哈哈,自動呼叫已經可以實現對 creator_id 的自動寫入了,是不是很優雅,哈哈。

現在一步步的來解釋一下是怎麼寫的。

開始

官方解釋: Trait 是為類似 PHP 的單繼承語言而準備的一種程式碼複用機制。Trait 為了減少單繼承語言的限制,使開發人員能夠自由地在不同層次結構內獨立的類中複用 method。 Trait 和 Class 組合的語義定義了一種減少複雜性的方式,避免傳統多繼承和 Mixin 類相關典型問題。。

  1. 首先我們得知道如何定義一個Trait, 使用的關鍵字是 trait 而不是 class

    namespace App\Traits;
    
    trait HasCreator
    {
    }
    
  2. 定義方法(我們先從簡單的來)

    namespace App\Traits;
    
    trait HasCreator
    {
        public static function hasCreator()
        {
            static::saving(function ($model) {
                $model->creator_id = $model->creator_id ?? 1;
            });
        }
    }
    

    可以看到在 Trait中宣告瞭一個 setCreator 方法,裡面裡面依舊是對 creator 設定預設值

  3. 呼叫

    namespace App;
    
    use App\Traits\HasCreator;
    use Illuminate\Database\Eloquent\Model;
    use Illuminate\Database\Eloquent\SoftDeletes;
    
    class Post extends Model
    {
        use HasCreator, SoftDeletes;
    
        protected $fillable = ['title', 'user_id'];
    
        protected static function boot()
        {
            parent::boot();
    
            self::hasCreator();
        }
    }

    用我的理解來說就是將 Trait 中的方法合併到 模型中去了,要想使用就 use 一下,然後當自己宣告的一樣去呼叫就好了。

大家可以看到上面的例子中還 useSoftDeletes , 我們來簡單的看一下它的原始碼:

namespace Illuminate\Database\Eloquent;

trait SoftDeletes
{
    /**
     * Indicates if the model is currently force deleting.
     *
     * @var bool
     */
    protected $forceDeleting = false;

    /**
     * Boot the soft deleting trait for a model.
     *
     * @return void
     */
    public static function bootSoftDeletes()
    {
        static::addGlobalScope(new SoftDeletingScope);
    }

    /**
     * Force a hard delete on a soft deleted model.
     *
     * @return bool|null
     */
    public function forceDelete()
    {
        $this->forceDeleting = true;

        return tap($this->delete(), function ($deleted) {
            $this->forceDeleting = false;

            if ($deleted) {
                $this->fireModelEvent('forceDeleted', false);
            }
        });
    }

    ......
}

從展示的原始碼中我們可以看到,當前 Trait 定義了一個屬性、兩個方法,居然還可以定義屬性,是不是很意外,哈哈。

大家可能會問,要是 Task 中也定義了 $forceDeleting 屬性怎麼辦,哪個為主呢,這裡面其實有個優先順序的:呼叫類 >Trait > 父類,也就是說當 Trait 中出現於呼叫類重複的屬性和方法的時候,預設是以呼叫類為主的。

接下來我們來看下面兩個方法:

bootSoftDeletes:靜態、字首加了 boot, 這表示啥呢?表示預設執行的操作,哈哈。

既然可以定義為自動呼叫,我們是不是把上面的 HasCreator 改一下呢:

    namespace App\Traits;

    trait HasCreator
    {
        public static function hasCreator()  // -> 改為 bootHasCreator
        {
            static::saving(function ($model) {
                $model->creator_id = $model->creator_id ?? 1;
            });
        }
    }

已經自動呼叫了,那麼:

namespace App;

use App\Traits\HasCreator;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use HasCreator, SoftDeletes;

    protected $fillable = ['title', 'user_id'];
}

這樣就可以啦!

後面的那個方法和之前的 hasCreator 是一樣的,當作自身的方法呼叫就好啦,是否宣告為靜態就看自己的需要了。

下面給大家推薦一些在專案中用得到的 Trait,都是從超哥那裡摘下來的,哈哈。

小案例

HasCreator

指定建立者

namespace App\Traits;

use App\User;

/**
 * Trait HasCreator.
 *
 * @property \App\User $creator
 */
trait HasCreator
{
    public static function bootHasCreator()
    {
        static::saving(function ($model) {
            $model->creator_id = $model->creator_id ?? \auth()->id();
        });
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function creator()
    {
        return $this->belongsTo(User::class, 'creator_id')->withTrashed();
    }

    /**
     * @param \App\User|int $user
     *
     * @return bool
     */
    public function isCreatedBy($user)
    {
        if ($user instanceof User) {
            $user = $user->id;
        }

        return $this->creator_id == \intval($user);
    }
}

Trait 中定義了三個方法,現在給大家簡單的解釋一哈:

  1. bootHasCreator:預設給定當前認證使用者。至於下面的 static::saving 不明白的,可以看之前的文章噠。
  2. creator:定義模型關聯
  3. isCreatedBy:判斷傳入的使用者是否為當前建立者

BelongsToUser

指定使用者

namespace App\Traits;

use App\User;

/**
 * Trait BelongsToUser.
 *
 * @property \App\User $user
 */
trait BelongsToUser
{
    public static function bootBelongsToUser()
    {
        static::creating(function ($model) {
            if (!$model->user_id) {
                $model->user_id = \auth()->id();
            }
        });
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        return $this->belongsTo(User::class)->withTrashed();
    }

    /**
     * @param \App\User|int $user
     *
     * @return bool
     */
    public function isOwnedBy($user)
    {
        if ($user instanceof User) {
            $user = $user->id;
        }

        return $this->user_id == \intval($user);
    }
}

我就不解釋啦,和上面的是差不多的,大家看看就明白了。

結束語

就簡單的給大家介紹一下 TraitLaravel 中如何使用的,寫的不對的地方和補充歡迎大家留言噢,哈哈。

相關連結: 《我所理解的 PHP Trait》 ( 對 Trait 更深層次的講解 -- 超哥出品,哈哈)、 《掌握 PHP Trait 的概念和用法》

eightone # Lhao

相關文章