Skip to content

Commit 1bc3b3d

Browse files
committed
Implement tool registration persistence in application container for Octane compatibility
1 parent 89859b2 commit 1bc3b3d

File tree

3 files changed

+114
-1
lines changed

3 files changed

+114
-1
lines changed

src/Loop.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,25 @@ public function tool(Tool $tool): static
3838
{
3939
$this->tools->push($tool);
4040

41+
$this->storeRegistration(function (Loop $loop) use ($tool) {
42+
$loop->tools->push($tool);
43+
});
44+
4145
return $this;
4246
}
4347

4448
public function toolkit(Toolkit $toolkit): static
4549
{
4650
foreach ($toolkit->getTools() as $tool) {
47-
$this->tool($tool);
51+
$this->tools->push($tool);
4852
}
4953

54+
$this->storeRegistration(function (Loop $loop) use ($toolkit) {
55+
foreach ($toolkit->getTools() as $tool) {
56+
$loop->tools->push($tool);
57+
}
58+
});
59+
5060
return $this;
5161
}
5262

@@ -108,4 +118,18 @@ public function getPrismTool(string $name): PrismTool
108118
{
109119
return $this->tools->getTool($name)->build();
110120
}
121+
122+
/**
123+
* Store a registration callback for replay in new instances.
124+
*/
125+
protected function storeRegistration(callable $callback): void
126+
{
127+
if (! app()->bound('loop.registrations')) {
128+
app()->instance('loop.registrations', []);
129+
}
130+
131+
$registrations = app('loop.registrations');
132+
$registrations[] = $callback;
133+
app()->instance('loop.registrations', $registrations);
134+
}
111135
}

src/LoopServiceProvider.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ public function packageBooted(): void
4242
return $loop;
4343
});
4444

45+
/**
46+
* Set up container resolving hook to replay tool registrations for Octane compatibility
47+
*/
48+
$this->app->resolving(Loop::class, function ($loop, $app) {
49+
if ($app->bound('loop.registrations')) {
50+
$registrations = $app->make('loop.registrations');
51+
52+
if (is_array($registrations)) {
53+
foreach ($registrations as $registration) {
54+
if (is_callable($registration)) {
55+
$registration($loop);
56+
}
57+
}
58+
}
59+
}
60+
});
61+
4562
$this->app->bind(SseService::class, function ($app) {
4663
return new SseService;
4764
});
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
use Kirschbaum\Loop\Facades\Loop as LoopFacade;
4+
use Kirschbaum\Loop\Loop;
5+
use Kirschbaum\Loop\Toolkits\LaravelModelToolkit;
6+
use Workbench\App\Models\User;
7+
8+
beforeEach(function () {
9+
app()->forgetInstance(Loop::class);
10+
app()->forgetInstance('loop.registrations');
11+
});
12+
13+
test('toolkits persist after instance recreation', function () {
14+
LoopFacade::toolkit(LaravelModelToolkit::make([User::class]));
15+
16+
$firstInstance = app(Loop::class);
17+
$firstTools = $firstInstance->getPrismTools();
18+
19+
expect($firstTools->count())
20+
->toBeGreaterThan(0, 'First instance should have tools from registered toolkit');
21+
22+
app()->forgetInstance(Loop::class); // Simulates Octane instance recreation.
23+
24+
$secondInstance = app(Loop::class);
25+
$secondTools = $secondInstance->getPrismTools();
26+
27+
expect($secondTools->count())
28+
->toBeGreaterThan(0, 'Second instance should have restored tools from registrations')
29+
->and($secondTools->count())
30+
->toEqual($firstTools->count(), 'Both instances should have the same number of tools');
31+
});
32+
33+
test('multiple toolkit registrations are preserved', function () {
34+
LoopFacade::toolkit(LaravelModelToolkit::make([User::class]));
35+
LoopFacade::toolkit(LaravelModelToolkit::make([User::class]));
36+
37+
$firstInstance = app(Loop::class);
38+
$firstTools = $firstInstance->getPrismTools();
39+
$firstCount = $firstTools->count();
40+
41+
expect($firstCount)->toBeGreaterThan(0, 'Should have tools from multiple registrations');
42+
43+
app()->forgetInstance(Loop::class);
44+
45+
$secondInstance = app(Loop::class);
46+
$secondTools = $secondInstance->getPrismTools();
47+
48+
expect($secondTools->count())->toEqual($firstCount, 'All toolkit registrations should be preserved');
49+
});
50+
51+
test('empty registrations handle gracefully', function () {
52+
$instance = app(Loop::class);
53+
$tools = $instance->getPrismTools();
54+
55+
expect($tools->count())->toEqual(0, 'Should have no tools when nothing is registered');
56+
});
57+
58+
test('registrations are stored correctly', function () {
59+
LoopFacade::toolkit(LaravelModelToolkit::make([User::class]));
60+
61+
expect(app()->bound('loop.registrations'))->toBeTrue('Registrations should be stored in container');
62+
63+
$registrations = app('loop.registrations');
64+
expect($registrations)
65+
->toBeArray('Registrations should be an array')
66+
->and(count($registrations))
67+
->toBeGreaterThan(0, 'Should have at least one registration');
68+
69+
foreach ($registrations as $registration) {
70+
expect(is_callable($registration))->toBeTrue('Each registration should be callable');
71+
}
72+
});

0 commit comments

Comments
 (0)