Judia
Judia

Reputation: 101

Add Tags to Laravel Built Blog

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

Answers (3)

Usama Munir
Usama Munir

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

KirtJ
KirtJ

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

Donkarnash
Donkarnash

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

  1. posts
  2. tags
  3. post_tag

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

Related Questions