php 54 lines · 9 steps

Managing many-to-many tags in Laravel

A service class that syncs, features, and prunes post tags through Eloquent's belongsToMany pivot methods.

Explained by highlit
1<?php
2 
3namespace App\Services;
4 
5use App\Models\Post;
6use App\Models\Tag;
7use Illuminate\Support\Str;
8use Illuminate\Support\Facades\DB;
9 
10class PostTagManager
11{
12 public function applyTags(Post $post, array $names): array
13 {
14 $tagIds = collect($names)
15 ->map(fn (string $name) => trim($name))
16 ->filter()
17 ->unique()
18 ->map(function (string $name) {
19 return Tag::firstOrCreate(
20 ['slug' => Str::slug($name)],
21 ['name' => $name],
22 )->id;
23 });
24 
25 return $post->tags()->sync($tagIds);
26 }
27 
28 public function feature(Post $post, Tag $tag): void
29 {
30 $post->tags()->syncWithoutDetaching([
31 $tag->id => ['featured' => true],
32 ]);
33 }
34 
35 public function unfeature(Post $post, Tag $tag): void
36 {
37 $post->tags()->updateExistingPivot($tag->id, ['featured' => false]);
38 }
39 
40 public function toggle(Post $post, Tag $tag): array
41 {
42 return $post->tags()->toggle($tag->id);
43 }
44 
45 public function clearStale(Post $post): int
46 {
47 $staleIds = $post->tags()
48 ->wherePivot('created_at', '<', now()->subMonths(6))
49 ->wherePivot('featured', false)
50 ->pluck('tags.id');
51 
52 return $post->tags()->detach($staleIds);
53 }
54}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Eloquent's belongsToMany relation exposes sync, toggle, and detach helpers so you rarely touch the pivot table by hand.
  2. 2firstOrCreate keyed on a slug keeps tags deduplicated even when the display name varies in casing or spacing.
  3. 3Pivot-aware queries like wherePivot let you filter and prune relationships by columns stored on the join table.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Managing many-to-many tags in Laravel — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code