# EloquentORM關聯關系之遠層一對多
“遠層一對多”指的是通過一個中間關聯對象訪問遠層的關聯關系,比如用戶與文章之間存在一對多關系,國家與用戶之間也存在一對多關系,那么通過用戶可以建立國家與文章的之間的一對多關聯關系,我們稱之為“遠層一對多”,我們可以利用這種關聯關系處理多語言環境下的文章列表。
## 軟件版本
* Laravel Version 5.4.19
* PHP Version 7.0.8
## 關鍵字和表
* `hasOne()`
* `hasMany()`
* `belongsTo()`
* `hasManyThrough()`
* `posts` 、`countries` 和 `users` 表
數據操作之前請先配置好,數據庫的一些連接信息。例如下面使用mysql數據庫,修改項目根目錄下的 `.env` 文件內容。
```
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db_name
DB_USERNAME=db_username
DB_PASSWORD=db_password
```
我們定義關聯關系,文章表 `posts` 和 國家表 `countries`(**假設用戶表使用系統自帶的**)
## 生成模型和遷移文件
```shell
php artisan make:migration create_posts_table --create=posts
php artisan make:migration create_countries_table --create=countries
php artisan make:model Post
php artisan make:model Country
```
### 編輯遷移文件
文件 `<project>/database/migrate/*_create_users_table.php` 內容如下
```
$table->increments('id');
$table->string('name');
$table->unsignedInteger('country_id'); // 新增這個表字段
$table->string('email',30)->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
```
文件 `<project>/database/migrate/*_create_posts_table.php` 內容如下
```
$table->increments('id');
$table->unsignedInteger('user_id');
$table->string('title', 60);
$table->text('body');
$table->timestamps();
$table->timestamp('published_at')->nullable();
$table->foreign('user_id')
->references('id')
->on('users')
->onUpdate('cascade')
->onDelete('cascade');
```
文件 `<project>/database/migrate/*_create_countries_table.php` 內容如下
```
$table->increments('id');
$table->string('name',20);
$table->string('display_name',20);
$table->timestamps();
```
### 運行 php artisan 命令保存修改到數據庫
```shell
php artisan migrate
```
> 執行上面的命令后數據庫將生成五張表,
> migrations
> password_resets
> post
> users
> countries
### 定義關聯關系和修改模型的 fillable 屬性
`App\Country` 模型中定義與 `App\Post` 模型的遠層一對多關系
```
public function user()
{
/**
* User::class related 關聯模型
* country_id foreignKey 當前表關聯字段
* id localKey 關聯表字段
*/
return $this->hasMany('App\User' , 'country_id' , 'id');
}
public function posts()
{
/**
* @param string $related
* @param string $through
* @param string|null $firstKey
* @param string|null $secondKey
* @param string|null $localKey 不填默認為當前模型的主鍵
*/
return $this->hasManyThrough('App\Post' , 'App\User' , 'country_id' , 'user_id', 'id');
}
```
> 由此可見我們通過 `hasManyThrough()` 方法來定義遠層一對多關聯。其中第一個參數是關聯對象類名,第二個參數是中間對象類名。
`App\Post` 模型關聯關系:
```
protected $fillable = ['title' , 'user_id' , 'body' , 'published_at'];
public function user()
{
/**
* User::class related 關聯模型
* user_id ownerKey 當前表關聯字段
* id relation 關聯表字段
*/
return $this->belongsTo(User::class , 'user_id' , 'id');
}
```
`App\User` 模型關聯關系
```
public function posts()
{
/**
* Post::class related 關聯模型
* user_id foreignKey 當前表關聯字段
* id localKey 關聯表字段
*/
return $this->hasMany(Post::class , 'user_id' , 'id');
}
public function country()
{
/**
* Country::class related 關聯模型
* id foreignKey 當前表關聯字段
* country_id localKey 關聯表字段
*/
return $this->hasOne(Country::class , 'id' , 'country_id');
}
```
### 使用 tinker 填充數據
修改?`/databases/factories/ModelFactory.php`,新增關聯數據。
```
$factory->define(App\User::class , function(Faker\Generator $faker){
static $password;
$country_ids = \App\Country::pluck('id')->toArray();
return [
'name' => $faker->name ,
'country_id' => $faker->randomElement($country_ids) ,
'email' => $faker->unique()->safeEmail ,
'password' => $password ? : $password = bcrypt('secret') ,
'remember_token' => str_random(10) ,
];
});
$factory->define(App\Post::class , function(Faker\Generator $faker){
$user_ids = \App\User::pluck('id')->toArray();
return [
'user_id' => $faker->randomElement($user_ids) ,
'title' => $faker->word ,
'body' => $faker->text() ,
];
});
$factory->define(App\Country::class , function(Faker\Generator $faker){
return [
'name' => $faker->country ,
'display_name' => $faker->country ,
];
});
```
```
php artisan tinker
## 進入到 tinker 界面執行如下命令
namespace App
factory(Country::class,2)->create(); // 生成兩個國家數據
factory(User::class,3)->create(); // 生成3個用戶
factory(Post::class,30)->create() // 生成30條 posts 表的測試數據
```
## 關聯操作
### 新增數據
#### 使用 save() 方法進行關聯數據的新增
常見的新增 `posts` 數據場景是用戶發布一篇文章,如下:
```
$post = new \App\Post([
'title' => 'test title',
'body' => 'test body',
'published_at' => null,
]);
\Auth::user()->posts()->save($post);
// 或者獲取 \Request 對象傳遞的數據寫入
$post = new \App\Post($request->all());
\Auth::user->posts()->save($post));
```
#### 使用 saveMany() 方法進行關聯數據的批量新增
```
// 如果需要保存多個關聯模型,可以使用 `saveMany()` 方法,如下:
\Auth::user()->posts()->saveMany([
new \App\Post(['title' => 'test title', 'body' => 'test body', 'published_at' => null]),
new \App\Post(['title' => 'test title2', 'body' => 'test body2', 'published_at' => null])
]);
```
#### 使用 create() 方法進行關聯數據的新增
```
\Auth::user()->posts()->create([
'title' => 'test title3',
'body' => 'test body3',
'published_at' => null,
]);
```
> `create()` 方法接受屬性數組、 創建模型,然后寫入數據庫,`save()`?和?`create()`?的不同之處在于?`save()`?接收整個 Eloquent 模型實例,而?`create()`?接收原生 PHP 數組。
> **注意:** 使用 create 方法之前確保 `$fillable` 屬性填充批量賦值。
### 查詢數據
#### 根據國家查詢數據
##### 查詢國家下的用戶和發布的文章
```
// 查詢國家下的所有文章數據
$country = \App\Country::find(1);
$posts = $country->posts;
// 或者通過下面的關聯關系
$posts = \App\Country::with(array('user','posts'))->find(1);
```
##### 查詢國家下的用戶或文章
```
$posts = \App\Country::with(array('user'))->find(1);
$posts = \App\Country::with(array('user.posts'))->find(1);
$posts = \App\Country::with(array('posts.user'))->find(1);
```
#### 查詢文章所屬國家信息
```
$posts = \App\Post::with(['user.country'])->get();
```
##### 獲取用戶列表并關聯所屬文章
```
\App\User::with('posts')->get()->toArray();
```
#### 查詢文章所屬用戶
##### 查詢單個文章的關聯用戶信息
```
$post = \App\Post::find(1); // 獲取文章數據
$user = $post->user->toArray(); // 獲取文字所屬用戶
```
##### 文章列表關聯用戶信息
```
$post = \App\Post::with('user')->get()->toArray();
```
### 關聯刪除
通過用戶關聯刪除文章信息
```
$user = \App\User::find(1);
$user->posts()->delete(); // 刪除 posts 表中相關記錄
```
通過國家關聯刪除文章信息
```
$country = \App\Country::find(1);
$country->posts()->delete(); // 關聯刪除 posts 表中country_id 為 1 的相關記錄,此處country_id 為 1 通過用戶表關聯得出。
```
> 相關的更多的關聯刪除操作,可以自行嘗試。
### 更新數據
#### 通過關聯 User 數據
- 介紹
- Laravel5發送郵件使用Service隔離業務
- 如何使用Repository模式
- 如何使用Service模式
- 如何使用Presenter模式
- Laravel 5.* 執行遷移文件報錯:Specified key was too long error
- EloquentORM關聯關系
- EloquentORM關聯關系之一對一
- EloquentORM關聯關系之一對多
- EloquentORM關聯關系之遠層一對多
- EloquentORM關聯關系之多對多
- EloquentORM關聯關系之多態關聯
- EloquentORM關聯關系之多對多多態關聯
- Laravel測試
- Laravel中涉及認證跳轉地址的修改的地方
- Laravel中Collection的基本使用
- all
- avg
- chuck
- collapse
- combine
- contains
- containsStrict
- count
- diff
- diffAssoc
- diffKeys
- each
- every
- except
- filter
- first
- flatMap
- flatten
- flip
- forget
- forPage
- get
- groupBy
- has
- implode
- intersect
- intersectKey
- isEmpty
- isNotEmpty
- keyBy
- keys
- last
- map
- mapWithKeys
- max
- median
- merge
- min
- mode
- nth
- only
- partition
- pipe
- pluck
- pop
- prepend
- pull
- push
- put
- random
- reduce
- reject
- reverse
- search
- shift
- shuffle
- slice
- sort
- sortBy
- sortByDesc
- splice
- split
- sum
- take
- tap
- times
- toArray
- toJson
- transform
- union
- unique
- uniqueStrict
- values
- when
- where
- whereStrict
- whereIn
- whereInStrict
- whereNotIn
- whereNotInStrict
- zip
- Laravel中Collection的實際使用
- collection中sum求和
- collection格式化計算數據
- collection格式化計算數據計算github事件得分總和
- collection格式化markdown數據列表
- collection格式化計算兩個數組的數據
- collection中reduce創建lookup數組
- TODO