Отказ от ответственности: Это незавершенное производство .Пока что эта техника фокусируется только на связях «многие ко многим Eloquent», а не на более экзотических типах, таких как «Has-Many-Through» или «Polymorphics».
Ток с Laravel v5.5.*
Пакеты генерации UUID для Laravel
Прежде чем мы начнем, нам нужен механизм, с помощью которого генерируются идентификаторы UUID.
Наиболее популярный пакет для генерации UUID выглядит следующим образом:
https://github.com/webpatser/laravel-uuid
Реализация UUID для красноречивых моделей
Возможность для модели использовать UUID в качестве своего первичного ключа может быть предоставлена либо путем расширения базового класса модели Laravel,или путем реализации черты.У каждого подхода есть свои сильные и слабые стороны, и поскольку в статье medium.com Стива Аззопарди (цитированной выше) уже объясняется метод trait (хотя он предшествует свойству $keyType = 'string';
в Eloquent), я продемонстрирую подход к расширению модели, который, конечно,, могут быть легко адаптированы к признаку.
Используем ли мы модель или признак, важнейшими аспектами являются $incrementing = false;
и protected $keyType = 'string';
.Расширение базового класса Model накладывает ограничения из-за единого наследования в 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;
});
}
}
Далее, мы 'Определяем первую из двух моделей, User
и Role
, которые связаны в качестве «многие ко многим».
Модель User
:
<?php
namespace Acme\Rocket\Models;
use Acme\Rocket\Models\UuidModel;
class User extends UuidModel
{
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
}
}
Thisэто все, что требуется для любой отдельной модели, чтобы использовать UUID для своего первичного ключа.Всякий раз, когда создается новая модель, столбец id
будет заполняться вновь сгенерированным UUID автоматически.
Реализация красноречивых связей для моделей с первичными ключами UUID
Необходимое условие для достижения желаемойПоведение использует пользовательскую сводную модель, в частности, потому что нам нужно отключить автоинкремент для столбца первичного ключа (id
) и изменить его тип с int
на string
, как мы это делали в UuidModel
класс, выше.
Настройка моделей сводной оси стала возможной начиная с Laravel 5.0 , но использование развивалось в более поздних версиях .Интересно, что для того, чтобы все это работало, необходимо объединить использование 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';
}
Теперь мы добавимотношения с первой (User
) моделью:
<?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);
}
}
Ключевыми элементами, на которые следует обратить внимание, являются пользовательская сводная модель в методе roles()
, ->using(RoleUser::class)
и переопределение метода newPivot()
;оба необходимы для вставки UUID в столбец id
сводной таблицы всякий раз, когда модели attach()
ed.
Далее, нам нужно определить модель Role
, которая по существу идентична, нос обратным отношением «многие ко многим»:
<?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');
}
}
После выполнения вышеуказанной миграции role_user
таблица выглядит следующим образом:
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",
},
},
],
},
},
],
}
Как видно, мы определилидве модели и связали их через отношение «многие ко многим», используя UUID вместо автоматически увеличивающихся целых чисел во всех случаях.
Этот подход позволяет нам избежать конфликтов первичных ключей в любом количестве распределенной или реплицированной базы данных.сценарии, тем самым проложив путь к большим, сложным структурам данных, которые будут хорошо масштабироваться на протяжении десятилетий.
Заключительные мысли
Многие ко многимМетоды синхронизации работают, например, sync()
, syncWithoutDetaching()
и toggle()
, хотя я не проверил их полностью.
Это не единственный подход к более широкой технике, и он вряд ли будет "лучшим" подходом.И хотя это работает для моего ограниченного варианта использования, я уверен, что другие, которые лучше разбираются в Laravel и Eloquent, чем я, могли бы предложить предложения по улучшению (пожалуйста!).
Я намерен расширитьОбщая методология для других типов отношений, таких как Has-Many-Through и Polymorphics, и обновит этот Вопрос соответствующим образом.
Общие ресурсы для работы с UUID в MySQL / MariaDB
http://www.mysqltutorial.org/mysql-uuid/
Состояние поддержки собственного UUID в MySQL
Насколько я понимаю, MySQL 8 просто добавляет новые функции, которые облегчают работу с UUID;он не добавляет «родной» тип данных UUID.
И благодаря «проще» кажется, что новые функции облегчают некоторые проблемы, связанные с преобразованием между строкой VARCHAR(36)
/ CHAR(36)
и BINARY(16)
представительство.По-видимому, последний намного быстрее.
https://mysqlserverteam.com/mysql-8-0-uuid-support/
Состояние поддержки встроенного UUID в MariaDB
Для лучшей поддержки UUID открыт «Запрос о функции» (этот билетобъясняет некоторые причины)
https://mariadb.atlassian.net/browse/MDEV-4958