Automatic auditing for Eloquent relationship changes (attach, detach, sync) using Laravel Auditing.
- 🔍 Automatic Tracking: Captures before/after state of relationship changes
- 📝 Detailed Logs: Stores complete related model data, not just IDs
- 🎯 Event-Based: Uses
owen-it/laravel-auditingfor consistent audit logs - ⚡ Zero Configuration: Works out of the box after trait inclusion
- 🔧 Flexible: Supports BelongsToMany and MorphToMany relationships
- 📦 Lightweight: Minimal overhead, maximum value
composer require a2zwebltd/auditable-relations- PHP 8.1+
- Laravel 10/11/12
- owen-it/laravel-auditing 13/14
use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Auditable as AuditableTrait;
use OwenIt\Auditing\Contracts\Auditable;
class Post extends Model implements Auditable
{
use AuditableTrait;
}use A2ZWeb\AuditableRelations\Traits\AuditsRelationships;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Post extends Model implements Auditable
{
use AuditableTrait;
use AuditsRelationships;
public function tags(): BelongsToMany
{
return $this->auditableRelation(
$this->belongsToMany(Tag::class)
);
}
}That's it! Now all changes to the tags relationship will be automatically audited.
$post = Post::find(1);
// These operations are automatically audited
$post->tags()->attach([1, 2, 3]);
$post->tags()->detach([2]);
$post->tags()->sync([1, 3, 4]);Each operation creates an audit log entry like:
[
'event' => 'synced', // or 'attached', 'detached'
'auditable_type' => 'App\Models\Post',
'auditable_id' => 1,
'old_values' => [
'tags' => [
['id' => 1, 'name' => 'Laravel', 'created_at' => '...'],
['id' => 2, 'name' => 'PHP', 'created_at' => '...'],
]
],
'new_values' => [
'tags' => [
['id' => 1, 'name' => 'Laravel', 'created_at' => '...'],
['id' => 3, 'name' => 'Vue', 'created_at' => '...'],
['id' => 4, 'name' => 'Tailwind', 'created_at' => '...'],
]
],
'user_id' => 123,
'user_type' => 'App\Models\User',
]You can audit multiple relationships on the same model:
class Post extends Model implements Auditable
{
use AuditableTrait;
use AuditsRelationships;
public function tags(): BelongsToMany
{
return $this->auditableRelation(
$this->belongsToMany(Tag::class)
);
}
public function categories(): BelongsToMany
{
return $this->auditableRelation(
$this->belongsToMany(Category::class)
);
}
public function attachments(): MorphToMany
{
return $this->auditableRelation(
$this->morphToMany(File::class, 'attachable')
);
}
}Works seamlessly with polymorphic relationships:
class Post extends Model implements Auditable
{
use AuditableTrait;
use AuditsRelationships;
public function images(): MorphToMany
{
return $this->auditableRelation(
$this->morphToMany(Image::class, 'imageable')
);
}
}- ✅
BelongsToMany - ✅
MorphToMany - ⏳ Other relationship types (planned)
The package respects Laravel Auditing's global configuration:
// config/audit.php
return [
'enabled' => true, // Disable to stop all auditing
'console' => false, // Audit console commands
// ... other audit config
];You can control auditing at runtime:
// Temporarily disable auditing
config(['audit.enabled' => false]);
$post->tags()->sync([1, 2, 3]); // Not audited
config(['audit.enabled' => true]);The package uses standard event names:
attached- When models are attacheddetached- When models are detachedsynced- When models are synced
use OwenIt\Auditing\Models\Audit;
// Get all audits for a model
$audits = Audit::where('auditable_type', Post::class)
->where('auditable_id', 1)
->get();
// Get relationship change audits
$relationshipAudits = Audit::where('auditable_type', Post::class)
->whereIn('event', ['attached', 'detached', 'synced'])
->get();- Trait Application:
AuditsRelationshipstrait wraps relationship definitions - Relationship Proxy: Creates auditable versions of
BelongsToManyandMorphToMany - Operation Interception: Intercepts
attach(),detach(), andsync()calls - State Capture: Records relationship state before and after the operation
- Event Dispatch: Fires
AuditCustomevent with the captured data - Audit Creation: Laravel Auditing processes the event and creates the audit log
- Minimal overhead: Only one additional query per operation (to capture current state)
- Efficient: Uses existing Laravel Auditing infrastructure
- Asynchronous-ready: Compatible with queued audit processing
Unlike other solutions:
- ✅ Complete Data: Stores full related model data, not just IDs
- ✅ Native Integration: Uses Laravel Auditing's standard audit model
- ✅ Zero Config: No additional tables or setup required
- ✅ Framework-Native: Uses Laravel's event system
- Verify auditing is enabled:
config('audit.enabled'); // Should be true- Check model implements
Auditable:
class Post extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
use \OwenIt\Auditing\Auditable;
}- Ensure relationship is wrapped:
public function tags(): BelongsToMany
{
return $this->auditableRelation( // Don't forget this!
$this->belongsToMany(Tag::class)
);
}Enable console auditing in config/audit.php:
'console' => true,composer testContributions are welcome! Please see CONTRIBUTING.md for details.
If you discover a security vulnerability, please email contact@a2zweb.co.
The MIT License (MIT). Please see License File for more information.