Laravel中User、Document、Permission三模型关联关系定义咨询
Hey there! Let's walk through setting up these Laravel model relationships exactly how you need them. You already know User and Document are many-to-many, and we need to add granular read/write permissions per user-document pair. Here's a step-by-step breakdown that fits your example scenarios perfectly:
First, we'll build the table structure to support permissions and their associations. We'll cover two approaches—one flexible for future permission expansion, and a simpler version if you only ever need read/write.
Flexible Approach (Supports Adding Permissions Later)
Create a permissions table to store permission types:
Schema::create('permissions', function (Blueprint $table) { $table->id(); $table->string('name')->unique(); // Stores 'read' or 'write' (add more later if needed) $table->string('description')->nullable(); $table->timestamps(); });
Then create a junction table to link users, documents, and their assigned permissions:
Schema::create('user_document_permissions', function (Blueprint $table) { $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->foreignId('document_id')->constrained()->onDelete('cascade'); $table->foreignId('permission_id')->constrained()->onDelete('cascade'); $table->primary(['user_id', 'document_id', 'permission_id']); // Prevents duplicate permissions for the same user-document pair });
Simplified Approach (Only Read/Write)
If you're 100% sure you'll never need more than read and write, skip the separate permissions table and add a permission field directly to the user-document junction:
Schema::create('user_document', function (Blueprint $table) { $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->foreignId('document_id')->constrained()->onDelete('cascade'); $table->enum('permission', ['read', 'write']); $table->primary(['user_id', 'document_id']); // One permission per user-document pair });
Let's define the models for each approach. We'll start with the flexible version first.
Flexible Approach Models
Permission Model
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Permission extends Model { protected $fillable = ['name', 'description']; public function userDocumentPermissions(): HasMany { return $this->hasMany(UserDocumentPermission::class); } }
User Model
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; class User extends Model { // Many-to-many with Document, including permission data public function documents(): BelongsToMany { return $this->belongsToMany(Document::class) ->through(UserDocumentPermission::class) ->withPivot('permission_id'); } // Direct access to permission associations for easy management public function userDocumentPermissions(): HasMany { return $this->hasMany(UserDocumentPermission::class); } // Helper: Check if user has a specific permission for a document public function hasPermissionForDocument(Document $document, string $permissionName) { $permission = Permission::where('name', $permissionName)->firstOrFail(); return $this->userDocumentPermissions() ->where('document_id', $document->id) ->where('permission_id', $permission->id) ->exists(); } // Helper: Get all permissions for a document public function getPermissionsForDocument(Document $document) { return $this->userDocumentPermissions() ->where('document_id', $document->id) ->pluck('permission_id') ->map(fn($id) => Permission::find($id)->name); } }
Document Model
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; class Document extends Model { protected $fillable = ['name', 'content']; // Adjust fields to your needs public function users(): BelongsToMany { return $this->belongsToMany(User::class) ->through(UserDocumentPermission::class) ->withPivot('permission_id'); } public function userDocumentPermissions(): HasMany { return $this->hasMany(UserDocumentPermission::class); } }
Pivot Model (UserDocumentPermission)
Create this model to handle the junction table logic:
namespace App\Models; use Illuminate\Database\Eloquent\Relations\Pivot; class UserDocumentPermission extends Pivot { protected $table = 'user_document_permissions'; protected $fillable = ['user_id', 'document_id', 'permission_id']; public function permission() { return $this->belongsTo(Permission::class); } }
Simplified Approach Models
If you went with the simplified junction table, your User and Document models get simpler:
User Model
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class User extends Model { public function documents(): BelongsToMany { return $this->belongsToMany(Document::class)->withPivot('permission'); } // Helper: Check permission for a document public function hasPermissionForDocument(Document $document, string $permission) { return $this->documents() ->where('documents.id', $document->id) ->wherePivot('permission', $permission) ->exists(); } }
Document Model
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Document extends Model { protected $fillable = ['name', 'content']; public function users(): BelongsToMany { return $this->belongsToMany(User::class)->withPivot('permission'); } }
Populate the permissions table with your read and write values:
// In DatabaseSeeder or a dedicated PermissionSeeder Permission::firstOrCreate(['name' => 'read'], ['description' => 'Read access to document']); Permission::firstOrCreate(['name' => 'write'], ['description' => 'Write access to document']);
Let's implement your sample scenarios:
Flexible Approach
// Get permission instances $readPerm = Permission::where('name', 'read')->first(); $writePerm = Permission::where('name', 'write')->first(); // User1: Write to Document1, Read Document2 $user1 = User::find(1); $doc1 = Document::find(1); $doc2 = Document::find(2); $user1->userDocumentPermissions()->create([ 'document_id' => $doc1->id, 'permission_id' => $writePerm->id ]); $user1->userDocumentPermissions()->create([ 'document_id' => $doc2->id, 'permission_id' => $readPerm->id ]); // User2: Write to both documents $user2 = User::find(2); $user2->userDocumentPermissions()->create(['document_id' => $doc1->id, 'permission_id' => $writePerm->id]); $user2->userDocumentPermissions()->create(['document_id' => $doc2->id, 'permission_id' => $writePerm->id]); // User3: Read both documents $user3 = User::find(3); $user3->userDocumentPermissions()->create(['document_id' => $doc1->id, 'permission_id' => $readPerm->id]); $user3->userDocumentPermissions()->create(['document_id' => $doc2->id, 'permission_id' => $readPerm->id]); // Check permissions if ($user1->hasPermissionForDocument($doc1, 'write')) { // Allow write operation for User1 on Document1 }
Simplified Approach
// User1: Write to Document1, Read Document2 $user1 = User::find(1); $user1->documents()->attach($doc1, ['permission' => 'write']); $user1->documents()->attach($doc2, ['permission' => 'read']); // User2: Write to both documents $user2 = User::find(2); $user2->documents()->attach([$doc1->id => ['permission' => 'write'], $doc2->id => ['permission' => 'write']]); // Check permissions if ($user3->hasPermissionForDocument($doc2, 'read')) { // Allow read operation for User3 on Document2 }
内容的提问来源于stack exchange,提问作者Patrick Heppler

