EloquentORM关联关系之多对多

数据表之间往往不是孤立的,而是纵横交叉、相互关联的,比如一个用户对应多个角色,一个角色拥有多个用户等类似的多对多关联。

软件版本

  • Laravel Version 5.4.19

  • PHP Version 7.0.8

关键字和表

  • belongsToMany()

  • attach()

  • detach()

  • sync()

  • toggle()

  • rolesrole_userusers

  • UserRoleRoleUser 模型

一种常见的关联关系是多对多,即表A的某条记录通过中间表 C 与表 B 的多条记录关联,反之亦然。比如一个用户有多种角色,反之一个角色对应多个用户。   比如用户与角色组之间的关系,我们建立一个中间表 role_user,这个表关联用户表 users (使用系统自带的users表)roles 表,如下

生成迁移文件和模型

php artisan make:migration create_role_user_table --create=role_user

php artisan make:model Role -m

编辑迁移文件

文件 <project>/database/migrate/*_create_users_table.php 内容如下

Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('email',30)->unique();
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();
});

文件 <project>/database/migrate/*_create_roles_table.php 内容如下

Schema::create('roles', function (Blueprint $table) {
    $table->increments('id')->comment('角色ID');
    $table->string('name',20)->unique()->comment('角色英文名称');
    $table->char('display_name',20)->nullable()->comment('角色中文名称');
    $table->string('description',180)->nullable()->comment('角色简要描述');
    $table->timestamps();
});

文件 <project>/database/migrate/*_create_role_user_table.php 内容如下

Schema::create('role_user' , function(Blueprint $table){
    $table->unsignedInteger('user_id')->comment('用户id,关联users表');
    $table->unsignedInteger('role_id')->comment('角色id,关联roles表');

    $table->foreign('user_id')->references('id')->on('users')
        ->onUpdate('cascade')->onDelete('cascade');
    $table->foreign('role_id')->references('id')->on('roles')
        ->onUpdate('cascade')->onDelete('cascade');

    $table->primary(['user_id' , 'role_id']);
    $table->timestamps();
});

运行 php artisan 命令保存修改到数据库

php artisan migrate

执行上面的命令后数据库将生成五张表, migrations password_resets users roles role_user

定义关联关系和修改模型的 fillable 属性

User 模型中定义与 Role 模型的对应关系:

public function roles()
{
    /**
     * @param  string $related    关联关系
     * @param  string $table      关联中间表 不填这里默认为 role_user 规则为:Str::snake(class_basename($related)). '_' . Str::snake(class_basename($this)) 并在数据拼接前使用 sort() 排序;
     * @param  string $foreignKey 当前模型的外键id,不填默认为 user_id 规则为:Str::snake(class_basename($this)).'_'.$this->primaryKey;
     * @param  string $relatedKey 关联模型的外键id,不填默认为 role_id 规则为:Str::snake(class_basename($related)).'_'.$related->primaryKey
     * @param  string $relation   关联方法名 不填默认为roles
     */
    return $this->belongsToMany('App\Role' , 'role_user' , 'user_id' , 'role_id' , 'roles')->withTimestamps();
}

Role 模型中定义与 User 模型的关联对应关系:

public function users()
{
    /**
     * @param  string $related    关联关系
     * @param  string $table      关联中间表 不填默认为 role_user 规则为:Str::snake(class_basename($related)). '_' . Str::snake(class_basename($this)) 并在数据拼接前使用 sort() 排序;
     * @param  string $foreignKey 当前模型的外键id,不填默认为 role_id 规则为:Str::snake(class_basename($this)).'_'.$this->primaryKey;
     * @param  string $relatedKey 关联模型的外键id,不填默认为 user_id 规则为:Str::snake(class_basename($related)).'_'.$related->primaryKey
     * @param  string $relation   关联方法名 不填默认为 users
     */
    return $this->belongsToMany(User::class , 'role_user' , 'role_id' , 'user_id' , 'users')
                      ->withPivot(['created_at','updated_at']) // 中间表的字段,这里的中间表是 role_user
                      ->withTimestamps();
}

如果想要中间表自动维护 created_atupdated_at 时间戳,可在定义关联方法时加上 withTimestamps() 方法

使用 tinker 填充数据

修改 /databases/factories/ModelFactory.php,新增关联数据。

/** @var \Illuminate\Database\Eloquent\Factory $factory */
$factory->define(App\User::class , function(Faker\Generator $faker){
    static $password;

    return [
        'name'           => $faker->name ,
        'email'          => $faker->unique()->safeEmail ,
        'password'       => $password ? : $password = bcrypt('secret') ,
        'remember_token' => str_random(10) ,
    ];
});

$factory->define(App\Role::class , function(Faker\Generator $faker){
    return [
        'name'         => $faker->name ,
        'display_name' => $faker->name ,
        'description'  => $faker->text(150) ,
    ];
});
$factory->define(App\RoleUser::class , function(Faker\Generator $faker){
    $user_ids = \App\User::pluck('id')->toArray();
    $role_ids = \App\User::pluck('id')->toArray();

    return [
        'user_id' => $faker->randomElement($user_ids) ,
        'role_id' => $faker->randomElement($role_ids)
    ];
});

使用 tinker 命令

php artisan tinker

## 进入到 tinker 界面执行如下命令
namespace App
factory(User::class,4)->create(); // 生成4个用户
factory(Role::class,4)->create() // 生成4条 role_user 表的测试数据

关联操作

新增数据

将用户关联到角色

$role_id = 2;
$user = \App\User::find(1);
$user->roles()->attach($role_id);

将用户批量放入到角色

$role_ids = [1,3,4];
$user = \App\User::find(1);
$user->roles()->attach($role_ids);

// $user->roles()->attach([1 => ['attribute1' => 'value1'], 2, 3]);

有时可能想要使用一个命令,在建立新模型数据的同时附加关联。可以使用 save方法达成目的:

$role = new Role(['name' => 'Editor']);

\App\User::find(1)->roles()->save($role);

上面的例子里,新的 Role 模型对象会在储存的同时关联到 user 模型。也可以传入属性数组把数据加到关联数据库表:

\App\User::find(1)->roles()->save($role, ['field' => 'value']);

查询数据

查询用户所拥有的角色

$user = \App\User::find(1);
$roles = $user->roles;
dd($roles->toArray());

查询角色下属的所有用户

$role = \App\Role::find(2);
$users = $role->users;

关联删除

将用户从角色中移除

$role_id = 1;
$user = \App\User::find(1);
$user->roles()->detach($role_id);

将用户从所有角色中移除

$user = \App\User::find(1);
$user->roles()->detach();

删除角色下的所有用户关联数据

$role = \App\Role::find(2);
$role->users()->delete();

更新数据

把用户"同步"到角色中

也可以使用 sync 方法附加关联模型。 sync 方法会把根据 ID 数组把关联存到中间表。附加完关联后,中间表里的模型只会关联到 ID 数组里的 id。

$user = \App\User::find(1);
$user->roles()->sync([1,2,4]);

$user->roles()->sync([1 => ['field' => 'value']]); // 加入其他字段的数据

把角色"同步"给用户

$role = \App\Role::find(3);
$role->users()->sync([1]);

如果在定义 belongsToMany() 关联关系的时候,同时想操作中间关联表的数据,这里指的是role_user 表,那么可以定义 with->withPivot($columns) (参数填写中间表的字段) 那么,我们可以在使用 attach() 方法的时候传入第二个参数进行数据的同步更新,例如:

dd($user->roles()->attach($role_id,['created_at'=>'2019-04-24 06:08:22']));

当然,如果单独需要更新中间表,这里指的是role_user 表的字段,可以使用 updateExistingPivot(),例如:

$role_id = 2;
$user = \App\User::find(1);
$user->roles()->updateExistingPivot($role_id,['created_at'=>'2019-04-24 06:08:22']);

一些方法

toggle

顾名思义,如果表中存在则删除数据,如果表中不存在则新增数据。运用场景比如:点赞、喜欢或踩等切换操作。

$role_id = 2; // 入参 integer | array
$user = \App\User::find(1);
$user->roles()->toggle($role_id);
Copyright © http://blog.webfsd.com 2018 all right reserved,powered by Gitbook该文件修订时间: 2019-05-21 04:55:26

results matching ""

    No results matching ""