如何在 Laravel 中灵活的使用 Trait

@这是小豪的第九篇文章

好久没有更新文章了,说好了周更结果还是被自己对时间的安排打败了。。。。

今天给大家介绍的是在 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 的概念和用法》

本作品采用《CC 协议》,转载必须注明作者和本文链接
finecho # Lhao
本帖由系统于 4年前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 14

这个写法很overtrue :yum:

5年前 评论
kkk1

三句离不开一个 哈哈

5年前 评论
finecho

@kkk1 哈哈,还真是

5年前 评论

大家可能会问,要是 Task 中也定义了 $forceDeleting 属性怎么办,哪个为主呢,这里面其实有个优先级的:调用类 >Trait > 父类,也就是说当 Trait 中出现于调用类重复的属性和方法的时候,默认是以调用类为主的。

Trait 中的属性好像是不能被调用类重新定义的,会抛出致命错误。

5年前 评论

感谢楼主,对 trait 有了新的认识 :+1:

4年前 评论

为了trait而trait,过度设计最要命

3年前 评论

腾讯外包给的多吗

2年前 评论

我想问问,Trait中的方法只需要在方法名称前加上boot就可以自动调用吗?

7个月前 评论
finecho (楼主) 7个月前

谢谢,我还有个问题,像这样在trait中调用其他trait不需要在开头声明而只需要在trait内部使用use也可以吗?我看你们在开头也有声明啊。

file

7个月前 评论
finecho (楼主) 7个月前
forfaye (作者) 7个月前
finecho (楼主) 7个月前
forfaye (作者) 7个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!