Many-to-Many Portfolio Categories
March 29, 2025 · AI Conversations
—
### 🧱 Step 1: Create the Pivot Table
Run a migration to create a pivot table, usually named `portfolio_portfolio_category`.
php artisan make:migration create_portfolio_portfolio_category_table
In the migration file:
Schema::create('portfolio_portfolio_category', function (Blueprint $table) {
$table->id();
$table->foreignId('portfolio_id')->constrained()->onDelete('cascade');
$table->foreignId('portfolio_category_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
Then run:
php artisan migrate
—
### 🧬 Step 2: Define Relationships in Models
#### In `Portfolio.php`
public function categories()
{
return $this->belongsToMany(PortfolioCategory::class, 'portfolio_portfolio_category');
}
#### In `PortfolioCategory.php`
public function portfolios()
{
return $this->belongsToMany(Portfolio::class, 'portfolio_portfolio_category');
}
—
### 📝 Step 3: Sync or Attach Categories
When saving or updating a portfolio, you can sync categories like this:
$portfolio = Portfolio::find($id);
$portfolio->categories()->sync([1, 2, 3]); // array of category IDs
Or attach:
$portfolio->categories()->attach($categoryId);
Or detach:
$portfolio->categories()->detach($categoryId);
—
If you're using a form with checkboxes for categories, just collect the selected category IDs and sync them.
Let me know if you're using Nova, Livewire, Inertia, or plain Blade – I can help tailor the form example for that.
I'll show you the setup for:
– A form with checkboxes for categories
– The controller logic to handle form submission
Let’s go 👇
—
### ✅ 1. **Form Input (Blade Example)**
<form method="POST" action="{{ route('portfolios.store') }}">
@csrf
<!-- Portfolio fields -->
<input type="text" name="title" placeholder="Portfolio Title" required>
<!-- Portfolio Categories -->
<label>Select Categories:</label><br>
@foreach ($categories as $category)
<label>
<input type="checkbox" name="categories[]" value="{{ $category->id }}">
{{ $category->name }}
</label><br>
@endforeach
<button type="submit">Save Portfolio</button>
</form>
Make sure you pass `$categories` to the view from the controller:
$categories = PortfolioCategory::all();
return view('portfolios.create', compact('categories'));
—
### 🧠 2. **Controller Logic**
In your `PortfolioController`:
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'categories' => 'array',
'categories.*' => 'exists:portfolio_categories,id',
]);
// Create portfolio
$portfolio = Portfolio::create([
'title' => $request->title,
// add other fields here
]);
// Attach categories
$portfolio->categories()->sync($request->categories);
return redirect()->route('portfolios.index')->with('success', 'Portfolio created successfully!');
}
—
Let me know if you're using **Inertia.js**, **Vue**, **Livewire**, or **Nova**, and I can adjust the form/logic accordingly.
—
### ✅ 1. **Update HTML Form**
Make sure your form has `enctype="multipart/form-data"` to allow file upload:
<form method="POST" action="{{ route('portfolios.store') }}" enctype="multipart/form-data">
@csrf
<input type="text" name="title" placeholder="Portfolio Title" required>
<!-- Image Upload -->
<label>Upload Image:</label>
<input type="file" name="image" required>
<!-- Categories -->
<label>Select Categories:</label><br>
@foreach ($categories as $category)
<label>
<input type="checkbox" name="categories[]" value="{{ $category->id }}">
{{ $category->name }}
</label><br>
@endforeach
<button type="submit">Save Portfolio</button>
</form>
—
### 🧠 2. **Controller Logic to Handle Image + Categories**
In your `PortfolioController`:
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'image' => 'required|image|mimes:jpeg,png,jpg,webp|max:2048',
'categories' => 'array',
'categories.*' => 'exists:portfolio_categories,id',
]);
// Store image
$imagePath = $request->file('image')->store('portfolios', 'public');
// Create portfolio with image path
$portfolio = Portfolio::create([
'title' => $request->title,
'image' => $imagePath,
]);
// Attach categories
$portfolio->categories()->sync($request->categories);
return redirect()->route('portfolios.index')->with('success', 'Portfolio created!');
}
—
### 🧾 3. **Migration for Portfolios Table (if needed)**
Make sure your `portfolios` table has an `image` column:
$table->string('image')->nullable();
—
### 🎨 4. **Displaying the Image (Optional)**
<img src="{{ asset('storage/' . $portfolio->image) }}" alt="{{ $portfolio->title }}" width="200">
—
Let me know if you'd prefer the image name to be stored custom (with timestamp, slug, etc.), or if you’re using Vue/Inertia instead of Blade for the form.