earmsby's avatar

Audit trail for attaching relation

I am using owen-it/laravel-auditing package to log audits on my Laravel/Filament app. It's working great for create, update, and delete operations on the Contract model.

I'd like to add the ability to log attach and detach events for a relationship (files) on the model I have the audit trail set up for. Searching online I found a suggestion to add a relationship listener event to add a custom audit record. Thus, I added this code to my model (Contract):

However, when I try to load the contracts resource in the browser, I get this error:

Call to undefined method App\Models\Contract::pivotAttached()

Do I need to reference something or install another package in order to access "static::pivotAttached"? The resource where I found this method did not mention needing to install an additional package or reference anything other than:

use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;
use OwenIt\Auditing\Models\Audit;

I do have these three in my Model.

Hoping it isn't devilishly difficult to log attach/detach events to the audit trail for this model.

0 likes
3 replies
LaryAI's avatar
Level 58

You're right to look for a way to log attach/detach events for your many-to-many relationships, but the issue is that Eloquent models do not have pivotAttached or pivotDetached methods. These are not native Eloquent features, nor are they provided by popular packages like owen-it/laravel-auditing.

Why the Error?

Call to undefined method App\Models\Contract::pivotAttached()

Eloquent models can't "listen" for relationship attaches/detaches, because attaching and detaching is done via the relationship object, not the model itself (e.g., $contract->files()->attach(...)), and no events are fired on those actions by default.


How To Audit Many-to-Many Relation Events

Solution 1: Wrap Attach/Detach in Your Own Method

The most reliable and common approach is to never call attach/detach directly in your controllers/services. Always use your own methods so you can place the audit code there.

Example:

Note: Don't call ->files()->attach() directly. Use $contract->attachFile($id) instead.


Solution 2: Use Eloquent Events on Pivot Model (if you have one)

If your pivot table has its own Model (a custom pivot model), you can use its model events.

Example:

class ContractFile extends Pivot // extends Illuminate\Database\Eloquent\Relations\Pivot
{
    protected static function booted()
    {
        static::created(function ($pivot) {
            // Log attach here
        });

        static::deleted(function ($pivot) {
            // Log detach here
        });
    }
}

// In your Contract model:
public function files()
{
    return $this->belongsToMany(File::class)
        ->using(ContractFile::class);
}

Solution 3: Override the Relationship

If you always want this to happen, you can create a custom relation class and override the attach/detach methods. This is an advanced solution and rarely needed for most projects.


References


Summary

  • static::pivotAttached does not exist—that's why you get the error.
  • Wrap attach/detach in your own methods so you can log the audit.
  • Optionally, use custom Pivot models if your pivot table has extra columns or you need events.

This is a common request, but there's no native Eloquent event for attach or detach.

Let me know if you'd like example code for a service-based approach or customizing the pivot model further!

earmsby's avatar

Since the attaching/detaching is happening via Filament 4 relation manager, would it be possible to add an action to AttachAction in the Filament RelationManager?

earmsby's avatar

In case others are trying to solve this, here's how I solved it. I added a closure to the "after" property of "AttachAction" on the relation manager like so:

AttachAction::make()
                    ->label('Link File')
                    ->preloadRecordSelect()
                    ->after(function (array $data, ?Model $record): void {
                        $audit_log_entry = Audit::create([
                            'user_type' => 'App\Models\User',
                            'user_id' => auth()->id(),
                            'event' => 'linked file',
                            'auditable_id' => $this->ownerRecord->id,
                            'auditable_type' => 'App\Models\Contract',
                            'old_values' => [],
                            'new_values' => ['file_id' => $record->id],
                            'url' => request()->fullUrl(),
                            'ip_address' => request()->ip(),
                            'user_agent' => request()->userAgent(),
                        ]);
                    })

Now I can see any new linking for this relationship. I imagine I can do the same for detaching, although I haven't yet implemented that.

Please or to participate in this conversation.