Home > Mobile >  Add multiple categories for articles
Add multiple categories for articles

Time:10-08

I need to add several categories for a new article. I will write down everything I do in order:

migration of categories

public function up()
    {
        Schema::create('blog_categories', function (Blueprint $table) {
            $table->BigIncrements('id');
            $table->string('title', 128);
            $table->timestamps();
        });
    }

migration of articles

public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->BigIncrements('id');
            $table->string('title', 128);
            $table->string('slug', 64);
            $table->string('subtitle', 256)->nullable();
            $table->timestamps();
        });
    }

creating another migration category_article_table

public function up()
    {
        Schema::create('category_article', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('blog_category_id')->nullable();
            $table->foreign('blog_category_id')
                ->references('id')->on('blog_categories')->onDelete('set null');
            $table->unsignedBigInteger('article_id')->nullable();
            $table->foreign('article_id')
                ->references('id')->on('articles')->onDelete('cascade');
            $table->timestamps();
        });
    }

Doing belongsToMany in models

article model

public function blog_categories()
    {
        return $this->belongsToMany('App\Models\BlogCategory');
    }

category model

public function articles()
    {
        return $this->belongsToMany('App\Models\Article');
    }
}

Next, I write the function for adding an article in the controller (I think there is no need to write the function for adding a category, everything is clear there)

Articles controller

public static function saveArticle(Request $request) {
        $validator = Validator::make($request->all(), [
            'blog_category_id' => 'required|numeric',
            'title' => 'required|max:128',
            'slug' => 'required|unique:articles|max:64',
            'subtitle' => 'max:256',
        ]);

        if ($validator->fails()) {
            return response()->json([
                'message' => $validator->errors()->first()
            ], 422);
        }

        $article = new Article();

        $blog_category = BlogCategory::where('id', $request->blog_category_id)->first();

        if(!$blog_category){
            return response()->json([
                'message' => 'Blog category not found'
                ], 404);
        }

        $article->blog_category_id = $request->blog_category_id;
        $article->title = $request->title;
        $article->slug = $request->slug;
        $article->subtitle = $request->subtitle;

        $article->save();

        return Article::where('slug', $article->slug)->first();
    }

I have a method in the function to add one category. The question of how to add here so that you can add several categories, I cannot figure it out. You need something like $article->blog_categories()->attach($request->blog_category_id); but how to apply it correctly?

CodePudding user response:

Your naming convention is complicating your task.

Rename table in categories migration:

Schema::create('categories', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->timestamps();
});

Also for simplicity, rename the joint (pivot) table

Schema::create('article_category', function (Blueprint $table) {
    // You don't need a table id here
    $table->foreignId('category_id')->index();
    $table->foreignId('article_id')->index();
    $table->unique(['article_id', 'category_id']);
    // You also don't need timestamps
});

Defining relationships in the models:

// Article model
public function categories()
{
    return $this->belongsToMany(\App\Models\Category::class);
}

// Category model
public function articles()
{
    return $this->belongsToMany(\App\Models\Article::class);
}

Article controller

public function store() // No need to make the function static
{
    $data = validator(request()->all(), [
        // To save many categories, send them as an array   
        'categories' => 'array',
        'categories.*' => [\Illuminate\Validation\Rule::exists('categories', 'id')], // Checks if category id is in the db
        'title' => 'required|max:128',
        'slug' => 'required|unique:articles|max:64',
        'subtitle' => 'string',
    ])->validate();

    $article = Article::create(
        \Illuminate\Support\Arr::except($data, 'categories');
    );

    // Save categories
    $article->categories()->attach($data['categories']);

    return $article;
}

CodePudding user response:

You are using incorrect naming conventions; either your table name or your model name should be changed.

Your problem is the pivot table name category_article is not standard. Per documentation:

To determine the table name of the relationship's intermediate table, Eloquent will join the two related model names in alphabetical order.

So Laravel expects the two model names in alphabetical order separated by an underscore, e.g. article_blog_category. If you're not able to change the name of the table:

you are free to override this convention. You may do so by passing a second argument to the belongsToMany method:

class Article extends Model
{
    public function blog_categories()
    {
        return $this->belongsToMany('App\Models\BlogCategory', 'category_article');
    }
}

class BlogCategory extends Model {
    public function articles()
    {
        return $this->belongsToMany('App\Models\Article', 'category_article');
    }
}

Once you do this, assuming you pass an array of IDs (or an array of models) your suggested code will work.

// assume $request->blog_category_id is an array, e.g. `<select multiple>` element.
$article->blog_categories()->attach($request->blog_category_id);

I suggest renaming your model to single word names, it will make life easier and your code more intuitive. In this case, change BlogCategory model to Category and then change the table name to categories, foreign keys to category_id. Pivot table will be named article_category.

CodePudding user response:

According to Documentation Many To Many Relationships Attaching / Detaching

//You can pass an array of id's categories in attach method:
$article->blog_categories()->attach($categories_ids_array);

/* 
*if you want to pass more columns value you can pass an associative array of 
*column names with their values e.g attach($categories ids array, an array of 
*more columns with their values)
*/

$article->blog_categories()->attach($categories_ids_array, ['column_name' => 
$column_values]);
  • Related