Open Oui-Dev opened 8 months ago
any fix?
You can extend LogsActivity with your own custom trait that logs the many-to-many actions. This is what I'm using
<?php
namespace App\Models\Traits;
use App\Exceptions\InvalidRelation;
use Illuminate\Support\Collection;
use Spatie\Activitylog\ActivityLogStatus;
use Spatie\Activitylog\Traits\LogsActivity as TraitsLogsActivity;
trait LogsActivity
{
use TraitsLogsActivity {
eventsToBeRecorded as parentEventsToBeRecorded;
shouldLogEvent as parentShouldLogEvent;
attributeValuesToBeLogged as parentAttributeValuesToBeLogged;
}
private $logRelationChanges = [];
/**
* Log the attachment of a related model.
*
* @param string $relationName
* @param mixed $id
* @param array $attributes
* @param bool $touch
* @param array $columns
* @return void
* @throws InvalidRelation
*/
public function logAttach(string $relationName, $id, array $attributes = [], $touch = true, $columns = ['*'])
{
// Check if the relationship and attach method exist
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'attach')) {
throw new InvalidRelation('Relationship ' . $relationName . ' was not found or does not support method attach');
}
// Get the current state before attaching the new related model
$old = $this->{$relationName}()->get($columns);
// Attach the new related model
$this->{$relationName}()->attach($id, $attributes, $touch);
// Get the updated state after attaching
$new = $this->{$relationName}()->get($columns);
// Dispatch the relation change event if there are differences
if ($old->count() !== $new->count()) {
// Dispatch the relation change event
$this->dispatchRelationChanges($relationName, 'relationAttached', $old, $new);
}
}
/**
* Log the detachment of related models.
*
* @param string $relationName
* @param mixed $ids
* @param bool $touch
* @param array $columns
* @return int
* @throws InvalidRelation
*/
public function logDetach(string $relationName, $ids = null, $touch = true, $columns = ['*'])
{
// Check if the relationship and detach method exist
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'detach')) {
throw new InvalidRelation('Relationship ' . $relationName . ' was not found or does not support method detach');
}
// Get the current state before detaching the related models
$old = $this->{$relationName}()->get($columns);
// Detach the related models
$results = $this->{$relationName}()->detach($ids, $touch);
// Get the updated state after detaching
$new = $this->{$relationName}()->get($columns);
// Dispatch the relation change event if there are differences
if (!empty($results)) {
// Dispatch the relation change event
$this->dispatchRelationChanges($relationName, 'relationDetached', $old, $new);
}
return empty($results) ? 0 : $results;
}
/**
* Log the syncing of related models.
*
* @param $relationName
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
* @param bool $detaching
* @param array $columns
* @return array
* @throws InvalidRelation
*/
public function logSync($relationName, $ids, $detaching = true, $columns = ['*'])
{
// Check if the relationship and sync method exist
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'sync')) {
throw new InvalidRelation('Relationship ' . $relationName . ' was not found or does not support method sync');
}
// Get the current state before syncing the related models
$old = $this->{$relationName}()->get($columns);
// Perform the sync operation
$changes = $this->{$relationName}()->sync($ids, $detaching);
// Determine old and new states based on changes
if (collect($changes)->flatten()->isEmpty()) {
$old = $new = collect([]);
} else {
$new = $this->{$relationName}()->get($columns);
}
// Dispatch the relation change event if there are differences
if ($old->count() > 0 || $new->count() > 0) {
$this->dispatchRelationChanges($relationName, 'relationSynced', $old, $new);
}
return $changes;
}
/**
* Log the syncing of related models without detaching.
*
* @param string $relationName
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
* @param array $columns
* @return array
* @throws InvalidRelation
*/
public function logSyncWithoutDetaching(string $relationName, $ids, $columns = ['*'])
{
// Check if the relationship and syncWithoutDetaching method exist
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'syncWithoutDetaching')) {
throw new InvalidRelation('Relationship ' . $relationName . ' was not found or does not support method syncWithoutDetaching');
}
return $this->logSync($relationName, $ids, false, $columns);
}
/**
* Dispatch the changes made to a related model.
*
* @param string $relationName
* @param string $eventName
* @param Collection $old
* @param Collection $new
* @return void
*/
protected function dispatchRelationChanges($relationName, $eventName, $old, $new)
{
// Store the changes for logging purposes
$this->logRelationChanges = [
'old' => [
$relationName => $old->toArray()
],
'attributes' => [
$relationName => $new->toArray()
]
];
// Fire the model event to notify listeners about the relation change
$this->fireModelEvent($eventName);
// Clear the changes to avoid interference with subsequent events
$this->logRelationChanges = [];
}
/**
* Determine if the specified event should be logged.
*
* @param string $eventName
* @return bool
*/
protected function shouldLogEvent(string $eventName): bool
{
// Check the global activity log status and model-specific logging settings
$logStatus = app(ActivityLogStatus::class);
if (!$this->enableLoggingModelsEvents || $logStatus->disabled()) {
return false;
}
// Log only relation events, delegate other events to parent method
if (!in_array($eventName, ['relationAttached', 'relationDetached', 'relationSynced'])) {
return true;
}
return $this->parentShouldLogEvent($eventName);
}
/**
* Get the values to be logged based on the specified event.
*
* @param string $processingEvent
* @return array
*/
public function attributeValuesToBeLogged(string $processingEvent): array
{
// Return relation changes for specific events, delegate others to parent method
if (in_array($processingEvent, ['relationAttached', 'relationDetached', 'relationSynced'])) {
return $this->logRelationChanges;
}
return $this->parentAttributeValuesToBeLogged($processingEvent);
}
/**
* Get the events that should be recorded, including custom relation events.
*
* @return Collection
*/
protected static function eventsToBeRecorded(): Collection
{
// Include parent events and custom relation events
return self::parentEventsToBeRecorded()->concat(['relationAttached', 'relationDetached', 'relationSynced']);
}
/**
* Register a model event for when a related model is attached.
*
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
* @return void
*/
public static function relationAttached($callback)
{
static::registerModelEvent('relationAttached', $callback);
}
/**
* Register a model event for when a related model is detached.
*
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
* @return void
*/
public static function relationDetached($callback)
{
static::registerModelEvent('relationDetached', $callback);
}
/**
* Register a model event for when a related model is synced.
*
* @param \Illuminate\Events\QueuedClosure|\Closure|string|array $callback
* @return void
*/
public static function relationSynced($callback)
{
static::registerModelEvent('relationSynced', $callback);
}
}
I don't know if there is a way to log many to many relationship automaticaly, because it's really annoying to handle them by hand. I've searched everywhere, but I can't find any solutions. Is there a solution, how do you do it ?
Related issues/discussions/pr : https://github.com/spatie/laravel-activitylog/issues/487 https://github.com/spatie/laravel-activitylog/issues/386 https://github.com/spatie/laravel-activitylog/discussions/1269 https://github.com/spatie/laravel-activitylog/pull/1270
Versions