リレーションを持つリソース——BelongsToとHasMany
リレーションとFilament
Laravelのリレーションとfilamentは深く統合されています。BelongsTo はフォームのSelectフィールドで、HasMany はRelationManagerで管理するのが基本パターンです。
今回はブログシステムを例にします。
Category (カテゴリ)
└── Post (記事) ← BelongsTo Category
└── Comment (コメント) ← HasMany Comments
モデルとマイグレーションの準備
まずカテゴリモデルを作成します。
php artisan make:model Category -m
<?php
// 📁 database/migrations/xxxx_create_categories_table.php
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
});
Postテーブルにカテゴリの外部キーを追加します。
php artisan make:migration add_category_id_to_posts_table
<?php
// 📁 database/migrations/xxxx_add_category_id_to_posts_table.php
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('category_id')
->nullable()
->constrained()
->nullOnDelete();
});
コメントモデルも作成します。
php artisan make:model Comment -m
<?php
// 📁 database/migrations/xxxx_create_comments_table.php
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained()->cascadeOnDelete();
$table->string('author_name');
$table->string('author_email');
$table->text('body');
$table->boolean('is_approved')->default(false);
$table->timestamps();
});
php artisan migrate
モデルにリレーションを定義
<?php
// 📁 app/Models/Post.php
class Post extends Model
{
protected $fillable = ['title', 'content', 'is_published', 'category_id'];
// Post は Category に属する
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
// Post は複数の Comment を持つ
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
}
<?php
// 📁 app/Models/Category.php
class Category extends Model
{
protected $fillable = ['name', 'slug'];
// Category は複数の Post を持つ
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}
<?php
// 📁 app/Models/Comment.php
class Comment extends Model
{
protected $fillable = ['post_id', 'author_name', 'author_email', 'body', 'is_approved'];
protected $casts = ['is_approved' => 'boolean'];
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}
BelongsTo をフォームで扱う
PostResourceのフォームにカテゴリ選択を追加します。
<?php
// 📁 app/Filament/Resources/PostResource.php
use Filament\Forms\Components\Select;
public static function form(Form $form): Form
{
return $form
->schema([
TextInput::make('title')
->label('タイトル')
->required(),
// BelongsTo: リレーション名と表示カラムを指定
Select::make('category_id')
->label('カテゴリ')
->relationship(
name: 'category', // リレーションメソッド名
titleAttribute: 'name', // 表示するカラム名
)
->searchable() // 入力で絞り込み
->preload() // 選択肢を事前ロード
->nullable() // null許容
->createOptionForm([
// インライン作成フォーム(+ボタンで開く)
TextInput::make('name')
->label('カテゴリ名')
->required(),
TextInput::make('slug')
->label('スラッグ')
->required(),
]),
// ...
]);
}
->createOptionForm([...]) を追加すると、Selectの隣に「+」ボタンが表示され、その場で新しいカテゴリを作成できます。
BelongsTo をテーブルで表示する
関連モデルのカラムはドット記法でアクセスできます。
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('title')
->label('タイトル')
->searchable(),
// category.name でリレーション先のカラムにアクセス
TextColumn::make('category.name')
->label('カテゴリ')
->sortable()
->badge()
->color('primary'),
// ...
]);
}
テーブルに ->searchable() を追加する場合は、eager loadingが必要です。リソースに以下を追加します。
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->with(['category']); // N+1問題を防ぐ
}
RelationManager を作成する
RelationManagerは編集ページの下部に表示されるサブテーブルです。Post編集画面でそのPostに紐づくCommentを管理できます。
php artisan make:filament-relation-manager PostResource comments body
引数:
PostResource→ 親リソース名comments→ リレーションメソッド名body→ テーブルに表示するカラム名
生成されるファイル:
app/Filament/Resources/PostResource/RelationManagers/
└── CommentsRelationManager.php
CommentsRelationManager を実装する
<?php
// 📁 app/Filament/Resources/PostResource/RelationManagers/CommentsRelationManager.php
namespace App\Filament\Resources\PostResource\RelationManagers;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
class CommentsRelationManager extends RelationManager
{
// リレーションメソッド名
protected static string $relationship = 'comments';
// サブテーブルのラベル
protected static ?string $title = 'コメント';
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('author_name')
->label('投稿者名')
->required(),
Forms\Components\TextInput::make('author_email')
->label('メールアドレス')
->email()
->required(),
Forms\Components\Textarea::make('body')
->label('コメント本文')
->required()
->columnSpanFull(),
Forms\Components\Toggle::make('is_approved')
->label('承認済み'),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('body')
->columns([
Tables\Columns\TextColumn::make('author_name')
->label('投稿者'),
Tables\Columns\TextColumn::make('body')
->label('コメント')
->limit(60),
Tables\Columns\IconColumn::make('is_approved')
->label('承認')
->boolean(),
Tables\Columns\TextColumn::make('created_at')
->label('投稿日時')
->dateTime('Y/m/d H:i'),
])
->filters([
Tables\Filters\TernaryFilter::make('is_approved')
->label('承認状態'),
])
->headerActions([
Tables\Actions\CreateAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
// 承認ボタン
Tables\Actions\Action::make('approve')
->label('承認')
->icon('heroicon-o-check')
->color('success')
->visible(fn ($record) => ! $record->is_approved)
->action(fn ($record) => $record->update(['is_approved' => true])),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
}
PostResourceに RelationManager を登録
<?php
// 📁 app/Filament/Resources/PostResource.php
public static function getRelations(): array
{
return [
RelationManagers\CommentsRelationManager::class,
];
}
これで Post 編集ページの下部にコメントのサブテーブルが表示されます。
HasManyThrough の扱い
hasManyThrough も通常の hasMany と同じように RelationManager で扱えます。
// User → Post → Comment の through リレーション
class User extends Model
{
public function comments(): HasManyThrough
{
return $this->hasManyThrough(Comment::class, Post::class);
}
}
RelationManager の $relationship にそのまま指定します。
php artisan make:filament-relation-manager UserResource comments body
Morph リレーションの基本
MorphMany は morphMany を使った RelationManager で対応できます。
たとえば Post と Product の両方に Image を紐付ける場合:
// 📁 app/Models/Post.php
public function images(): MorphMany
{
return $this->morphMany(Image::class, 'imageable');
}
RelationManager の生成はHasManyと同じです。
php artisan make:filament-relation-manager PostResource images path
RelationManager 内では $relationship = 'images' と定義するだけで動作します。
BelongsToMany(多対多)
TagとPostの多対多リレーションもRelationManagerで管理できます。
php artisan make:filament-relation-manager PostResource tags name
// 📁 CommentsRelationManager.php の代わりに TagsRelationManager.php
protected static string $relationship = 'tags';
->attachAction() / ->detachAction() を使うと、既存のTagをアタッチ・デタッチできます。
->headerActions([
Tables\Actions\AttachAction::make()
->preloadRecordSelect(), // 既存タグを選択してアタッチ
])
->actions([
Tables\Actions\DetachAction::make(), // アタッチを解除
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DetachBulkAction::make(),
]),
])
まとめ
BelongsToはフォームのSelect::make()->relationship()で実装- テーブルでは
TextColumn::make('category.name')のドット記法でリレーション先を表示 make:filament-relation-managerで HasMany のサブテーブルを生成- RelationManager は
getRelations()に登録することで編集ページに追加される HasManyThrough/MorphMany/BelongsToManyも同様のパターンで対応可能- N+1問題を防ぐため
getEloquentQuery()でwith()を設定する
次回はフォームのSection・条件付き表示・フィールド間連動を学びます。