ManagesManyToMany trait for controllers that expose belongsToMany relationships.
Overview
The trait provides:- List and show related entities with filtering, pagination, and ordering
- Create and update related models through the relationship
- Delete related models (with optional cascade)
- Attach/detach existing entities (pivot-only operations)
- Sync the entire relationship set
- Toggle specific IDs
- Update pivot fields without modifying the related model
- Export related entities to Excel/PDF
Setup
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class User extends BaseModel
{
const RELATIONS = ['addresses'];
public function addresses(): BelongsToMany
{
return $this->belongsToMany(
Address::class,
'user_addresses', // Pivot table
'user_id', // Foreign key for this model
'address_id' // Foreign key for related model
)
->withPivot(['is_primary', 'label', 'expires_at'])
->withTimestamps();
}
}
Use
->withPivot() to include custom pivot columns in responses. Without it, pivot data won’t be visible.use Ronu\RestGenericClass\Core\Traits\ManagesManyToMany;
class UserController extends RestController
{
use ManagesManyToMany;
protected array $manyToManyConfig = [
'addresses' => [
'relationship' => 'addresses', // BelongsToMany method name
'relatedModel' => Address::class,
'pivotModel' => UserAddress::class,
'parentModel' => User::class,
'parentKey' => 'user_id',
'relatedKey' => 'address_id',
'mutation' => [
'dataKey' => ['Addresses', 'addresses'],
'deleteRelated' => true,
'pivotColumns' => ['is_primary', 'label', 'expires_at'],
],
],
];
}
Route::prefix('users/{user_id}/addresses')->group(function () {
// List and show
Route::get('/', [UserController::class, 'listRelation'])
->middleware('inject:_relation,addresses');
Route::get('/{relatedId}', [UserController::class, 'showRelation'])
->middleware('inject:_relation,addresses');
// Attach operations
Route::post('/', [UserController::class, 'attachRelation'])
->middleware('inject:_relation,addresses,_scenario,attach');
Route::post('/sync', [UserController::class, 'attachRelation'])
->middleware('inject:_relation,addresses,_scenario,sync');
// Detach
Route::delete('/{relatedId}', [UserController::class, 'detachRelation'])
->middleware('inject:_relation,addresses,_scenario,detach');
// Update pivot
Route::put('/{relatedId}/pivot', [UserController::class, 'updatePivotRelation'])
->middleware('inject:_relation,addresses,_scenario,update_pivot');
});
Configuration Reference
Required Fields
| Field | Description | Example |
|---|---|---|
relationship | BelongsToMany method name on parent model | 'addresses' |
relatedModel | Fully qualified class name of related model | Address::class |
pivotModel | Fully qualified class name of pivot model | UserAddress::class |
parentModel | Fully qualified class name of parent model | User::class |
parentKey | Foreign key column for parent in pivot table | 'user_id' |
relatedKey | Foreign key column for related model in pivot | 'address_id' |
Mutation Config (Optional)
| Field | Type | Default | Description |
|---|---|---|---|
dataKey | string|array | [] | Keys to extract bulk data from request body |
deleteRelated | bool | true | Delete related model when deleteRelation is called |
pivotColumns | array | [] | Whitelist of allowed pivot columns (empty = allow all) |
Listing Related Entities
List all addresses for a user:With Filtering
With Pagination
With Ordering
Showing a Single Related Entity
Attach Operations
Attach operations link existing entities without creating new ones.Single Attach
Attach address ID 5 with pivot data:Bulk Attach
Attach multiple addresses at once:Sync Operation
Sync replaces the entire relationship set. IDs not in the sync payload are detached.Sync with ID Array
- User 42 now has exactly 3 addresses (1, 2, 3)
- Any other addresses are detached
- No pivot data is updated
Sync with Objects
Sync with Laravel Map Format
Toggle Operation
Toggle reverses the attachment status: attached IDs become detached, detached IDs become attached.- User has addresses: [1, 5, 7]
- User has addresses: [2, 3, 5, 7]
- 1 was detached (was attached)
- 2 and 3 were attached (were detached)
- 5 and 7 unchanged (not in toggle list)
Detach Operations
Single Detach
Remove the pivot row (keeps the Address model):Bulk Detach
Update Pivot Fields
Update pivot data without changing the related model.Single Pivot Update
Bulk Pivot Update
Pivot Column Whitelist
ThepivotColumns config provides a security whitelist:
How It Works
With the whitelist above: Request:approved_at and internal_notes are silently stripped (not in whitelist).
When
pivotColumns is empty or not set, all pivot columns are accepted (backward compatible).Create and Update Related Models
Create new related entities through the relationship.Create Single
Create Bulk
Update Related Model
Delete Related Models
By default, deleting a relation also deletes the related model.Single Delete
- Detaches the address from user 42
- Deletes the Address model (if
deleteRelated=true)
Pivot-Only Removal
To keep the Address model and only remove the pivot row, configure:DELETE only removes the pivot row.
Export Related Entities
Export to Excel
columns: Specify columns to exportselect,oper,orderby: Apply filters before export
Export to PDF
Real-World Examples
User Addresses (CRM)
UserController.php
Product Tags (E-commerce)
ProductController.php
Course Enrollments (LMS)
CourseController.php
Next Steps
Relation Loading
Learn eager loading for many-to-many relationships
Bulk Operations
Optimize bulk attach/detach operations
Permissions
Secure many-to-many endpoints with Spatie
API Reference
Complete many-to-many API reference
Evidence
-
File:
src/Core/Traits/ManagesManyToMany.php
Lines: 16-1191 (entire file)
Implements all many-to-many operations including listRelation, attachRelation, detachRelation, updatePivotRelation -
File:
documentacion/doc-en/03-usage/05-many-to-many.md
Lines: 1-391
Complete documentation of the trait with examples and configuration