|null $old_values * @property array|null $new_values * @property string|null $url * @property string|null $ip_address * @property string|null $user_agent * @property string|null $tags * @property string|null $owner_user_id * @property string|null $owner_organization_id * @property Carbon|null $created_at * @property Carbon|null $updated_at * @property-read User|null $ownerUser * @property-read Organization|null $ownerOrganization * * @method static AuditFactory factory() * @method static Builder whereMissingOwner() */ class Audit extends PackageAuditModel { /** @use HasFactory */ use HasFactory; /** * @return BelongsTo */ public function ownerUser(): BelongsTo { return $this->belongsTo(User::class, 'owner_user_id'); } /** * @return BelongsTo */ public function ownerOrganization(): BelongsTo { return $this->belongsTo(Organization::class, 'owner_organization_id'); } /** * Whether audits of the given auditable type intentionally have no owner (see AuditableWithoutOwner). */ public static function isAuditableTypeWithoutOwner(string $auditableType): bool { $modelClass = Relation::getMorphedModel($auditableType) ?? $auditableType; return is_subclass_of($modelClass, AuditableWithoutOwner::class); } /** * Auditable types (morph aliases) whose audits intentionally have no owner (see AuditableWithoutOwner). * * @return array */ public static function getAuditableTypesWithoutOwner(): array { return collect(Relation::morphMap()) ->filter(fn (string $modelClass): bool => is_subclass_of($modelClass, AuditableWithoutOwner::class)) ->keys() ->values() ->all(); } /** * Audits that have neither an owner organization nor an owner user, although their auditable type should have one. * These are audits whose owner no longer exists or could not be determined (yet). * * @param Builder $builder */ public function scopeWhereMissingOwner(Builder $builder): void { $builder->whereNull('owner_organization_id') ->whereNull('owner_user_id') ->whereNotIn('auditable_type', self::getAuditableTypesWithoutOwner()); } }