I am working on a blogging application in Laravel 8.
There is the option to add tags to articles. There is a many-to-many relationship between articles and tags. I have an article_tag pivot table.
In the Tag
model I have:
class Tag extends Model
{
use HasFactory;
protected $fillable = ['name'];
public function articles()
{
return $this->belongsToMany(Article::class);
}
}
To the Article
model I have added the tags()
method
public function tags()
{
return $this->belongsToMany(Tag::class)->as('tags');
}
I the ArticleController controller I have the two methods for editing and updating an article:
public function edit($id)
{
$article = Article::find($id);
return view(
'dashboard/edit-article',
[
'categories' => $this->categories(),
'tags' => $this->tags(),
'selected_tags' => $this->tags()->pluck('id')->toArray(),
'article' => $article
]
);
}
public function update(Request $request, $id)
{
$validator = Validator::make($request->all(), $this->rules, $this->messages);
if ($validator->fails()) {
return redirect()->back()->withErrors($validator->errors())->withInput();
}
$fields = $validator->validated();
$article = Article::find($id);
// If a new image is uploaded, set it as the article image
// Otherwise, set the old image...
if (isset($request->image)) {
$imageName = md5(time()) . Auth::user()->id . '.' . $request->image->extension();
$request->image->move(public_path('images/articles'), $imageName);
} else {
$imageName = $article->image;
}
$article->title = $request->get('title');
$article->short_description = $request->get('short_description');
$article->category_id = $request->get('category_id');
$article->tags[] = $request->get('tags[]');
$article->featured = $request->has('featured');
$article->image = $request->get('image') == 'default.jpg' ? 'default.jpg' : $imageName;
$article->content = $request->get('content');
// Save changes to the article
$article->save();
//Attach tags to article
if ($request->has('tags')) {
$article->tags()->attach($request->tags);
}
return redirect()->route('dashboard.articles')->with('success', 'The article titled "' . $article->title . '" was updated');
}
The form for editing an article:
<form method="POST" action="{{ route('dashboard.articles.update', [$article->id]) }}" enctype="multipart/form-data"
novalidate>
@csrf
<div class="row mb-2">
<label for="title" class="col-md-12">{{ __('Title') }}</label>
<div class="col-md-12 @error('title') has-error @enderror">
<input id="title" type="text" placeholder="Title"
class="form-control @error('title') is-invalid @enderror" name="title"
value="{{ old('title', $article->title) }}" autocomplete="title" autofocus>
@error('title')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-2">
<label for="short_description" class="col-md-12">{{ __('Short description') }}</label>
<div class="col-md-12 @error('short_description') has-error @enderror">
<input id="short_description" type="text" placeholder="Short description"
class="form-control @error('short_description') is-invalid @enderror" name="short_description"
value="{{ old('short_description', $article->short_description) }}" autocomplete="short_description"
autofocus>
@error('short_description')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-2">
<label for="category" class="col-md-12">{{ __('Category') }}</label>
<div class="col-md-12 @error('category_id') has-error @enderror">
<select name="category_id" id="category" class="form-control @error('category_id') is-invalid @enderror">
<option value="0">Pick a category</option>
@foreach ($categories as $category)
<option value="{{ $category->id }}"
{{ $category->id == $article->category->id ? 'selected' : '' }}>{{ $category->name }}</option>
@endforeach
</select>
@error('category_id')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-2">
<label for="tags" class="col-md-12">{{ __('Tags') }}</label>
<select name="tags[]" id="tags" class="form-control" multiple="multiple">
@foreach ($tags as $tag)
<option value="{{ $tag->id }}" {{ in_array($tag->id, $selected_tags) ? 'selected' : '' }}>
{{ $tag->name }}</option>
@endforeach
</select>
</div>
<div class="row mb-2">
<div class="col-md-12 d-flex align-items-center switch-toggle">
<p class="mb-0 me-3">Featured article?</p>
<input class="mt-1" type="checkbox" id="featured" name="featured" value="featured"
{{ old('featured', $article->featured) ? 'checked' : '' }}>
<label class="px-1" for="featured">{{ __('Toggle') }}</label>
</div>
</div>
<div class="row mb-2">
<label for="image" class="col-md-12">{{ __('Article image') }}</label>
<div class="col-md-12 post-image @error('image') has-error @enderror">
<input type="file" value="{{ old('image', $article->image) }}" name="image" id="file"
class="file-upload-btn">
@error('image')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-2">
<label for="content" class="col-md-12">{{ __('Content') }}</label>
<div class="col-md-12 @error('content') has-error @enderror">
<textarea name="content" id="content" class="form-control @error('content') is-invalid @enderror"
placeholder="Content" cols="30" rows="6">{{ old('content', $article->content) }}</textarea>
@error('content')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-0">
<div class="col-md-12">
<button type="submit" class="w-100 btn btn-primary">
{{ __('Update') }}
</button>
</div>
</div>
</form>
For a reason I have not been able to spot, the update method fails with the error below:
Integrity constraint violation: 1062 Duplicate entry '306-1' for key 'PRIMARY' (SQL: insert into `article_tag` (`article_id`, `tag_id`)
Where is my mistake?
When updating a pivot table, you are not expected to use attach()
, you should use sync
or syncWithoutDetaching
.
This ensure you do not have duplicate entries.
https://laravel.com/docs/11.x/eloquent-relationships#syncing-associations