Reputation: 101
I built a blog with Laravel, but I haven't figured out how to add tags to it. I would like to avoid using packages. Here is the code I have so far:
TagsController -
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Tag;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Session;
class TagsController extends Controller
{
public function index() {
$tags = Tags::paginate(4);
return view('dashboard/tags/index', compact('tags'));
}
public function create() {
return view('dashboard/tags/create');
}
public function store() {
$tags = new Tags;
$tags->name= $request->input('name');
$tags->save();
Session::flash('flash_message', 'Tag successfully added!');
return Redirect('/dashboard/tags');
}
}
Tag Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
public function posts()
{
return $this->belongsToMany('App\Post');
}
}
I'm not sure how to add more than one tag, so I can put it on the posts.
I'm on Laravel 5.3.
Upvotes: 7
Views: 12631
Reputation: 635
You can create polymorphic MorphMany relation for this. It's pretty simple.
// Tags table
Schema::create('tags', function (Blueprint $table) {
$table->bigIncrements('id');
$table->morphs('taggable');
$table->string('name');
$table->timestamps();
});
Now for the relationship between the tables.
On Tag Table
public function taggable()
{
return $this->morphTo();
}
On Any other model that can have a tag, for instance in your case, its posts table.
public function tags()
{
return $this->morphMany(Tag::class, 'taggable');
}
Next on, while fetching, you can create a simple getter on Post model likeso
protected $appends =['tags'];
public function getTagsAttribute()
{
return $this->tags()->pluck('name')->toArray();
}
Upvotes: 0
Reputation: 594
I think the Easiest way to do post tagging and other relations with tags is by using many-to-many polymorphic relationship... morphedByMany()
and morphToMany()
.
See this example code...
In migration their are 3 tables
posts
,tags
,taggables
# --- for Post Table ---
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
// ---
});
}
# --- for Tags Table ---
public function up()
{
Schema::create('tags', function (Blueprint $table) {
$table->increments('id');
$table->string('tagname');
});
}
# --- for Taggables Table ---
public function up()
{
Schema::create('taggables', function (Blueprint $table) {
$table->integer('tag_id');
$table->integer('taggable_id'); // for storing Post ID's
$table->string('taggable_type'); // Aside from Post, if you decide to use tags on other model eg. Videos, ...
});
}
In the Model
# Tag.php Model
class Tag extends Model
{
protected $fillable = [
'tagname',
];
public function post()
{
return $this->morphedByMany('Yourapp\Post', 'taggable');
}
}
# Post.php Model
class Post extends Model
{
protected $fillable = [
'title',
# and more...
];
public function tags()
{
return $this->morphToMany('Yourapp\Tag', 'taggable');
}
}
In the AppServiceProvide.php ~ Yourapp/app/Providers/AppServiceProvider.php
public function boot()
{
//... by creating this map you don't need to store the "Yourapp\Post" to the "taggable_type" on taggable table
Relation::morphMap([
'post' => 'Yourapp\Post',
'videos' => 'Yourapp\Videos', // <--- other models may have tags
]);
}
Now using Elequent you can easily access data
$post->tags; # retrieve related tags for the post
$tags->post; # or $tags->post->get() retrieve all post that has specific tag
For storing and updating post
public function store(Request $request) # SAVING post with tags
{
$validatedData = $request->validate([
'title' => 'required',
//----
// Validate tags and also it should be an Array but its up to you
'tag' => 'required|array|exists:tags,id' # < (exist:tags,id) This will check if tag exist on the Tag table
]);
$post = Post::create([
'title' => $request->input('title'),
//----
]);
//Adding tags to post, Sync() the easy way
$post->tags()->sync($request->input('tag'));
return "Return anywhare";
}
public function update(Request $request, $id) # UPDATE tags POST
{
# Validate first and ...
$post = Post::find($id)->first();
$post->title = $request->input('title');
$post->save();
//Updating tags of the post, Sync() the easy way
$post->tags()->sync($request->input('tag'));
return "Return anywhare";
}
Lastly, in your form tag inputs
<input name="tag[]" ...
For more details of many To many Polymorphic.
Upvotes: 6
Reputation: 12835
A post can have multiple/many tags and a tag can be shared by multiple/many posts. Which basically means that there is a Many-To-Many relationship between Post and Tag.
To define a many-to-many relation you will need 3 database tables
post_tag will have post_id and tag_id with migration as
class CreatePostTagPivotTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('post_tag', function(Blueprint $table)
{
$table->integer('post_id')->unsigned()->index();
$table->foreign('post_id')->references('id')->on('posts')->onUpdate('cascade')->onDelete('cascade');
$table->integer('tag_id')->unsigned()->index();
$table->foreign('tag_id')->references('id')->on('tags')->onUpdate('cascade')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('post_tag');
}
}
The you can define the relationship in respective models as
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
public function posts()
{
return $this->belongsToMany('App\Post');
}
}
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function tags()
{
return $this->belongsToMany('App\Tag');
}
}
Then you can access the relation, like you normally do $tag->with('posts')->get();
to get all posts associated with a tag and so on.
By the way there's a typo in your controller code new Tags
it should be $tag = new Tag;
. The model name that you have is Tag
.
Hope this helps.
Then in your create-post form you could have an input filed like
<input type="text" name="tags" class="form-control"/>
//here you can input ','(comma)separated tag names you want to associate with the post
And in your PostsController
public function store(Request $request)
{
$post = Post::create([
'title' => $request->get('title'),
'body' => $request->get('body')
});
if($post)
{
$tagNames = explode(',',$request->get('tags'));
$tagIds = [];
foreach($tagNames as $tagName)
{
//$post->tags()->create(['name'=>$tagName]);
//Or to take care of avoiding duplication of Tag
//you could substitute the above line as
$tag = App\Tag::firstOrCreate(['name'=>$tagName]);
if($tag)
{
$tagIds[] = $tag->id;
}
}
$post->tags()->sync($tagIds);
}
}
Upvotes: 24