Laravel 中的同步(Sync)、附加(Attach)和分離(Detach):使用 Eloquent 管理關係

在 Laravel 中,Eloquent ORM(物件關聯映射)提供了與資料庫表互動的強大方法。在 Eloquent 中處理關係時,我們經常會遇到三種基本方法:sync、attach 和 detach。這些方法對於管理應用程式中模型之間的關係至關重要。在本文中,將探討這些方法之間的差異,並探索何時以及如何有效地使用它們。

Relationships in Laravel

Laravel 支援多種類型的模型關係(Relationships),包括:

這些關係使我們能夠在不同的資料庫表上連接相關資料。

Attach(附加):新增記錄附加方法主要用於多對多關係

用於在連接兩個模型的資料透視表中新增記錄。例如,假設一個使用者可以擁有多個角色,一個角色也可以擁有多個使用者。你可以使用 Attach 方法,透過在關係的中間表中插入一筆記錄,將角色附加到使用者:使用方法如下:

use App\Models\User;

$user = User::find(1);

$user->roles()->attach($roleId);

Detach(分離):從多對多關係中刪除記錄

有時可能需要從使用者中刪除角色。若要刪除多對多關係記錄,請使用 detach 方法。 detach 方法將從中間表中刪除對應的記錄;但是,兩個模型仍將保留在資料庫中:

// Detach a single role from the user...
$user->roles()->detach($roleId);

// Detach all roles from the user...
$user->roles()->detach();

sync:在多對多關係中同步記錄

sync 方法是一種強大的多對多關係記錄同步方法。它以相關模型 ID 的陣列作為參數,並確保資料透視表僅包含這些記錄。任何不在所提供數組中的現有記錄都將被刪除:

$roleIds = [1, 2, 3];
$user->roles()->sync($roleIds);

ER-Model

假設文章(Blog)與 標籤(Tag )的 ORM (物件關聯映射)如下圖

image 21

建立 Migration

建立 blog_post_tag 表格,作為 blog_posts & blog_tags 的關連

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        // 標籤
        Schema::create('blog_tags', function (Blueprint $table) {
            $table->id();
            $table->string('name');
        });

        // 部落格文章
        Schema::create('blog_posts', function (Blueprint $table) {
            $table->id(); //Primary Key
            $table->string('title');
            $table->text('content')->nullable();
        });

        Schema::create('blog_post_tag', function (Blueprint $table) {
            $table->foreignId('blog_post_id');
            $table->foreignId('blog_tag_id');
            $table->primary(['blog_post_id', 'blog_tag_id']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('blog_post_tag');
        Schema::dropIfExists('blog_tags');
        Schema::dropIfExists('blog_posts');
    }
};

建立 Model: BlogPost

在 Model 中用 Eloquent 使用 belongsToMany 來將 blog_posts & blog_tags 關連起來。

class BlogPost extends Model
{
    use HasFactory, CreatedUpdatedBy, BlogPostTrait;

    protected $fillable = [
        'title',
        'content',
    ];
    public function user()
    {
        return $this->belongsTo(User::class);
    }
    public function tags()
    {
        return $this->belongsToMany(BlogTag::class,'blog_post_tag');
    }
}

新增文章時(createPost)

在下面的程式碼中,需要先建立 標籤(Tag ) 的資料,然後再使用 $post->tags()->sync($tag_id); 就會自動在 table: blog_post_tag 建立相對應的記錄了。

/**
 * 新增文章
 */
public function createPost(array $data): ?Model
{
	$form_data = $data['form_data'] ?? [];

	// 驗證資料
	$validator = Validator::make($form_data, [
		'title'       => 'required|string|max:255',		
		'user_id'     => 'required|exists:users,id',
		'blog_category_id' => 'required|exists:blog_categories,id',
		'tags'        => 'array'
	]);

	if ($validator->fails()) {
		throw new \InvalidArgumentException($validator->errors()->first());
	}

	// 文章/tag...儲存
	return DB::transaction(function () use ($form_data) {
		// 文章
		$post = self::create([
			'title'       => $form_data['title'],
			'content'     => $form_data['content'],
			'user_id'     => $form_data['user_id']
		]);
		// tag
		if (!empty($form_data['tags'])) {
			// 先尋找 tag 是否存在, 不存在就新增
			$i = 0;
			foreach ($form_data['tags'] as $tag) {
				// Retrieve flight by name or create it if it doesn't exist...
				$t = BlogTag::firstOrCreate([
					'name' => $tag
				]);
				$tag_id[$i] = $t->id;
				$i++;
			}
			// 更新table: blog_post_tag 關連
			$post->tags()->sync($tag_id);
		}

		return $post;
	});
}

更新資料時(update)

更新與新增資料大致相同,需要先 找到 或 建立 標籤(Tag ) 的資料,然後再使用 $post->tags()->sync($tag_id); 就會自動在 table: blog_post_tag 建立相對應的記錄了。

/**
 * 更新作業
 * Update the specified resource in storage.
 */
public function update(Request $request)
{
	$input = $request->all();
	$form_data = $request->form_data;
	$id = $form_data['id'];


	$post = BlogPost::findOrFail($id);

		// 開始儲存記錄
		DB::transaction( function() use ($post, $form_data) {
			$post->update($form_data);

			// tag
		if (!empty($form_data['tags'])) {
			// 先尋找 tag 是否存在, 不存在就新增
			$i=0;
			foreach ($form_data['tags'] as $tag) {
				// Retrieve flight by name or create it if it doesn't exist...
				$t = BlogTag::firstOrCreate([
					'name'=>$tag
				]);
				$tag_id[$i] = $t->id;
				$i++;
			}
			// 更新table: blog_post_tag 關連
			$post->tags()->sync($tag_id);
		}
		});
	}
	return $this->sendResponse($post, __('response.data_updated_successfully', [], env('APP_LOCALE')));
}

刪除資料時(deletePost)

在刪除文章後,只要使用 $post->tags()->detach(); ,就可以移除table: blog_post_tag 相對應的記錄了

/**
 * 刪除單一記錄
 * @param int $id
 * @return bool
 */
public function deletePost(int $id): bool
{	
	$post = self::findOrFail($id);
	$post->tags()->detach();
	return $post->delete();
}

以上適用於大多數的基本用法

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


內容索引