Installation:
composer require cybercog/laravel-ownership
Publish the config (optional):
php artisan vendor:publish --provider="Cog\Laravel\Ownership\OwnershipServiceProvider"
Configure Auth Model (in config/ownership.php):
'auth_model' => App\Models\User::class,
Apply to a Model (strict ownership):
use Cog\Laravel\Ownership\Traits\HasOwner;
class Post extends Model
{
use HasOwner;
// Optional: Override default foreign key
// public $ownedBy = 'author_id';
}
First Use Case:
// Assign ownership
$post = new Post(['title' => 'Hello']);
$post->changeOwnerTo(auth()->user());
// Query owned posts
$posts = Post::ownedBy(auth()->user());
Strict Ownership (Single Type):
HasOwner for models with one owner type (e.g., User).Post owned by a User.class Post extends Model
{
use HasOwner;
public function owner()
{
return $this->belongsTo(User::class, 'owned_by_id');
}
}
Polymorphic Ownership (Multiple Types):
HasMorphOwner for models with multiple owner types (e.g., User or Team).Comment owned by either a User or Team.class Comment extends Model
{
use HasMorphOwner;
public function owner()
{
return $this->morphTo();
}
}
Auto-Assignment on Create:
config/ownership.php:
'auto_assign_on_create' => true,
resolveDefaultOwner() to customize logic:
protected function resolveDefaultOwner()
{
return auth()->guard('admin')->user() ?? parent::resolveDefaultOwner();
}
Query Scopes:
$posts = Post::ownedBy($user);
$posts = Post::withoutOwner();
$posts = Post::withDefaultOwner();
API Methods:
$post->changeOwnerTo($newOwner);
$post->abandonOwner();
$post->hasOwner(); // bool
$post->isOwnedBy($user); // bool
Morph Map: Register custom morph maps if using polymorphic ownership:
class Comment extends Model
{
protected $morphClass = 'commentable';
public static function boot()
{
parent::boot();
self::morphMap([
'user' => User::class,
'team' => Team::class,
]);
}
}
Observers: Extend OwnableObserver for custom logic:
class PostObserver extends OwnableObserver
{
public function saving(Post $post)
{
if ($post->isDirty('title') && $post->hasOwner()) {
// Custom logic
}
}
}
Policies: Use Laravel's policies to restrict ownership changes:
class PostPolicy
{
public function update(User $user, Post $post)
{
return $user->can('edit', $post->owner);
}
}
Strict Ownership Validation:
HasOwner throws InvalidOwnerType if the owner type doesn’t match the configured model. Ensure your owner() relationship returns the correct model class.belongsTo relationship in your model matches the config:
public function owner()
{
return $this->belongsTo(User::class); // Must match config
}
Polymorphic Ownership Quirks:
HasMorphOwner doesn’t validate owner types by default. Ensure your CanBeOwner implementations are correct.instanceof checks in custom logic:
if ($comment->owner instanceof User) {
// Handle user-owned comments
}
Auto-Assignment Conflicts:
auto_assign_on_create is true, ensure resolveDefaultOwner() returns a valid owner. Null owners can cause issues.NullOwnerException in logs.Foreign Key Conflicts:
author_id) must be defined in both the trait and the model’s owner() relationship.$ownedBy in your model:
use HasOwner;
class Post extends Model
{
use HasOwner;
protected $ownedBy = 'author_id';
public function owner()
{
return $this->belongsTo(User::class, 'author_id');
}
}
Observer Overrides:
OwnableObserver, ensure you call parent::method() to preserve default behavior unless intentionally overriding.Check Config:
config/ownership.php settings, especially auth_model and auto_assign_on_create.Log Ownership Changes:
resolveDefaultOwner() or observer methods:
Log::debug('Default owner resolved:', ['owner' => $this->owner]);
Test Edge Cases:
HasOwner).Database Migrations:
$table->foreignId('owned_by_id')->constrained()->onDelete('cascade');
Custom Ownership Logic:
changeOwnerTo() or abandonOwner():
public function changeOwnerTo($owner)
{
$this->fireModelEvent('changing_owner', false);
parent::changeOwnerTo($owner);
$this->fireModelEvent('changed_owner', false);
}
Dynamic Owner Resolution:
resolveDefaultOwner() for context-aware assignment:
protected function resolveDefaultOwner()
{
return request()->has('team_id')
? Team::find(request('team_id'))
: auth()->user();
}
Custom Contracts:
Cog\Contracts\Ownership\CanBeOwner for non-standard owner models:
class Team implements CanBeOwner
{
// Implement required methods
}
Query Extensions:
public function scopeOwnedByTeam($query)
{
return $query->whereHas('owner', fn($q) => $q->where('owner_type', Team::class));
}
How can I help you explore Laravel packages today?