Laravelで中間テーブルを活用する
目次
中間テーブルとは?
中間テーブルとは、多対多のリレーションを実現するためのテーブルです。
Laravelにおいてテーブル名は複数形にするのが一般的ですが、中間テーブルに関しては双方のテーブル名の単数形で命名します。
どういう時に利用するの?
例えばユーザに複数の権限を設定したい場合などに使用します。

その他にもユーザが複数獲得出来るバッジや、画像をfilesテーブルで一括管理する場合のメニューと画像の紐付け、ブログ記事とタグなど色々な活用方法があります。
中間テーブルの登録・更新方法
まず前提としてModelにリレーションを設定します。
class Menu extends Model
{
// 中略
public function files()
{
return $this->belongsToMany('App\Models\Entities\File', 'menu_file', 'menu_id', 'file_id');
}
例えばメニューに紐付ける画像を中間テーブルとして保存したい場合は以下のようになります。
//画像ファイルの保存
if (isset($inputs['files'])) {
$file_ids = $this->saveFiles($inputs); // ファイルを保存し、ファイルIDを配列で返す
}
if (isset($file_ids) && is_array($file_ids)) {
$article->files()->sync($file_ids);
}
リレーションに対して紐付けたいIDの配列を利用してsyncするたけです。
簡単ですね!
ちなみに、元々紐付いていたファイルIDのうち使用されなくなったものは自動的に中間テーブルから削除されます。
中間テーブルの応用編
メニューの画像にソート順を指定したい場合はどうでしょうか?
まず中間テーブルにソート順を指定するカラムを追加します。ここでは「sort_no」というカラムを追加したという前提で進めます。
先ほどファイル保存用に使用していたメソッドsaveFilesを次のように変更します。
public function saveFiles($inputs, $prefix = null): array
{
$fileRepository = app(FileRepositoryInterface::class);
$directory = date('Ymd');
$file_ids = [];
if (isset($inputs[$prefix . 'files'])) {
foreach ($inputs[$prefix . 'files'] as $index => $file) {
$is_new_file = !isset($inputs[$prefix . 'file_ids']) || $inputs[$prefix . 'file_ids'][$index] == 0;
$file_path = str_replace(Storage::url(''), '', $file);
$mime_type = Storage::mimeType($file_path);
$file_size = Storage::size($file_path);
list($basename, $file_extension) = explode('.', basename($file));
$file_data = [
'directory' => 'tmp',
'name' => $basename,
'mime_type' => $mime_type,
'extension' => $file_extension,
'size' => $file_size,
'file_name' => $inputs[$prefix . 'file_names'][$index] ?? null,
];
if (!$is_new_file) {
// 登録済みファイル
$file = $fileRepository->find((int)$inputs[$prefix . 'file_ids'][$index]);
$file_data['directory'] = $file->directory;
$file = $fileRepository->edit($file, $file_data);
} else {
// 新規ファイル
$file = $fileRepository->new($file_data);
if (!Storage::exists($directory)) {
Storage::makeDirectory($directory);
}
$file->directory = $directory;
Storage::move($file_path, $directory . '/' . $file->basename);
}
$file = $fileRepository->persist($file);
// sort_noは中間テーブル持たせるカラムでファイルのソート順として利用
$file_ids[$file->id]['sort_no'] = $index + 1;
}
}
// 使用されなくなったファイルの削除
if (isset($inputs[$prefix . 'old_file_ids'])) {
foreach ($inputs[$prefix . 'old_file_ids'] as $old_file_id) {
if (in_array($old_file_id, array_keys($file_ids))) {
continue;
}
$file = $fileRepository->find((int)$old_file_id);
$file->delete();
$old_file = $file->directory . '/' . $file->basename;
Storage::delete($old_file);
}
}
return $file_ids;
}
$file_idsをファイルIDの単純な配列にするのではなく、多次元の連想配列の中に各ファイルIDの子配列としてsort_noの値を指定すると中間テーブル上のsort_noを更新可能になります。
あとはメニューモデルのリレーションにソート順を指定します。
class Menu extends Model
{
// 中略
public function files()
{
return $this->belongsToMany('App\Models\Entities\File', 'menu_file', 'menu_id', 'file_id')
->orderByPivot('sort_no');
}
通常のorderByだとfilesテーブルの値を参照しようとするのでorderByPivotを利用します。
ソート順の他に表示、非表示のフラグを持たせたりすることも可能です。
親子テーブルとして保存するよりも非常にシンプルに更新が可能なので是非中間テーブルを活用してみてください!



















