准备
我们来查看迁移中三张表的关系
public function up()
{
// 用户表
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->timestamps();
});
// 员工表
Schema::create('employees', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->string('employee_code');
$table->string('phone');
$table->timestamps();
});
// 客户表
Schema::create('clients', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->string('company_name');
$table->string('phone');
$table->timestamps();
});
}
其中,员工和客户都关联了 users 表,都具有 user_id 字段。
前言
大家可能经常看到以下几种情况:
class Employee extends Model
{
protected static function boot()
{
parent::boot();
static::saving(function ($employee){
$employee->user_id = $employee->user_id ?? \auth()->id();
});
}
}
// 或者
class Client extends Model
{
protected static function boot()
{
parent::boot();
static::saving(function ($client){
$client->user_id = $client->user_id ?? \auth()->id();
});
}
}
// 或者直接在控制器中指定 user_id
可以看到以上代码明显有重复的,那么如何将重复的代码分离出去达到复用的效果呢?
可能我们会想到将它封装到公共方法中,在模型或者在控制器中调用。
public function hasUser($model)
{
$model->user_id = $model->user_id ?? \auth()->id();
}
从上面的示例中发现不是很好,不够优雅,这时我们可以使用Trait
trait HasUser
{
public static function booHasUser()
{
static::saving(function ($model){
$model->user_id = $model->user_id ?? \auth()->id();
});
}
}
// 调用
class Employee extends Model
{
use HasUser;
}
接下来我们来讲解一下Trait的使用:
Trait 是什么
Trait [treɪt] 翻译过来是 "特性"、"特点" 、"特质",是一种在 PHP 中复用代码的形式。
官方解释:
Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。 Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。
Trait 使用
1. 使用关键字Trait来定义
namespace App\Traits;
Trait HasUser
{
public static function booHasUser()
{
static::saving(function ($model){
$model->user_id = $model->user_id ?? \auth()->id();
});
}
}
2. 在类中使用use将该trait引入
class Employee extends Model
{
use HasUser;
}
3. trait 有优先级顺序,即:调用类 > Trait > 父类
trait HasUser
{
public function myTrait()
{
echo "user trait" . PHP_EOL;
}
}
class Employee
{
use HasUser;
public function myTrait()
{
echo "employee trait" . PHP_EOL;
}
}
$employee = new Employee();
$employee->myTrait();
结果:
Traits ✗ php TestTrait.php
employee trait
以上例子我们可以看出调用的是类中的myTrait()方法
4. 多个Trait使用逗号分隔
trait HasUser
{
public function myTrait()
{
echo "user trait" . PHP_EOL;
}
}
trait Address
{
public function getCity()
{
echo "南京" . PHP_EOL;
}
}
class Employee
{
use HasUser, Address;
}
$employee = new Employee();
$employee->myTrait();
$employee->getCity();
5. 冲突的解决
如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。
trait HasUser
{
public function myTrait()
{
echo "user trait" . PHP_EOL;
}
public function sayHello()
{
echo "user say hello!" . PHP_EOL;
}
}
trait Address
{
public function getCity()
{
echo "南京" . PHP_EOL;
}
public function sayHello()
{
echo "address say hello!" . PHP_EOL;
}
}
class Employee
{
use HasUser, Address;
}
$employee = new Employee();
$employee->myTrait();
$employee->getCity();
$employee->sayHello();
结果:
Traits ✗ php TestTrait.php
PHP Fatal error: Trait method sayHello has not been applied, because there are collisions with other trait methods on Employee in /app/Traits/TestTrait.php on line 27
Fatal error: Trait method sayHello has not been applied, because there are collisions with other trait methods on Employee in /app/Traits/TestTrait.php on line 27
从执行的结果我们可以看出两个trait方法存在冲突。
为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof
操作符来明确指定使用冲突方法中的哪一个。还可以使用as操作符为某个方法别名。
trait HasUser
{
public function myTrait()
{
echo "user trait" . PHP_EOL;
}
public function sayHello()
{
echo "user say hello!" . PHP_EOL;
}
}
trait Address
{
public function getCity()
{
echo "南京" . PHP_EOL;
}
public function sayHello()
{
echo "address say hello!" . PHP_EOL;
}
}
class Employee
{
use HasUser, Address {
HasUser::sayHello insteadOf Address;
Address::sayHello as sayHelloAddr;
}
}
$employee = new Employee();
$employee->myTrait();
$employee->getCity();
$employee->sayHello();
$employee->sayHelloAddr();
结果:
Traits git:(release/2.1.6) ✗ php TestTrait.php
user trait
南京
user say hello!
address say hello!
6. laravel 模型关系使用Trait
很多模型中都有如下代码
public function user()
{
return $this->belongsTo(User::class);
}
如何复用这段代码呢?
下面我们创建一个trait用来存放用户相关的代码
namespace App\Traits;
use App\Models\User;
trait BelongsToUser
{
public static function bootBelongsToUser()
{
static::creating(function ($model) {
if (!$model->user_id) {
$model->user_id = \auth()->id();
}
});
}
public function user()
{
return $this->belongsTo(User::class);
}
}
员工模型中使用Trait
namespace App\Models;
use App\Traits\BelongsToUser;
use Illuminate\Database\Eloquent\Model;
class Employee extends Model
{
use BelongsToUser;
}
客户模型中使用Trait
namespace App\Models;
use App\Traits\BelongsToUser;
use Illuminate\Database\Eloquent\Model;
class Client extends Model
{
use BelongsToUser;
}