Laravel 中的多對多關係詳解

13122826258發表於2019-05-09

資料表之間是縱橫交叉、相互關聯的,laravel的一對一,一對多比較好理解,官網介紹滴很詳細了,在此我就不贅述啦,重點我記下多對多的關係

一種常見的關聯關係是多對多,即表A的某條記錄通過中間表C與表B的多條記錄關聯,反之亦然。比如一個使用者有多種角色,反之一個角色對應多個使用者。

為了測試該關聯關係,我們沿用官網的使用者角色示例:

需要三張資料表:usersroles 和 role_userrole_user 表按照關聯模型名的字母順序命名(這裡role_user是中間表),並且包含 user_id 和 role_id兩個列。

多對多關聯通過編寫返回 belongsToMany 方法返回結果的方法來定義。廢話不說多,直接上資料結構:

1:建立一個角色表`roles`,並新增一些初始化資料:

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `password` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
  `remember_token` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
  `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_email_unique` (`email`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES ('1', 'admin', 'admin@163.com', '$2y$10$J/yXqscucanrHAGZp9G6..Tu1Md.SOljX3M8WrHsUdrgat4zeSuhC', 'ilocXtjZJwhrmIdLG1cKOYegeCwQCkuyx1pYAOLuzY2PpScQFT5Ss7lBCi7i', '2016-04-21 16:26:23', '2016-12-14 09:29:59');
INSERT INTO `users` VALUES ('2', 'baidu', '10940370@qq.com', '$2y$10$2A5zJ4pnJ5uCp1DN3NX.5uj/Ap7P6O4nP2BaA55aFra8/rti1K6I2', null, '2016-04-22 06:48:10', '2016-04-22 06:48:10');
INSERT INTO `users` VALUES ('3', 'fantasy', '1009@qq.com', '', null, '2017-06-14 10:38:57', '2017-06-15 10:39:01');

 2:建立一個角色表`roles`,並新增一些初始化資料:
    SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for roles
-- ----------------------------
DROP TABLE IF EXISTS `roles`;
CREATE TABLE `roles` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-- ----------------------------
-- Records of roles
-- ----------------------------
INSERT INTO `roles` VALUES ('1', '超級版主', '2016-04-21 16:26:23', '2016-12-14 09:29:59');
INSERT INTO `roles` VALUES ('2', '司令', '2016-04-22 06:48:10', '2016-04-22 06:48:10');
INSERT INTO `roles` VALUES ('3', '軍長', '2017-06-14 10:38:57', '2017-06-15 10:39:01');
INSERT INTO `roles` VALUES ('4', '司長', '2017-06-07 10:41:41', '2017-06-15 10:41:51');
INSERT INTO `roles` VALUES ('5', '團戰', '2017-06-22 10:41:44', '2017-06-28 10:41:54');
INSERT INTO `roles` VALUES ('6', '小兵', '2017-06-22 10:41:47', '2017-06-22 10:41:56');

3:建立一箇中間表`role_user`用於記錄`users`表與`roles`表的對應關係,並新增一些初始化資料

    SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for role_user
-- ----------------------------
DROP TABLE IF EXISTS `role_user`;
CREATE TABLE `role_user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=8 DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of role_user
-- ----------------------------
INSERT INTO `role_user` VALUES ('1', '1', '2', '2017-06-07 11:42:13', '2017-06-21 11:32:16');
INSERT INTO `role_user` VALUES ('2', '1', '3', '2017-06-07 11:32:13', '2017-06-07 11:22:13');
INSERT INTO `role_user` VALUES ('3', '2', '4', '2017-06-07 11:32:13', '2017-06-07 11:12:13');
INSERT INTO `role_user` VALUES ('4', '1', '5', '2017-06-07 11:32:13', '2017-06-07 11:22:13');
INSERT INTO `role_user` VALUES ('5', '3', '6', '2017-06-07 11:32:13', '2017-06-07 11:52:13');
INSERT INTO `role_user` VALUES ('6', '3', '2', '2017-06-07 11:32:13', '2017-06-07 11:42:13');
INSERT INTO `role_user` VALUES ('7', '2', '2', '2017-06-07 11:42:13', '2017-06-07 11:52:13');

注意我們定義中間表的時候沒有在結尾加s並且命名規則是按照字母表順序,將role放在前面,user放在後面,並且用_分隔,這一切都是為了適應Eloquent模型關聯的預設設定:在定義多對多關聯的時候如果沒有指定中間表,Eloquent預設的中間表使用這種規則拼接出來。

建立一個`Role`模型:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Role
 * @package App\Models
 * @mixin \Eloquent
 */
class Role extends Model
{

}

然後我們在 `User` 模型上定義 `roles` 方法:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

/**
 * Class User
 * @package App\Models
 * @mixin \Eloquent
 */
class User extends Model
{
    /**
     * 使用者角色
     */
    public function roles()
    {
        return $this->belongsToMany('App\Models\Role');
    }
}

        注:正如我們上面提到的,如果中間表不是`role_user`,那麼需要將中間表作為第二個引數傳入`belongsToMany`方法,如果中間表中的欄位不是`user_id`和`role_id`,這裡我們姑且將其命名為`$user_id`和`$role_id`,那麼需要將`$user_id`作為第三個引數傳入該方法,`$role_id`作為第四個引數傳入該方法,如果關聯方法名不是`roles`還可以將對應的關聯方法名作為第五個引數傳入該方法。

        接下來我們在控制器中編寫測試程式碼

        <?php

$user = User::find(1);
$roles = $user->roles;
echo '使用者'.$user->name.'所擁有的角色:';
foreach($roles as $role)
    echo $role->name.'  '; //對應輸出為:使用者admin所擁有的角色:司令  軍長  團戰

     當然,和所有其它關聯關係型別一樣,你可以呼叫`roles` 方法來新增條件約束到關聯查詢上
     User::find(1)->roles()->orderBy('name')->get();
正如前面所提到的,為了確定關聯關係連線表的表名,Eloquent 以字母順序連線兩個關聯模型的名字。不過,你可以重寫這種約定 —— 通過傳遞第二個引數到 `belongsToMany` 方法:

return $this->belongsToMany('App\Models\Role', 'user_roles');

除了自定義連線表的表名,你還可以通過傳遞額外引數到 `belongsToMany` 方法來自定義該表中欄位的列名。第三個引數是你定義關聯關係模型的外來鍵名稱,第四個引數你要連線到的模型的外來鍵名稱:

return $this->belongsToMany('App\Models\Role', 'user_roles', 'user_id', 'role_id');

**定義相對的關聯關係**

要定義與多對多關聯相對的關聯關係,只需在關聯模型中呼叫一下 `belongsToMany` 方法即可。我們在 `Role` 模型中定義 `users` 方法:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Role
 * @package App\Models
 * @mixin \Eloquent
 */
class Role extends Model
{
    /**
     * 角色使用者
     */
    public function users()
    {
        return $this->belongsToMany('App\Models\User');
    }

}

 正如你所看到的,定義的關聯關係和與其對應的`User` 中定義的一模一樣,只是前者引用 `App\Models\Role`,後者引用`App\Models\User`,由於我們再次使用了 `belongsToMany` 方法,所有的常用表和鍵自定義選項在定義與多對多相對的關聯關係時都是可用的。

測試程式碼如下:

$role = Role::find(2); $users = $role->users; echo '角色#'.$role->name.'下面的使用者:'; foreach ($users as $user) 
  echo $user->name.' ';//對應輸出為:角色#司令下面的使用者:admin fantasy baidu \

正如你看到的,處理多對多關聯要求一箇中間表。Eloquent 提供了一些有用的方法來與這個中間表進行互動,例如,我們假設 `User` 物件有很多與之關聯的 `Role` 物件,訪問這些關聯關係之後,我們可以使用這些模型上的`pivot` 屬性訪問中間表欄位:

$roles = User::find(1)->roles; foreach ($roles as $role) echo $role->pivot->role_id.'<br>';//對應輸出為:2 3 5
\

 注意我們獲取到的每一個 `Role` 模型都被自動賦上了 `pivot` 屬性。該屬性包含一個代表中間表的模型,並且可以像其它 Eloquent 模型一樣使用。

預設情況下,只有模型主鍵才能用在 `pivot` 物件上,如果你的 `pivot` 表包含額外的屬性,必須在定義關聯關係時進行指定:

return $this->belongsToMany('App\Models\Role')->withPivot('column1', 'column2');

比如我們修改role_user表增加一個欄位 username 資料如下:

Laravel 中的多對多關係詳解

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

/**
 * Class User
 * @package App\Models
 * @mixin \Eloquent
 */
class User extends Model
{
    /**
     * 使用者角色
     */
    public function roles()
    {
        //return $this->belongsToMany('App\Models\Role');
        return $this->belongsToMany('App\Models\Role')->withPivot('username');
    }
}
$user = User::find(1); foreach ($user->roles as $role) 
  echo $role->pivot->username;//對應輸出為:馬特馬特2馬特3\

如果你想要你的 `pivot` 表自動包含`created_at` 和 `updated_at` 時間戳,在關聯關係定義時使用 `withTimestamps` 方法:

return $this->belongsToMany('App\Models\Role')->withTimestamps();

 **通過中間表欄位過濾關聯關係**

你還可以在定義關聯關係的時候使用 `wherePivot` 和 `wherePivotIn` 方法過濾`belongsToMany` 返回的結果集:

return $this->belongsToMany('App\Models\Role')->withPivot('username')->wherePivot('username', '馬特2');

//return $this->belongsToMany('App\Models\Role')->wherePivotIn('role_id', [1, 2]);

測試程式碼如下:

$user = User::find(1); print_r($user->roles->toArray())

以上對應輸出:

Array
(
    [0] => Array
        (
            [id] => 3
            [name] => 軍長
            [created_at] => 2017-06-14 10:38:57
            [updated_at] => 2017-06-15 10:39:01
            [pivot] => Array
                (
                    [user_id] => 1
                    [role_id] => 3
                    [username] => 馬特2
                )

        )

)

如果你想要你的`pivot`表自動包含`created_at`和`updated_at`時間戳,在關聯關係定義時使用`withTimestamps`方法:

return $this->belongsToMany('App\Models\Role')->withTimestamps();

相關文章