src/Models/BaseModel.php
<?php
namespace AlwaysOpen\AuditLog\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use AlwaysOpen\AuditLog\EventType;
/**
* @property int event_type
* @property int subject_id
* @property \Illuminate\Support\Carbon occurred_at
*/
abstract class BaseModel extends Model
{
public $casts = [
self::CREATED_AT => 'datetime:Y-m-d H:i:s.u',
self::CREATED_AT => 'datetime:Y-m-d H:i:s.u',
'occurred_at' => 'datetime:Y-m-d H:i:s.u',
];
protected $dateFormat = 'Y-m-d H:i:s.u';
/**
* Record the change in the appropriate audit log table.
*
* @param int $event_type
* @param Model $model
*/
public function recordChanges(int $event_type, Model $model): void
{
$changes = self::getChangesByType($event_type, $model);
$this->saveChanges(
$this->passingChanges($changes, $model),
$event_type,
$model
);
}
/**
* @param array $changes
* @param $model
*
* @return Collection
*/
public function passingChanges(array $changes, Model $model): Collection
{
return collect($changes)
->except(config('model-auditlog.global_ignored_fields'))
->except($model->getAuditLogIgnoredFields())
->except([
$model->getKeyName(), // Ignore the current model's primary key
'created_at',
'updated_at',
'date_created',
'date_modified',
]);
}
/**
* @param Collection $passing_changes
* @param int $event_type
* @param Model $model
*/
public function saveChanges(Collection $passing_changes, int $event_type, Model $model): void
{
$passing_changes
->each(function ($change, $key) use ($event_type, $model) {
$log = new static();
$log->event_type = $event_type;
$log->occurred_at = now();
foreach ($model->getAuditLogForeignKeyColumns() as $k => $v) {
$log->setAttribute($k, $model->$v);
}
if (config('model-auditlog.enable_user_foreign_keys')) {
$log->user_id = Auth::{config('model-auditlog.auth_id_function', 'id')}();
}
$log->setAttribute('field_name', $key);
if ($event_type !== EventType::DELETED and $model->getRawOriginal($key) !== $change) {
$log->setAttribute('field_value_old', $model->getRawOriginal($key));
}
$log->setAttribute('field_value_new', $change);
$log->attributes;
$log->save();
});
}
/**
* @param int $event_type
* @param $model
* @param string $relationName
* @param array $pivotIds
*/
public function recordPivotChanges(int $event_type, Model $model, string $relationName, array $pivotIds): void
{
$pivot = $model->{$relationName}()->getPivotClass();
$changes = $this->getPivotChanges($pivot, $model, $pivotIds);
foreach ($changes as $change) {
$this->savePivotChanges(
$this->passingChanges($change, $model),
$event_type,
(new $pivot())
);
}
}
/**
* @param $pivot
* @param $model
* @param $pivotIds
*
* @return array
*/
public function getPivotChanges($pivot, Model $model, array $pivotIds): array
{
$columns = (new $pivot())->getAuditLogForeignKeyColumns();
$key = in_array($model->getForeignKey(), $columns) ? $model->getForeignKey() : $model->getKeyName();
$changes = [];
foreach ($pivotIds as $id => $pivotId) {
foreach ($columns as $auditColumn => $pivotColumn) {
if ($pivotColumn !== $key) {
$changes[$id][$auditColumn] = $pivotId;
} else {
$changes[$id][$auditColumn] = $model->getKey();
}
}
}
return $changes;
}
/**
* @param Collection $passing_changes
* @param int $event_type
* @param $pivot
*/
public function savePivotChanges(Collection $passing_changes, int $event_type, $pivot): void
{
$now = now();
$passing_changes
->each(function ($change, $key) use ($event_type, $passing_changes, $pivot, $now) {
$log = $pivot->getAuditLogModelInstance();
$log->event_type = $event_type;
$log->occurred_at = $now;
foreach ($passing_changes as $k => $v) {
$log->setAttribute($k, $v);
}
if (config('model-auditlog.enable_user_foreign_keys')) {
$log->user_id = \Auth::{config('model-auditlog.auth_id_function', 'id')}();
}
$log->setAttribute('field_name', $key);
$log->setAttribute('field_value_old', $change);
$log->setAttribute('field_value_new', null);
$log->attributes;
$log->save();
});
}
/**
* @param int $event_type
* @param Model $model
*
* @return array
*/
public static function getChangesByType(int $event_type, Model $model): array
{
switch ($event_type) {
case EventType::CREATED:
return $model->getAttributes();
break;
case EventType::RESTORED:
return $model->getChanges();
break;
case EventType::FORCE_DELETED:
return []; // if force deleted we want to stop execution here as there would be nothing to correlate records to
break;
case EventType::DELETED:
if (method_exists($model, 'getDeletedAtColumn')) {
return $model->only($model->getDeletedAtColumn());
}
return [];
break;
case EventType::UPDATED:
default:
return $model->getDirty();
break;
}
}
/**
* @return BelongsTo|null
*/
public function subject(): ?BelongsTo
{
return $this->belongsTo($this->getSubjectModelClassname(), 'subject_id');
}
/**
* @return string
*/
public function getSubjectModelClassname(): string
{
return str_replace(config('model-auditlog.model_suffix'), '', get_class($this));
}
/**
* Gets an instance of the audit log for this model.
*
* @return mixed
*/
public function getSubjectModelClassInstance()
{
$class = $this->getSubjectModelClassname();
return new $class();
}
}