Skip to content
This repository was archived by the owner on Mar 29, 2026. It is now read-only.

Commit 7797fb0

Browse files
committed
Test Fixes
1 parent 80e50fc commit 7797fb0

8 files changed

Lines changed: 207 additions & 11 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ vendor
1313
node_modules
1414
.php-cs-fixer.cache
1515
**/.DS_Store
16+
/.phpunit.cache

src/FilamentLoggerServiceProvider.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Illuminate\Support\Facades\Event;
1717
use MrAdder\FilamentLogger\Commands\PruneActivitiesCommand;
1818
use MrAdder\FilamentLogger\Loggers\ResourceLogger;
19+
use MrAdder\FilamentLogger\Support\ObserverRegistrar;
1920
use MrAdder\FilamentLogger\Support\ReplicationContextStore;
2021
use Spatie\LaravelPackageTools\Commands\InstallCommand;
2122
use Spatie\LaravelPackageTools\Package;
@@ -87,13 +88,13 @@ public function packageBooted(): void
8788
});
8889

8990
foreach ($loggableResources as $resource) {
90-
$resource::getModel()::observe($this->resolveResourceObserver($resource));
91+
ObserverRegistrar::register($resource::getModel(), $this->resolveResourceObserver($resource));
9192
}
9293
}
9394

9495
if (config('filament-logger.models.enabled', true) && ! empty(config('filament-logger.models.register'))) {
9596
foreach (config('filament-logger.models.register', []) as $model) {
96-
$model::observe(config('filament-logger.models.logger'));
97+
ObserverRegistrar::register($model, config('filament-logger.models.logger'));
9798
}
9899
}
99100
}

src/Loggers/AbstractModelLogger.php

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
namespace MrAdder\FilamentLogger\Loggers;
44

5+
use BadMethodCallException;
56
use Filament\Facades\Filament;
67
use Illuminate\Auth\GenericUser;
78
use Illuminate\Contracts\Auth\Authenticatable;
89
use Illuminate\Database\Eloquent\Model;
910
use Illuminate\Support\Str;
1011
use MrAdder\FilamentLogger\Support\LogDataSanitizer;
12+
use MrAdder\FilamentLogger\Support\PreviousAttributesStore;
1113
use MrAdder\FilamentLogger\Support\ReplicationContextStore;
1214
use Spatie\Activitylog\ActivityLogger;
1315
use Spatie\Activitylog\ActivityLogStatus;
@@ -141,9 +143,23 @@ protected function buildProperties(Model $model, array $attributes = [], array $
141143
/**
142144
* @return array<string, mixed>
143145
*/
144-
protected function getPreviousAttributes(Model $model): array
146+
protected function getPreviousAttributes(Model $model, bool $forget = false): array
145147
{
146-
return $model->getPrevious();
148+
$storedAttributes = $forget
149+
? PreviousAttributesStore::pull($model)
150+
: PreviousAttributesStore::get($model);
151+
152+
if ($storedAttributes !== []) {
153+
return $storedAttributes;
154+
}
155+
156+
try {
157+
return $model->getPrevious();
158+
} catch (BadMethodCallException) {
159+
$changes = $model->getChanges();
160+
161+
return array_intersect_key($model->getOriginal(), $changes);
162+
}
147163
}
148164

149165
protected function isForceDeleting(Model $model): bool
@@ -186,6 +202,20 @@ public function created(Model $model)
186202
$this->log($model, 'Created', properties: $this->buildProperties($model, $model->getAttributes()));
187203
}
188204

205+
public function updating(Model $model): void
206+
{
207+
$dirty = $model->getDirty();
208+
209+
if ($dirty === []) {
210+
return;
211+
}
212+
213+
PreviousAttributesStore::remember(
214+
$model,
215+
array_intersect_key($model->getOriginal(), $dirty),
216+
);
217+
}
218+
189219
public function updated(Model $model)
190220
{
191221
$changes = $this->getLoggableAttributes($model, $model->getChanges());
@@ -196,6 +226,7 @@ public function updated(Model $model)
196226
}
197227

198228
$this->log($model, 'Updated', properties: $this->buildProperties($model, $changes, $previous));
229+
PreviousAttributesStore::forget($model);
199230
}
200231

201232
public function deleted(Model $model)
@@ -210,7 +241,7 @@ public function deleted(Model $model)
210241
public function restored(Model $model)
211242
{
212243
$changes = $model->getChanges();
213-
$previous = $this->getPreviousAttributes($model);
244+
$previous = $this->getPreviousAttributes($model, true);
214245

215246
$this->log(
216247
$model,

src/Support/ObserverRegistrar.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace MrAdder\FilamentLogger\Support;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class ObserverRegistrar
8+
{
9+
public static function register(string $model, object|string $observer): void
10+
{
11+
if (is_string($observer)) {
12+
$model::observe($observer);
13+
14+
return;
15+
}
16+
17+
foreach ((new $model())->getObservableEvents() as $event) {
18+
if (! method_exists($observer, $event) || ! method_exists($model, $event)) {
19+
continue;
20+
}
21+
22+
$model::$event(function (Model $record) use ($observer, $event): void {
23+
$observer->{$event}($record);
24+
});
25+
}
26+
}
27+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
namespace MrAdder\FilamentLogger\Support;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use WeakMap;
7+
8+
class PreviousAttributesStore
9+
{
10+
protected static ?WeakMap $attributes = null;
11+
12+
/**
13+
* Fallback store for framework versions that dispatch a fresh model
14+
* instance to later lifecycle events.
15+
*
16+
* @var array<string, array<string, mixed>>
17+
*/
18+
protected static array $attributesByModelKey = [];
19+
20+
/**
21+
* @param array<string, mixed> $attributes
22+
*/
23+
public static function remember(Model $model, array $attributes): void
24+
{
25+
self::attributes()[$model] = $attributes;
26+
27+
$modelKey = self::getModelStoreKey($model);
28+
29+
if ($modelKey !== null) {
30+
self::$attributesByModelKey[$modelKey] = $attributes;
31+
}
32+
}
33+
34+
/**
35+
* @return array<string, mixed>
36+
*/
37+
public static function get(Model $model): array
38+
{
39+
$attributes = self::attributes()[$model] ?? null;
40+
41+
if (is_array($attributes) && $attributes !== []) {
42+
return $attributes;
43+
}
44+
45+
$modelKey = self::getModelStoreKey($model);
46+
47+
if ($modelKey === null) {
48+
return [];
49+
}
50+
51+
return self::$attributesByModelKey[$modelKey] ?? [];
52+
}
53+
54+
/**
55+
* @return array<string, mixed>
56+
*/
57+
public static function pull(Model $model): array
58+
{
59+
$attributes = self::get($model);
60+
self::forget($model);
61+
62+
return $attributes;
63+
}
64+
65+
public static function forget(Model $model): void
66+
{
67+
unset(self::attributes()[$model]);
68+
69+
$modelKey = self::getModelStoreKey($model);
70+
71+
if ($modelKey !== null) {
72+
self::forgetByModelKey($modelKey);
73+
}
74+
}
75+
76+
protected static function attributes(): WeakMap
77+
{
78+
return self::$attributes ??= new WeakMap();
79+
}
80+
81+
protected static function getModelStoreKey(Model $model): ?string
82+
{
83+
$key = $model->getKey();
84+
85+
if ($key === null) {
86+
return null;
87+
}
88+
89+
return implode(':', [
90+
$model->getConnectionName() ?? 'default',
91+
$model::class,
92+
(string) $key,
93+
]);
94+
}
95+
96+
protected static function forgetByModelKey(string $modelKey): void
97+
{
98+
unset(self::$attributesByModelKey[$modelKey]);
99+
100+
foreach (self::attributes() as $storedModel => $attributes) {
101+
if (self::getModelStoreKey($storedModel) !== $modelKey) {
102+
continue;
103+
}
104+
105+
unset(self::attributes()[$storedModel]);
106+
}
107+
}
108+
}

tests/Feature/ModelLoggerLifecycleTest.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,30 @@
22

33
use MrAdder\FilamentLogger\Loggers\ModelLogger;
44
use MrAdder\FilamentLogger\Loggers\ResourceLogger;
5+
use MrAdder\FilamentLogger\Support\ObserverRegistrar;
56
use MrAdder\FilamentLogger\Support\ReplicationContextStore;
67
use MrAdder\FilamentLogger\Tests\Fixtures\Models\TestRecord;
78
use MrAdder\FilamentLogger\Tests\Fixtures\Resources\TestRecordResource;
89
use Spatie\Activitylog\Models\Activity;
910

1011
it('stores old and new values while respecting ignored model fields', function () {
1112
config()->set('filament-logger.models.ignore', ['updated_at', 'remember_token', 'counter']);
13+
TestRecord::flushEventListeners();
14+
ObserverRegistrar::register(TestRecord::class, ModelLogger::class);
1215

1316
$record = TestRecord::query()->create([
1417
'name' => 'Before',
1518
'counter' => 1,
1619
]);
1720

21+
Activity::query()->delete();
22+
1823
$record->update([
1924
'name' => 'After',
2025
'counter' => 2,
2126
'remember_token' => 'secret-token',
2227
]);
2328

24-
(new ModelLogger())->updated($record);
25-
2629
$activity = Activity::query()->latest('id')->firstOrFail();
2730
$properties = $activity->properties->toArray();
2831

@@ -39,19 +42,21 @@
3942
config()->set('filament-logger.resources.ignore_for_resources', [
4043
TestRecordResource::class => ['counter'],
4144
]);
45+
TestRecord::flushEventListeners();
46+
ObserverRegistrar::register(TestRecord::class, new ResourceLogger(TestRecordResource::class));
4247

4348
$record = TestRecord::query()->create([
4449
'name' => 'Before',
4550
'counter' => 1,
4651
]);
4752

53+
Activity::query()->delete();
54+
4855
$record->update([
4956
'name' => 'After',
5057
'counter' => 9,
5158
]);
5259

53-
(new ResourceLogger(TestRecordResource::class))->updated($record);
54-
5560
$activity = Activity::query()->latest('id')->firstOrFail();
5661
$properties = $activity->properties->toArray();
5762

@@ -62,7 +67,7 @@
6267

6368
it('logs restore and force delete without duplicate update or delete events', function () {
6469
TestRecord::flushEventListeners();
65-
TestRecord::observe(ModelLogger::class);
70+
ObserverRegistrar::register(TestRecord::class, ModelLogger::class);
6671

6772
$record = TestRecord::query()->create([
6873
'name' => 'Lifecycle',
@@ -84,7 +89,7 @@
8489

8590
it('logs replicated records with source metadata', function () {
8691
TestRecord::flushEventListeners();
87-
TestRecord::observe(ModelLogger::class);
92+
ObserverRegistrar::register(TestRecord::class, ModelLogger::class);
8893

8994
$source = TestRecord::query()->create([
9095
'name' => 'Source',

tests/Pest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22

33
use MrAdder\FilamentLogger\Tests\TestCase;
44

5+
// Laravel's shipped database config still touches deprecated PDO mysql constants on PHP 8.4
6+
// during Testbench boot, even though this package uses sqlite in tests.
7+
error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
8+
59
uses(TestCase::class)->in(__DIR__);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
use MrAdder\FilamentLogger\Support\PreviousAttributesStore;
4+
use MrAdder\FilamentLogger\Tests\Fixtures\Models\TestRecord;
5+
6+
it('can retrieve remembered attributes for a fresh instance of the same model', function () {
7+
$record = TestRecord::query()->create([
8+
'name' => 'Before',
9+
]);
10+
11+
PreviousAttributesStore::remember($record, ['name' => 'Before']);
12+
13+
$freshRecord = $record->fresh();
14+
15+
expect($freshRecord)->not->toBeNull()
16+
->and(PreviousAttributesStore::get($freshRecord))->toBe(['name' => 'Before'])
17+
->and(PreviousAttributesStore::pull($freshRecord))->toBe(['name' => 'Before'])
18+
->and(PreviousAttributesStore::get($record))->toBe([]);
19+
});

0 commit comments

Comments
 (0)