Skip to content

Commit 562dcc4

Browse files
authored
Merge pull request #8965 from ProcessMaker/FOUR-32505
FOUR-32505 Octane MEDIUM — Accumulating State SettingObserver
2 parents 1f00e5d + c6fee3d commit 562dcc4

3 files changed

Lines changed: 150 additions & 3 deletions

File tree

ProcessMaker/Observers/SettingObserver.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
class SettingObserver
1212
{
13-
private static $added_refresh_artisan_caches = false;
13+
private bool $addedRefreshArtisanCaches = false;
1414

1515
/**
1616
* Handle the setting "created" event.
@@ -95,7 +95,7 @@ private function invalidateSettingCache(Setting $setting)
9595

9696
// Check to see if we already added the refresh to the app's terminating queue.
9797
// This is important for install commands when multiple settings are being created/updated.
98-
if (self::$added_refresh_artisan_caches) {
98+
if ($this->addedRefreshArtisanCaches) {
9999
return;
100100
}
101101

@@ -106,6 +106,6 @@ private function invalidateSettingCache(Setting $setting)
106106
RefreshArtisanCaches::dispatchSync();
107107
});
108108

109-
self::$added_refresh_artisan_caches = true;
109+
$this->addedRefreshArtisanCaches = true;
110110
}
111111
}

ProcessMaker/Providers/ProcessMakerServiceProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ public function register(): void
200200
return Models\AnonymousUser::resolve();
201201
});
202202

203+
$this->app->scoped(Observers\SettingObserver::class);
204+
203205
$this->app->singleton(PolicyExtension::class, function ($app) {
204206
return new PolicyExtension();
205207
});
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
namespace Tests\Unit\ProcessMaker\Observers;
4+
5+
use Illuminate\Foundation\Application;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Support\Facades\Bus;
8+
use Laravel\Octane\Events\RequestTerminated;
9+
use Laravel\Octane\Listeners\FlushTemporaryContainerInstances;
10+
use ProcessMaker\Cache\Settings\SettingCacheFactory;
11+
use ProcessMaker\Jobs\RefreshArtisanCaches;
12+
use ProcessMaker\Models\Setting;
13+
use ProcessMaker\Observers\SettingObserver;
14+
use ReflectionProperty;
15+
use Symfony\Component\HttpFoundation\Response;
16+
use Tests\TestCase;
17+
18+
class SettingObserverTest extends TestCase
19+
{
20+
public function test_it_schedules_one_refresh_callback_per_scope(): void
21+
{
22+
$this->app->forgetScopedInstances();
23+
24+
$observer = app(SettingObserver::class);
25+
$callbackCount = $this->terminatingCallbackCount();
26+
27+
$observer->saving($this->setting('first-setting'));
28+
$observer->saving($this->setting('second-setting'));
29+
$observer->deleted($this->setting('third-setting'));
30+
31+
$this->assertSame($observer, app(SettingObserver::class));
32+
$this->assertSame($callbackCount + 1, $this->terminatingCallbackCount());
33+
}
34+
35+
public function test_octane_termination_allows_a_refresh_callback_in_the_next_scope(): void
36+
{
37+
$this->app->forgetScopedInstances();
38+
39+
$firstObserver = app(SettingObserver::class);
40+
$firstObserver->saving($this->setting('first-request-setting'));
41+
$callbackCount = $this->terminatingCallbackCount();
42+
43+
(new FlushTemporaryContainerInstances())->handle(new RequestTerminated(
44+
$this->app,
45+
$this->app,
46+
Request::create('/first-request'),
47+
new Response()
48+
));
49+
50+
$secondObserver = app(SettingObserver::class);
51+
$secondObserver->saving($this->setting('second-request-setting'));
52+
53+
$this->assertNotSame($firstObserver, $secondObserver);
54+
$this->assertSame($callbackCount + 1, $this->terminatingCallbackCount());
55+
}
56+
57+
public function test_eloquent_saves_updates_and_deletes_share_the_scoped_observer(): void
58+
{
59+
$this->app->forgetScopedInstances();
60+
$callbackCount = $this->terminatingCallbackCount();
61+
62+
$firstSetting = Setting::factory()->create([
63+
'key' => 'four-32505-first-setting',
64+
'config' => 'first value',
65+
'format' => 'text',
66+
]);
67+
Setting::factory()->create([
68+
'key' => 'four-32505-second-setting',
69+
'config' => 'second value',
70+
'format' => 'text',
71+
]);
72+
73+
$firstSetting->config = 'updated value';
74+
$firstSetting->save();
75+
$firstSetting->delete();
76+
77+
$this->assertSame($callbackCount + 1, $this->terminatingCallbackCount());
78+
}
79+
80+
public function test_it_invalidates_every_setting_cache_entry_while_debouncing_the_refresh(): void
81+
{
82+
$this->app->forgetScopedInstances();
83+
84+
$observer = app(SettingObserver::class);
85+
$settingCache = SettingCacheFactory::getSettingsCache();
86+
$settings = [
87+
$this->setting('cached-first-setting'),
88+
$this->setting('cached-second-setting'),
89+
$this->setting('cached-deleted-setting'),
90+
];
91+
92+
foreach ($settings as $setting) {
93+
$settingCache->set($settingCache->createKey(['key' => $setting->key]), 'cached value');
94+
}
95+
96+
$callbackCount = $this->terminatingCallbackCount();
97+
$observer->saving($settings[0]);
98+
$observer->saving($settings[1]);
99+
$observer->deleted($settings[2]);
100+
101+
foreach ($settings as $setting) {
102+
$this->assertTrue($settingCache->missing(
103+
$settingCache->createKey(['key' => $setting->key])
104+
));
105+
}
106+
107+
$this->assertSame($callbackCount + 1, $this->terminatingCallbackCount());
108+
}
109+
110+
public function test_the_terminating_callback_dispatches_the_refresh_job_synchronously(): void
111+
{
112+
Bus::fake([RefreshArtisanCaches::class]);
113+
$this->app->forgetScopedInstances();
114+
115+
$callbackCount = $this->terminatingCallbackCount();
116+
app(SettingObserver::class)->saving($this->setting('refresh-job-setting'));
117+
118+
Bus::assertNotDispatched(RefreshArtisanCaches::class);
119+
120+
$callbacks = $this->terminatingCallbacks();
121+
$this->app->call($callbacks[$callbackCount]);
122+
123+
Bus::assertDispatchedSyncTimes(RefreshArtisanCaches::class, 1);
124+
}
125+
126+
private function setting(string $key): Setting
127+
{
128+
return new Setting([
129+
'key' => $key,
130+
'config' => 'value',
131+
'format' => 'text',
132+
]);
133+
}
134+
135+
private function terminatingCallbackCount(): int
136+
{
137+
return count($this->terminatingCallbacks());
138+
}
139+
140+
private function terminatingCallbacks(): array
141+
{
142+
return (new ReflectionProperty(Application::class, 'terminatingCallbacks'))
143+
->getValue($this->app);
144+
}
145+
}

0 commit comments

Comments
 (0)