chelout/laravel-relationship-events
Adds missing Eloquent relationship events to Laravel models. Use simple traits (HasOne/Many, BelongsTo/Many, Morph*) to listen for attach/detach/sync, saved/updated, and other relation lifecycle hooks with parent/related context and IDs/attributes.
Installation:
composer require chelout/laravel-relationship-events
Ensure compatibility with your Laravel version (check README).
First Use Case:
Add the appropriate trait to your model (e.g., HasOneEvents for one-to-one relationships) and register events in the boot() method:
use Chelout\RelationshipEvents\Concerns\HasOneEvents;
class User extends Model
{
use HasOneEvents;
public static function boot()
{
parent::boot();
static::hasOneSaved(function ($parent, $related) {
Log::info("User's profile saved: {$related->id}");
});
}
public function profile()
{
return $this->hasOne(Profile::class);
}
}
Trigger Events: Save a related model to test:
$user = User::first();
$user->profile()->save(new Profile(['bio' => 'Test']));
Check logs for the event output.
Event Registration:
HasOneEvents, HasManyEvents, etc.) in models.boot() with closures or dispatchable classes.HasMany:
static::hasManySaving(function ($parent, $related) {
// Pre-save logic
});
Observer Integration:
HasRelationshipObservables trait and define observer methods:
class UserObserver
{
public function hasManyCreating(User $user, Model $related)
{
Log::info("Creating related model for user {$user->name}");
}
}
AppServiceProvider:
User::observe(UserObserver::class);
Dispatchable Events:
HasDispatchableEvents trait to fire event classes:
use Chelout\RelationshipEvents\Traits\HasDispatchableEvents;
class User extends Model
{
use HasDispatchableEvents, HasOneEvents;
protected $dispatchesEvents = [
'hasOneSaved' => HasOneSaved::class,
];
}
Polymorphic Relationships:
morphToMany/morphedByMany, listen to events like:
static::morphToManyAttached(function ($relation, $parent, $ids) {
Log::info("Attached IDs: " . implode(',', $ids));
});
$this->expectsEvents(HasOneSaved::class);
$user->profile()->save(new Profile());
Dirty Models:
belongsToAssociated pass a "dirty" related model (unsaved). Save it explicitly if needed:
static::belongsToAssociated(function ($relation, $related, $parent) {
$related->save(); // Ensure model is persisted
});
Additional Queries:
belongsToDissociated) trigger extra queries to fetch parent models. Optimize with eager loading if performance is critical.Event Order:
hasManyCreating → hasManyCreated). Avoid side effects that assume prior events have completed.Observer vs. Closures:
Event Not Firing:
boot() is called (e.g., via parent::boot()).hasOneSaved vs. hasOneSave).Missing Data:
static::hasManySaved(function ($parent, $related) {
dd($parent->toArray(), $related->toArray());
});
Custom Events:
class CustomRelationshipEvent extends Event
{
public $parent;
public $related;
}
static::hasOneSaved(function ($parent, $related) {
event(new CustomRelationshipEvent($parent, $related));
});
Dynamic Events:
$relationName = 'dynamic_relation';
static::{$relationName.'Saved'}(function ($parent, $related) {
// Handle dynamic relation
});
Performance:
static::hasManySaved(function ($parent, $related) {
if ($parent->fresh()) {
// Skip if already processed
return;
}
// Process
});
v5.x for Laravel 13). Mixing versions may cause issues.HasEvents). Prefix custom methods if needed.How can I help you explore Laravel packages today?