lukeboy_2002's avatar

Posts, categories belongsToMany

Hi,

I want to assign multiple categories to a post. But I can get It to work. The post is added in the database including the user. But I get no results in my category_posts table.

Here are my migrations:

Post table

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('title');
            $table->string('slug')->unique();
            $table->string('image', 2048)->nullable();
            $table->longText('body');
            $table->boolean('active')->nullable()->default(false);
            $table->datetime('published_at')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Category table

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('title')->unique();
            $table->string('slug')->unique();
            $table->boolean('active')->nullable()->default(true);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('categories');
    }
};

Categorie_posts table

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('category_posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('category_id')->references('id')->on('categories')->onDelete('cascade');
            $table->foreignId('post_id')->references('id')->on('posts')->onDelete('cascade');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('category_posts');
    }
};

I made 3 models, Post, Category and CategoryPost.

In my Model Post I have:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'user_id',
        'title',
        'image',
        'body',
        'active',
        'published_at',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function categories(): BelongsToMany
    {
        return $this->belongsToMany(Category::class);
    }

in my Model Category I have.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Category extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'active',
    ];

    public function posts(): BelongsToMany
    {
        return $this->belongsToMany(Post::class);
    }

This Is my Create Post Blade file

<x-admin-layout>
    @push('styles')
        <link href="https://unpkg.com/filepond@^4/dist/filepond.css" rel="stylesheet" />
        <link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet" />
    @endpush

    <x-slot name="header">
        Posts
    </x-slot>

    <x-card.default>
        <form action="{{ route('admin.posts.store') }}" method="POST" enctype="multipart/form-data" class="space-y-6">
            @csrf
            <div class="flex justify-between gap-6">
                <div class="w-1/2">
                    <x-form.label for="title" value="Title" />
                    <x-form.input type="text" name="title" id="title" :value="old('title')" required autofocus />
                    <x-form.input-error for="name" class="mt-2" />
                </div>
                <div class="w-1/2">
                    <x-form.label for="categories" value="Categories" />
                    <x-form.select id="categories" name="categories[]" multiple="multiple">
                        @foreach($categories as $category)
                            <option value="{{ $category->id }}">{{ $category->title }}</option>
                        @endforeach
                    </x-form.select>
                    <x-form.input-error for="post.categories" class="mt-2" />
                </div>
            </div>
            <div>
                <x-form.label for="image" value="Image" />
                <x-form.input type="file" name="image" id="image" required  />
                <x-form.input-error for="image" class="mt-2" />
            </div>
            <div>
                <x-form.label for="body" value="Text" />
                <x-form.textarea id="body" name="body"/>
                <x-form.input-error for="body" class="mt-2" />
            </div>
            <div class="flex justify-between gap-6">
                <div class="w-1/2">
                    <div class="relative max-w-sm">
                        <div class="absolute inset-y-0 left-0 flex items-center pl-3.5 pointer-events-none">
                            <svg class="w-4 h-4 text-gray-700 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
                                <path d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"/>
                            </svg>
                        </div>
                        <input datepicker datepicker-format="yyyy-mm-dd" type="text" id="published_at" name="published_at" class="bg-gray-50 border border-gray-300 text-gray-700 text-sm rounded-lg focus:ring-orange-500 focus:border-orange-500 block w-full pl-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-white dark:text-white dark:focus:ring-orange-500 dark:focus:border-orange-500" placeholder="Select date">
                    </div>
                </div>
                <div class="w-1/2">
                    <label class="relative inline-flex items-center mr-5 cursor-pointer">
                        <input name="active"
                               id="active"
                               value="1"
                               aria-describedby="active"
                               type="checkbox"
                               class="sr-only peer"
                               checked
                        >
                        <div class="w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-focus:ring-4 peer-focus:ring-orange-300 dark:peer-focus:ring-orange-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-orange-500"></div>
                        <span class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">Active</span>
                    </label>
                </div>
            </div>
            <div class="flex justify-end space-x-2">
                <x-button.secondary type="button" onclick="history.back()" class="px-3 py-2 text-xs font-medium">Cancel</x-button.secondary>
                <x-button.primary class="px-3 py-2 text-xs font-medium">Save</x-button.primary>
            </div>
        </form>
    </x-card.default>

    @push('scripts')
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/1.7.0/datepicker.min.js"></script>
        <script src="https://unpkg.com/filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.js"></script>
        <script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"></script>
        <script src="https://unpkg.com/filepond-plugin-file-validate-size/dist/filepond-plugin-file-validate-size.js"></script>
        <script src="https://unpkg.com/filepond@^4/dist/filepond.js"></script>

        <script>
            FilePond.registerPlugin(FilePondPluginImagePreview);
            FilePond.registerPlugin(FilePondPluginFileValidateType);
            FilePond.registerPlugin(FilePondPluginFileValidateSize);


            // Get a reference to the file input element
            const inputElement = document.querySelector('#image');

            // Create a FilePond instance
            const pond = FilePond.create(inputElement, {
                acceptedFileTypes: ['image/*'],
                server: {
                    process: '{{ route('admin.filepond.upload') }}',
                    revert: '{{ route('admin.filepond.revert') }}',
                    headers: {
                        'X-CSRF-TOKEN': '{{ csrf_token() }}'
                    }
                }
            });
        </script>
    @endpush
</x-admin-layout>

Im my Post Controller:

  public function create(): View
    {
        $categories = Category::all();

        return view('admin.posts.create', [
            'categories'=>$categories
        ]);
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'title' => ['required', 'string', 'max:255', 'unique:posts'],
            'image' => ['required'],
            'body' => 'nullable|min:10',
            'active' => 'nullable',
            'published_at' => 'nullable',
        ]);

        $newFilename = Str::after($request->input('image'), 'tmp/');
        Storage::disk('public')->move($request->input('image'), "posts/$newFilename");

        $slug = SlugService::createSlug(Post::class, 'slug', $request->title);

        $post = Post::create([
            'user_id' => current_user()->id,
            'title' => $request['title'],
            'slug' => $slug,
            'image' => "posts/$newFilename",
            'body' => $request['body'],
            'active' => $request['active'],
            'published_at' => $request['published_at'],
        ]);


			 $post->categories()->attach($request->categories_id);

        $request->session()->flash('success', 'Post successfully created.');
        return redirect()->route('admin.posts.index');
    }

I get the error: SQLSTATE[42S02]: Base table or view not found: 1146 Table '******.category_post' doesn't exist Thats correct I need to have the table category_posts

How to store the multiple categories in de CategoryPost table:

0 likes
4 replies
LaryAI's avatar
Level 58

To store the multiple categories in the category_posts table, you need to make a few changes in your code.

  1. First, make sure you have the categories_id field in your form. Update your create post blade file as follows:
<x-form.select id="categories" name="categories_id[]" multiple="multiple">
    @foreach($categories as $category)
        <option value="{{ $category->id }}">{{ $category->title }}</option>
    @endforeach
</x-form.select>
  1. In your Post model, update the categories method to include the withTimestamps() method:
public function categories(): BelongsToMany
{
    return $this->belongsToMany(Category::class)->withTimestamps();
}
  1. In your PostController's store method, change $request->categories_id to $request->categories:
$post->categories()->attach($request->categories);
  1. Finally, run the migration to create the category_post table:
php artisan migrate

With these changes, the multiple categories selected in the form will be stored in the category_posts table.

Snapey's avatar
Snapey
Best Answer
Level 122

if you don't want to use the naming convention then you have to overwrite the assumed table name in the relationship.

Conventionally, your pivot should be category_post

Then, you are getting an array of ids from the form in $request->categories so use;

$post->categories()->sync($request->categories);

Please or to participate in this conversation.