Php 如何为Laravel雄辩的模型及其关系实现由UUID组成的主键,而不是自动递增的整数?
在可能存在冲突(冲突)的分布式数据库拓扑中,自动递增整数不能用于主键 关于UUIDs与自增整数的主题的现存文献是大量的,基本规则被广泛理解。然而,与此同时,似乎不存在关于如何在拉雷维尔实现这一目标的单一、全面的解释,以及对雄辩的模型和关系的支持 下面的文章值得一读,它解释了在Php 如何为Laravel雄辩的模型及其关系实现由UUID组成的主键,而不是自动递增的整数?,php,laravel,laravel-5,eloquent,uuid,Php,Laravel,Laravel 5,Eloquent,Uuid,在可能存在冲突(冲突)的分布式数据库拓扑中,自动递增整数不能用于主键 关于UUIDs与自增整数的主题的现存文献是大量的,基本规则被广泛理解。然而,与此同时,似乎不存在关于如何在拉雷维尔实现这一目标的单一、全面的解释,以及对雄辩的模型和关系的支持 下面的文章值得一读,它解释了在VARCHAR(36)/CHAR(36)中存储主键与通常用于自动递增键的4/8字节整数相比所产生的性能开销。我们应该注意这一建议(尤其是作者通篇提到的出版后更正): 同样有价值的是来自广泛讨论的评论: 下面的文章解释了如
VARCHAR(36)
/CHAR(36)
中存储主键与通常用于自动递增键的4/8字节整数相比所产生的性能开销。我们应该注意这一建议(尤其是作者通篇提到的出版后更正):
同样有价值的是来自广泛讨论的评论:
下面的文章解释了如何在Laravel Eloquent模型中使用UUID实现主键,但没有解释如何为Eloquent关系实现相同的主键,例如,使用“透视表”(按照Laravel的说法)实现多对多
其他人也提出了类似的问题,例如,但在那个例子中,询问者正在使用MySQL触发器生成要插入到透视表中的UUID,我倾向于避免使用纯粹雄辩的方法
在中提出了另一个类似的问题,但问题的关键是如何转换透视属性,而不是如何在附加或同步关系时为ID列生成自定义值
为了清楚起见,我们可以通过将可选数组参数传递给attach()
方法来轻松实现这一点:
->attach($modelAId, $modelBId, ['id' => Uuid::generate()]);
但是每次我们在任何一个模型上调用attach()
时都必须这样做,这既麻烦又违反了DRY原则
如果使用在模型类本身中实现的事件驱动方法,我们会得到更好的服务
这种方法可能是什么样的?免责声明:这是一项正在进行的工作。到目前为止,这项技术只关注多对多雄辩的关系,而不关注更奇特的类型,例如具有多通或多态性的类型
从Laravel v5.5开始的当前版本*
用于Laravel的UUID生成包
在开始之前,我们需要一种生成UUID的机制
最流行的UUID生成包如下所示:
为有说服力的模型实现UUID
模型使用UUID作为主键的能力可以通过扩展Laravel的基本模型类或实现特性来授予。每种方法都有其优缺点,而且由于Steve Azzopardi在media.com上的文章(如上所述)已经解释了trait方法(尽管它早于Eloquent的$keyType='string';
属性),我将演示模型扩展方法,当然,它可以轻松地适应一种特征
无论我们使用的是模型还是特征,关键的方面是$incrementing=false代码>和受保护的$keyType='string'代码>。由于PHP的单一继承设计,扩展基本模型类会带来一些限制,但它不需要在每个应该使用UUID主键的模型中包含这两个关键属性。相反,当使用某个特征时,忘记在使用该特征的每个模型中同时包含这两个特征将导致失败
基本UUID模型类:
<?php
namespace Acme\Rocket\Models;
use Illuminate\Database\Eloquent\Model;
use Webpatser\Uuid\Uuid;
class UuidModel extends Model
{
public $incrementing = false;
protected $keyType = 'string';
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
}
public static function boot()
{
parent::boot();
self::creating(function ($model) {
$model->{$model->getKeyName()} = Uuid::generate()->string;
});
}
}
这是任何单个模型使用UUID作为主键所需的全部内容。每当创建新模型时,id
列将自动填充新生成的UUID
为具有UUID主键的模型实现雄辩的关系
实现所需行为的必要条件是使用自定义透视模型,特别是因为我们需要禁用主键列(id
)的自动递增,并将其类型从int
更改为string
,就像我们在上面的UuidModel
类中所做的那样
自定义轴心模型,但是。有趣的是,有必要将5.0的用法与5.5+的用法结合起来,以使这一切都起作用
自定义枢轴模型非常简单:
<?php
namespace Acme\Rocket\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class RoleUser extends Pivot
{
public $incrementing = false;
protected $keyType = 'string';
}
需要注意的关键元素是roles()
方法中的自定义透视模型,->使用(RoleUser::class)
,以及newPivot()
方法覆盖;无论何时对模型进行attach()
ed,都需要将UUID插入透视表的id
列中
接下来,我们需要定义角色
模型,该模型本质上相同,但多对多关系相反:
<?php
namespace Acme\Rocket\Models;
use Webpatser\Uuid\Uuid;
use Illuminate\Database\Eloquent\Model;
use Acme\Rocket\Models\UuidModel;
use Acme\Rocket\Models\User;
use Acme\Rocket\Models\RoleUser;
class Role extends UuidModel
{
protected $fillable = ['name'];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
}
public function users()
{
return $this->belongsToMany(User::class)
->using(RoleUser::class);
}
public function newPivot(Model $parent, array $attributes, $table, $exists, $using = NULL) {
$attributes[$this->getKeyName()] = Uuid::generate()->string;
return new RoleUser($attributes, $table, $exists);
}
}
为了检索模型和关系,我们将执行以下操作(使用Tinker):
可以看出,我们定义了两个模型,并通过多对多关系将它们关联起来,在所有实例中使用UUID代替自动递增整数
这种方法使我们能够避免在任何数量的分布式或复制数据库场景中出现主键冲突,从而为在未来几十年内扩展良好的大型复杂数据结构铺平道路
最后的想法
多对多同步方法似乎可以工作,例如sync()
,syncWithoutDetaching()
,和toggle()
,尽管我还没有对它们进行彻底测试
这不是实现更大技术的唯一方法,也不可能是“最佳”方法。虽然它适用于我有限的用例,但我确信其他比我更精通Laravel和更雄辩的人可以提供改进建议(请提供!)
我打算将总体方法扩展到其他rel
<?php
namespace Acme\Rocket\Models;
use Webpatser\Uuid\Uuid;
use Illuminate\Database\Eloquent\Model;
use Acme\Rocket\Models\UuidModel;
use Acme\Rocket\Models\Role;
use Acme\Rocket\Models\RoleUser;
class User extends UuidModel
{
protected $fillable = ['name'];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
}
public function roles()
{
return $this->belongsToMany(Role::class)
->using(RoleUser::class);
}
public function newPivot(Model $parent, array $attributes, $table, $exists, $using = NULL) {
$attributes[$this->getKeyName()] = Uuid::generate()->string;
return new RoleUser($attributes, $table, $exists);
}
}
<?php
namespace Acme\Rocket\Models;
use Webpatser\Uuid\Uuid;
use Illuminate\Database\Eloquent\Model;
use Acme\Rocket\Models\UuidModel;
use Acme\Rocket\Models\User;
use Acme\Rocket\Models\RoleUser;
class Role extends UuidModel
{
protected $fillable = ['name'];
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
}
public function users()
{
return $this->belongsToMany(User::class)
->using(RoleUser::class);
}
public function newPivot(Model $parent, array $attributes, $table, $exists, $using = NULL) {
$attributes[$this->getKeyName()] = Uuid::generate()->string;
return new RoleUser($attributes, $table, $exists);
}
}
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
//use Webpatser\Uuid\Uuid;
use Acme\Rocket\Models\User;
use Acme\Rocket\Models\Role;
class UuidTest extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->uuid('id');
$table->primary('id');
$table->string('name');
$table->timestamps();
});
Schema::create('roles', function (Blueprint $table) {
$table->uuid('id');
$table->primary('id');
$table->string('name');
$table->timestamps();
});
Schema::create('role_user', function (Blueprint $table) {
$table->uuid('id');
$table->primary('id');
$table->unique(['user_id', 'role_id']);
$table->string('user_id');
$table->string('role_id');
});
$user = User::create([
'name' => 'Test User',
]);
$role = Role::create([
'name' => 'Test Role',
]);
// The commented portion demonstrates the inline equivalent of what is
// happening behind-the-scenes.
$user->roles()->attach($role->id/*, ['id' => Uuid::generate()->string]*/);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('role_users');
Schema::drop('users');
Schema::drop('roles');
}
}
MariaDB [laravel]> SELECT * FROM `role_user`;
+--------------------------------------+--------------------------------------+--------------------------------------+
| id | user_id | role_id |
+--------------------------------------+--------------------------------------+--------------------------------------+
| 6f7b3820-6b48-11e8-8c2c-1b181bec620c | 6f76bf80-6b48-11e8-ac88-f93cf1c70770 | 6f78e070-6b48-11e8-8b2c-8fc6cc4722fc |
+--------------------------------------+--------------------------------------+--------------------------------------+
1 row in set (0.00 sec)
>>> (new \Acme\Rocket\Models\User)->first()->with('roles')->get();
=> Illuminate\Database\Eloquent\Collection {#2709
all: [
Acme\Rocket\Models\User {#2707
id: "1d8bf370-6b1f-11e8-8c9f-8b67b13b054e",
name: "Test User",
created_at: "2018-06-08 13:23:21",
updated_at: "2018-06-08 13:23:21",
roles: Illuminate\Database\Eloquent\Collection {#2715
all: [
Acme\Rocket\Models\Role {#2714
id: "1d8d4310-6b1f-11e8-9c1b-d33720d21f8c",
name: "Test Role",
created_at: "2018-06-08 13:23:21",
updated_at: "2018-06-08 13:23:21",
pivot: Acme\Rocket\Models\RoleUser {#2712
user_id: "1d8bf370-6b1f-11e8-8c9f-8b67b13b054e",
role_id: "1d8d4310-6b1f-11e8-9c1b-d33720d21f8c",
id: "89658310-6b1f-11e8-b150-bdb5619fb0a0",
},
},
],
},
},
],
}