Skip to content

Commit

Permalink
Merge pull request #57 from sourcetoad/track-deleted-records
Browse files Browse the repository at this point in the history
Allow Deleted Owners and Entities to be Resolved and Fix `LazyLoadingViolation` When Loading Owners
  • Loading branch information
rbondoc96 authored Nov 26, 2024
2 parents 2bfa878 + 1d684aa commit 89fe4f8
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 34 deletions.
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Recommended action is creating an enum class to describe all models in your syst

This enforces the user to create shorthand notation for all models to cut down on database size. If a numeric morph is not found, the system will fail out. Due to issues with blindly overwriting and applying these morphs globally, they are manually applied. This means that morphs in your application are left untouched.

#### Trackable Trait
#### Trackable Trait and Contract
For models that may contain information that you wish to be notified was accessed or mutated. You may add the `Trackable` trait and contract to these models:

```php
Expand All @@ -85,7 +85,13 @@ use Sourcetoad\Logger\Traits\Trackable;
class TrackedModel extends Model implements TrackableContract {
use Trackable;

// Tracked models must implement this function
// Tracked models must implement these functions

public function getOwnerRelationshipName(): ?string
{
//
}

public function trackableOwnerResolver(): ?Model
{
//
Expand Down Expand Up @@ -130,7 +136,7 @@ class TrackedModel extends Model implements TrackableContract {
}

/**
* Collection of accesses of this model
* Collection of access logs of this model
*
* @return LoggerMorphMany<AuditModel>
*/
Expand All @@ -155,10 +161,31 @@ Schedule::command('logger:audit-resolver')
This will run taking 200 items of both changes and retrieved models. It will identify the owner associated with the model through the `Trackable` contract. The functions for each individual model should be easy.

```php
public function getOwnerRelationshipName(): string
{
return 'object.relation.owner';
}

public function trackableOwnerResolver(): Owner
{
return $this->object->relation->owner;
}
```

As you can see, we have to traverse whatever relation/property we need in order to relate the model at hand to an owner. If there is no match, you probably shouldn't be logging it.
As you can see, we have to traverse whatever relation/property we need in order to relate the model at hand to an owner.

However, there may be cases where a tracked model is also the owner of that model. In those cases, there is no relation/property to traverse.

```php
public function getOwnerRelationshipName(): null
{
return null;
}

public function trackableOwnerResolver(): TrackedModel
{
return $this;
}
```

If neither of these scenarios match, you probably shouldn't be logging it.
68 changes: 45 additions & 23 deletions src/Commands/AuditModelResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
namespace Sourcetoad\Logger\Commands;

use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Sourcetoad\Logger\Logger;
use Sourcetoad\Logger\Helpers\AuditResolver;
use Sourcetoad\Logger\LoggerServiceProvider;
use Sourcetoad\Logger\Models\AuditChange;
use Sourcetoad\Logger\Models\AuditModel;

Expand All @@ -15,28 +17,48 @@ class AuditModelResolver extends Command

public function handle(): void
{
AuditChange::query()->where('processed', false)->with('entity')->chunkById(200, function ($items) {
/** @var AuditChange[] $items */
foreach ($items as $item) {
$owner = AuditResolver::findOwner($item->entity);

$item->processed = true;
$item->owner_id = $owner?->getKey();
$item->owner_type = !is_null($owner) ? Logger::getNumericMorphMap($owner) : null;
$item->saveOrFail();
}
});

AuditModel::query()->where('processed', false)->with('entity')->chunkById(200, function ($items) {
/** @var AuditModel[] $items */
foreach ($items as $item) {
$owner = AuditResolver::findOwner($item->entity);

$item->processed = true;
$item->owner_id = $owner?->getKey();
$item->owner_type = !is_null($owner) ? Logger::getNumericMorphMap($owner) : null;
$item->saveOrFail();
}
});
AuditChange::query()
->where('processed', false)
->with(['entity' => function (MorphTo $morphTo) {
$morphTo->morphWith($this->getOwnerMorphs());
}])
->chunkById(200, function ($items) {
/** @var AuditChange[] $items */
foreach ($items as $item) {
$owner = AuditResolver::findOwner($item->entity);

$item->processed = true;
$item->owner_id = $owner?->getKey();
$item->owner_type = !is_null($owner) ? Logger::getNumericMorphMap($owner) : null;
$item->saveOrFail();
}
});

AuditModel::query()
->where('processed', false)
->with(['entity' => function (MorphTo $morphTo) {
$morphTo->morphWith($this->getOwnerMorphs());
}])
->chunkById(200, function ($items) {
/** @var AuditModel[] $items */
foreach ($items as $item) {
$owner = AuditResolver::findOwner($item->entity);

$item->processed = true;
$item->owner_id = $owner?->getKey();
$item->owner_type = !is_null($owner) ? Logger::getNumericMorphMap($owner) : null;
$item->saveOrFail();
}
});
}

/**
* @return array<class-string, string[]>
*/
private function getOwnerMorphs(): array
{
return collect(LoggerServiceProvider::$morphs)->mapWithKeys(fn(string $class) => [
$class => array_filter([(new $class)->getOwnerRelationshipName()]),
])->toArray();
}
}
1 change: 1 addition & 0 deletions src/Contracts/Trackable.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@

interface Trackable
{
public function getOwnerRelationshipName(): ?string;
public function trackableOwnerResolver(): ?Model;
}
6 changes: 3 additions & 3 deletions src/Helpers/AuditResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

class AuditResolver
{
public static function findOwner(?Trackable $model): ?Model
public static function findOwner(?Trackable $trackable): ?Model
{
if (empty($model)) {
if (empty($trackable)) {
return null;
}

$owner = $model->trackableOwnerResolver();
$owner = $trackable->trackableOwnerResolver();

if (empty($owner)) {
return null;
Expand Down
4 changes: 2 additions & 2 deletions src/Models/AuditChange.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class AuditChange extends BaseModel
/** @return MorphTo<Model, self> */
public function owner(): MorphTo
{
return $this->morphTo();
return $this->morphTo()->withTrashed();
}

public function key(): BelongsTo
Expand All @@ -64,6 +64,6 @@ public function key(): BelongsTo
/** @return MorphTo<Trackable, self> */
public function entity(): MorphTo
{
return $this->morphTo();
return $this->morphTo()->withTrashed();
}
}
4 changes: 2 additions & 2 deletions src/Models/AuditModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ public static function createOrFind(AuditActivity $activity, int $modelType, int
/** @return MorphTo<Model, self> */
public function owner(): MorphTo
{
return $this->morphTo();
return $this->morphTo()->withTrashed();
}

/** @return MorphTo<Trackable, self> */
public function entity(): MorphTo
{
return $this->morphTo();
return $this->morphTo()->withTrashed();
}
}
1 change: 1 addition & 0 deletions src/Traits/Trackable.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ public static function bootTrackable(): void
});
}

abstract public function getOwnerRelationshipName(): ?string;
abstract public function trackableOwnerResolver(): ?Model;
}

0 comments on commit 89fe4f8

Please sign in to comment.