Решение для добавления тегов в Laravel - PullRequest
0 голосов
/ 25 мая 2018

В форме у меня есть поле Теги, которое является просто стандартным текстовым полем.Пользователь может ввести имя тега, и оно будет добавлено в статью.

У меня уже есть три таблицы: tags, taggables и articles, и они связаны с помощью методов связи Eloquent, учитывая настройкив на предыдущий вопрос я задавал.

Это мой метод обновления в моем ArticleController

/**
 * Update the specified resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  int  $id
 * @return \Illuminate\Http\Response
 */
public function update(Request $request, $id)
{
    $validatedData = $request->validate([
        'title' => 'required',
        'excerpt' => 'required',
    ]);

    $article = Article::find($id);

    $article->title = $request->get('title');
    $article->author = $request->get('author');
    $article->category = $request->get('category');
    $article->excerpt = $request->get('excerpt');
    $article->content = $request->get('content');
    $article->featuredImage = $request->get('featuredImage');
    $article->featuredVideo = $request->get('featuredVideo');
    $article->readingTime = $request->get('readingTime');
    $article->published = $request->get('published');

    $article->save();

    /**
     * Once the article has been saved, we deal with the tag logic.
     * Grab the tag or tags from the field, sync them with the article
     */
    $tags = $request->get('tags');
    $comma = ',';

    if (!empty($tags)) {
        if (strpos($tags, $comma) !== false) {
            $tagList = explode(",", $tags);

            // Loop through the tag array that we just created
            foreach ($tagList as $tags) {

                // Get any existing tags
                $tag = Tag::where('name', '=', $tags)->first();

                // If the tag exists, sync it, otherwise create it
                if ($tag != null) {
                    $article->tags()->sync($tag->id);
                } else {
                    $tag = new Tag();

                    $tag->name = $tags;
                    $tag->slug = str_slug($tags);

                    $tag->save();

                    $article->tags()->sync($tag->id);
                }
            }
        } else {
            // Only one tag
            $tag = Tag::where('name', '=', $tags)->first();

            if ($tag != null) {
                $article->tags()->sync($tag->id);
            } else {
                $tag = new Tag();

                $tag->name = $tags;
                $tag->slug = str_slug($tags);

                $tag->save();

                $article->tags()->sync($tag->id);
            }
        }
    }

    return back();
    return redirect()->back();
}

В разделе этого метода, который ищет теги, явыполните следующие действия:

  1. Проверьте, не является ли поле не пустым
  2. Проверьте, содержит ли отправленная строка запятую
  3. Если есть запятая, я использую explode() для преобразования строки в массив
  4. Цикл по массиву, чтобы увидеть, существует ли данный тег в строке
  5. Если его не существует, я создаю его, а затем синхронизируюсо статьей, в противном случае я просто синхронизирую ее

Этот подход кажется очень грязным, однако, есть ли способ, которым я мог бы сделать это чище?

Обновление с учетом предоставленных ответов

Я выбрал следующий подход:

/**
 * Store a newly created resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function store(Request $request)
{
    $validatedData = $request->validate([
        'title' => 'required',
        'excerpt' => 'required',
    ]);

    $article = new Article();

    $article->title = $request->get('title');
    $article->author = $request->get('author');
    $article->category = $request->get('category');
    $article->excerpt = $request->get('excerpt');
    $article->content = $request->get('content');
    $article->featuredImage = $request->get('featuredImage');
    $article->featuredVideo = $request->get('featuredVideo');
    $article->readingTime = $request->get('readingTime');
    $article->published = $request->get('published');

    //If no featured image set, automatically create featured image placeholder
    if ($request->get('featuredImage') == null) {
        $article->featuredImage = "http://via.placeholder.com/350x150";
    }

    $article->save();

    // Handle Tags
    $tags = $request->get('tags');

    if (!empty($tags)) {
        $tagList = array_filter(explode(",", $tags));

        // Loop through the tag array that we just created
        foreach ($tagList as $tags) {
            $tag = Tag::firstOrCreate(['name' => $tags, 'slug' => str_slug($tags)]);
        }

        $tags = Tag::whereIn('name', $tagList)->get()->pluck('id');

        $article->tags()->sync($tags);
    }

    return redirect('editable/news-and-updates')->with('success', 'Article has been added');
}

И затем, чтобы отобразить теги при обновленииЯ сделал следующее:

/**
 * Show the form to edit this resource
 */
public function edit($id)
{
    $user = auth::user();
    $article = Article::find($id);

    // Get the tags associated with this article and convert to a comma seperated string
    if ($article->has('tags')) {
        $tags = $article->tags->pluck('name')->toArray();

        $tags = implode(', ', $tags);
    } else {
        $tags = "";
    }

    return view('editable.news.edit', compact('article', 'user', 'tags'));
}

По сути, я просто беру теги, связанные со статьей, преобразую их в массив, а затем использую implode().

Это дает мне теги в виде списка через запятую в поле тегов, например:

blue, red, orange

Однако при обновлении, если я пытаюсь сохранить с тем жетеги в поле, которое я получаю:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'sauce' for key 'tags_slug_unique' (SQL: insert into теги ( имя , slug , updated_at , create_at ) values ( sauce, sauce, 2018-05-26 11:42:17, 2018-05-26 11:42:17))

Вот тег тега для справки

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTagsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('slug')->unique();
            $table->timestamps();
        });

        Schema::create('taggables', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('tag_id')->unsigned();
            $table->integer('taggable_id')->unsigned();
            $table->string('taggable_type');

            $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('taggables');
        Schema::dropIfExists('tags');
    }
}

Ответы [ 3 ]

0 голосов
/ 25 мая 2018

Может быть, что-то вроде этого, набрано только с головы, не проверено, но я надеюсь, что это как-то даст вам идею

public function update(Request $request, Article $article)
{
    $validatedData = $request->validate([
        'title' => 'required',
        'excerpt' => 'required',
        // Make validation for all inputs !
    ]);

    // Fill model with inputs and save (make sure that inputs are in fillable model property)
    $article->fill($request->all())->save();

    // Handle Tags 
    $this->handleTags($request, $article);

    // Return Redirect to previous URL
    return redirect()->back();
}

/**
 * Handle Tags for Article
 * @param  \Illuminate\Http\Request  $request
 * @param \App\Article $article
 * @return void
 */
public function handleTags(Request $request, Article $article){
    /**
     * Once the article has been saved, we deal with the tag logic.
     * Grab the tag or tags from the field, sync them with the article
     */
    $tagsNames = explode(',', $request->get('tags'));

    // Create all tags (unassociet)
    foreach($tagsNames as $tagName){
        Tag::firstOrCreate(['name' => $tagName, 'slug' => str_slug($tagName)])->save();
    }

    // Once All tags are created we can query them
    $tags = Tag::whereIn('name', $tagNames)->get()->pluck('id')->get();
    $article->tags()->sync($tags);
}
0 голосов
/ 07 июня 2019

Я думаю, что самый простой способ сделать тегирование - это использовать полиморфные отношения «многие ко многим» ... morphedByMany() и morphToMany().Посмотрите этот пример кода ...

В миграции их 3 таблицы articles, tags, taggables

# --- for Article Table ---
public function up()
{
    Schema::create('articles', 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 Article ID's
        $table->string('taggable_type'); // Aside from Article, if you decide to use tags on other model eg. Videos, ... 
    });
}

ВМодель

# Tag.php Model
class Tag extends Model
{
     protected $fillable = [

            'tagname',
    ];

    public function article()
    {
        return $this->morphedByMany('Yourapp\Article', 'taggable');
    }

}


# Article.php Model
class Article extends Model
{
     protected $fillable = [
        'title',
        # and more...
    ];
    public function tags()
    {
        return $this->morphToMany('Yourapp\Tag', 'taggable');
    }

}

В 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([
        'article' => 'Yourapp\Article',
        'videos' => 'Yourapp\Videos', // <--- other models may have tags
    ]);

}

Теперь вы используете Elequent youможет легко получить доступ к данным

   $article->tags; # retrieve related tags for the article
   $tags->articles; # or $tags->articles->get()  retrieve all article that has specific tag

Для хранения и обновления статьи

# SAVING article with tags
public function store(Request $request) 
{
    $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
    ]);

    $article = Article::create([
        'title' => $request->input('title'),
        //----
    ]);

    //Adding tags to article, Sync() the easy way
    $article->tags()->sync($request->input('tag'));

    return "Return anywhare";
}

# UPDATE tags article
public function update(Request $request, $id) 
{   
    // Validate first and ...
    $article = Article::find($id)->first();
    $article->title = $request->input('title');
    $article->save();
    //Updating tags of the article, Sync() the easy way
    $article->tags()->sync($request->input('tag'));

    return "Return anywhare";
}

Для более Подробнее о многих длямного полиморфных отношений

0 голосов
/ 25 мая 2018

Нет необходимости проверять наличие запятых и наличие двух разных путей.Если запятых нет, explode вернет один элемент для повторения.Вы можете буквально просто удалить if и else.

$tagList = explode(",", $tags);

// Loop through the tag array that we just created
foreach ($tagList as $tags) {

    // Get any existing tags
    $tag = Tag::where('name', '=', $tags)->first();

    // If the tag exists, sync it, otherwise create it
    if ($tag != null) {
        $article->tags()->sync($tag->id);
    } else {
        $tag = new Tag();

        $tag->name = $tags;
        $tag->slug = str_slug($tags);

        $tag->save();

        $article->tags()->sync($tag->id);
    }
}

Кроме того, есть возможность сделать firstOrCreate, которую вы можете найти в документации для здесь .

Метод firstOrCreate попытаетсянайдите запись базы данных, используя заданные пары столбец / значение.Если модель не может быть найдена в базе данных, будет вставлена ​​запись с атрибутами из первого параметра, а также с атрибутами во втором необязательном параметре.

Это можно использовать для рефакторинга кода вследующее:

$tagList = explode(",", $tags);

// Loop through the tag array that we just created
foreach ($tagList as $tags) {
    $tag = Tag::firstOrCreate(['slug' => $tags];
}

$tags = Tag::whereIn('name', $tagList)->get()->pluck('id')->get();
$article->tags()->sync($tags);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...