Skip to content

Commit bce5539

Browse files
committed
feat(octane): add support for ticks and intervals with FrankenPHP
1 parent ee88fe3 commit bce5539

5 files changed

+220
-14
lines changed

src/Concerns/RegistersTickHandlers.php

+46-12
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,41 @@
66
use Illuminate\Contracts\Events\Dispatcher;
77
use Illuminate\Support\Facades\Cache;
88
use Laravel\Octane\Events\TickReceived;
9-
use Laravel\Octane\Swoole\InvokeTickCallable;
9+
use Laravel\Octane\FrankenPhp\ServerProcessInspector as FrankenPhpServerProcessInspector;
10+
use Laravel\Octane\Swoole\InvokeTickCallable as SwooleInvokeTickCallable;
11+
use Laravel\Octane\FrankenPhp\InvokeTickCallable as FrankenPhpInvokeTickCallable;
12+
use Laravel\Octane\Swoole\ServerProcessInspector as SwooleServerProcessInspector;
13+
use RuntimeException;
1014

1115
trait RegistersTickHandlers
1216
{
1317
/**
1418
* Register a callback to be called every N seconds.
15-
*
16-
* @return \Laravel\Octane\Swoole\InvokeTickCallable
1719
*/
18-
public function tick(string $key, callable $callback, int $seconds = 1, bool $immediate = true)
20+
public function tick(string $key, callable $callback, int $seconds = 1, bool $immediate = true): SwooleInvokeTickCallable|FrankenPhpInvokeTickCallable
1921
{
20-
$listener = new InvokeTickCallable(
21-
$key,
22-
$callback,
23-
$seconds,
24-
$immediate,
25-
Cache::store('octane'),
26-
app(ExceptionHandler::class)
27-
);
22+
$store = Cache::store('octane');
23+
$exceptionHandler = app(ExceptionHandler::class);
24+
25+
$listener = match (true) {
26+
$this->isSwooleServerRunning() => new SwooleInvokeTickCallable(
27+
$key,
28+
$callback,
29+
$seconds,
30+
$immediate,
31+
$store,
32+
$exceptionHandler
33+
),
34+
$this->isFrankenPhpServerRunning() => new FrankenPhpInvokeTickCallable(
35+
$key,
36+
$callback,
37+
$seconds,
38+
$immediate,
39+
$store,
40+
$exceptionHandler
41+
),
42+
default => throw new RuntimeException('Tick functionality is not supported in this environment.'),
43+
};
2844

2945
app(Dispatcher::class)->listen(
3046
TickReceived::class,
@@ -33,4 +49,22 @@ public function tick(string $key, callable $callback, int $seconds = 1, bool $im
3349

3450
return $listener;
3551
}
52+
53+
/**
54+
* Check if the Swoole server is running.
55+
*/
56+
protected function isSwooleServerRunning(): bool
57+
{
58+
return app(SwooleServerProcessInspector::class)
59+
->serverIsRunning();
60+
}
61+
62+
/**
63+
* Check if the FrankenPHP server is running.
64+
*/
65+
protected function isFrankenPhpServerRunning(): bool
66+
{
67+
return app(FrankenPhpServerProcessInspector::class)
68+
->serverIsRunning();
69+
}
3670
}

src/Facades/Octane.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use Illuminate\Support\Facades\Facade;
66

77
/**
8-
* @method static \Laravel\Octane\Swoole\InvokeTickCallable tick(string $key, callable $callback, int $seconds = 1, bool $immediate = true)
8+
* @method static \Laravel\Octane\Swoole\InvokeTickCallable|\Laravel\Octane\FrankenPhp\InvokeTickCallable tick(string $key, callable $callback, int $seconds = 1, bool $immediate = true)
99
* @method static \Swoole\Table table(string $name)
1010
* @method static \Symfony\Component\HttpFoundation\Response invokeRoute(\Illuminate\Http\Request $request, string $method, string $uri)
1111
* @method static array concurrently(array $tasks, int $waitMilliseconds = 3000)

src/FrankenPhp/InvokeTickCallable.php

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace Laravel\Octane\FrankenPhp;
4+
5+
use Illuminate\Contracts\Debug\ExceptionHandler;
6+
use Illuminate\Support\Carbon;
7+
use Throwable;
8+
9+
class InvokeTickCallable
10+
{
11+
public function __construct(
12+
protected string $key,
13+
protected $callback,
14+
protected int $seconds,
15+
protected bool $immediate,
16+
protected $cache,
17+
protected ExceptionHandler $exceptionHandler
18+
) {
19+
}
20+
21+
/**
22+
* Invoke the tick listener.
23+
* @throws Throwable
24+
*/
25+
public function __invoke(): void
26+
{
27+
$lastInvokedAt = $this->cache->get('tick-'.$this->key);
28+
29+
if (! is_null($lastInvokedAt) &&
30+
(Carbon::now()->getTimestamp() - $lastInvokedAt) < $this->seconds) {
31+
return;
32+
}
33+
34+
$this->cache->forever('tick-'.$this->key, Carbon::now()->getTimestamp());
35+
36+
if (is_null($lastInvokedAt) && ! $this->immediate) {
37+
return;
38+
}
39+
40+
try {
41+
call_user_func($this->callback);
42+
} catch (Throwable $e) {
43+
$this->exceptionHandler->report($e);
44+
}
45+
}
46+
47+
/**
48+
* Indicate how often the listener should be invoked.
49+
*/
50+
public function seconds(int $seconds): static
51+
{
52+
$this->seconds = $seconds;
53+
54+
return $this;
55+
}
56+
57+
/**
58+
* Indicate that the listener should be invoked on the first tick after the server starts.
59+
*/
60+
public function immediate(): static
61+
{
62+
$this->immediate = true;
63+
64+
return $this;
65+
}
66+
}
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
namespace Laravel\Octane\Tests;
4+
5+
use Illuminate\Contracts\Debug\ExceptionHandler;
6+
use Illuminate\Support\Carbon;
7+
use Laravel\Octane\FrankenPhp\InvokeTickCallable;
8+
use Mockery;
9+
10+
class FrankenPhpInvokeTickCallableTest extends TestCase
11+
{
12+
/**
13+
* @throws \Throwable
14+
*/
15+
public function test_callable_is_invoked_when_due(): void
16+
{
17+
Carbon::setTestNow($now = now());
18+
19+
$instance = new InvokeTickCallable(
20+
'key', fn () => $_SERVER['__test.invokeTickCallable'] = true, 1, true,
21+
$cache = Mockery::mock('stdClass'), Mockery::mock(ExceptionHandler::class)
22+
);
23+
24+
$cache->shouldReceive('get')->with('tick-key')->andReturn(time() - 100);
25+
26+
$cache->shouldReceive('forever')->once()->with('tick-key', $now->getTimestamp());
27+
28+
$instance();
29+
30+
$this->assertTrue($_SERVER['__test.invokeTickCallable'] ?? false);
31+
32+
Carbon::setTestNow();
33+
unset($_SERVER['__test.invokeTickCallable']);
34+
}
35+
36+
/**
37+
* @throws \Throwable
38+
*/
39+
public function test_callable_is_not_invoked_when_not_due(): void
40+
{
41+
Carbon::setTestNow(now());
42+
43+
$_SERVER['__test.invokeTickCallable'] = false;
44+
45+
$instance = new InvokeTickCallable(
46+
'key', fn () => $_SERVER['__test.invokeTickCallable'] = true, 30, true,
47+
$cache = Mockery::mock('stdClass'), Mockery::mock(ExceptionHandler::class)
48+
);
49+
50+
$cache->shouldReceive('get')->with('tick-key')->andReturn(time() - 10);
51+
52+
$cache->shouldReceive('forever')->never();
53+
54+
$instance();
55+
56+
$this->assertFalse($_SERVER['__test.invokeTickCallable'] ?? false);
57+
58+
Carbon::setTestNow();
59+
unset($_SERVER['__test.invokeTickCallable']);
60+
}
61+
62+
/**
63+
* @throws \Throwable
64+
*/
65+
public function test_callable_is_invoked_when_first_run_and_immediate(): void
66+
{
67+
Carbon::setTestNow($now = now());
68+
69+
$instance = new InvokeTickCallable(
70+
'key', fn () => $_SERVER['__test.invokeTickCallable'] = true, 1, true,
71+
$cache = Mockery::mock('stdClass'), Mockery::mock(ExceptionHandler::class)
72+
);
73+
74+
$cache->shouldReceive('get')->with('tick-key')->andReturn(null);
75+
76+
$cache->shouldReceive('forever')->once()->with('tick-key', $now->getTimestamp());
77+
78+
$instance();
79+
80+
$this->assertTrue($_SERVER['__test.invokeTickCallable'] ?? false);
81+
82+
Carbon::setTestNow();
83+
unset($_SERVER['__test.invokeTickCallable']);
84+
}
85+
86+
public function test_callable_is_not_invoked_when_first_run_and_not_immediate()
87+
{
88+
Carbon::setTestNow($now = now());
89+
90+
$instance = new InvokeTickCallable(
91+
'key', fn () => $_SERVER['__test.invokeTickCallable'] = true, 1, false,
92+
$cache = Mockery::mock('stdClass'), Mockery::mock(ExceptionHandler::class)
93+
);
94+
95+
$cache->shouldReceive('get')->with('tick-key')->andReturn(null);
96+
97+
$cache->shouldReceive('forever')->once()->with('tick-key', $now->getTimestamp());
98+
99+
$instance();
100+
101+
$this->assertFalse($_SERVER['__test.invokeTickCallable'] ?? false);
102+
103+
Carbon::setTestNow();
104+
unset($_SERVER['__test.invokeTickCallable']);
105+
}
106+
}

tests/InvokeTickCallableTest.php tests/SwooleInvokeTickCallableTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Laravel\Octane\Swoole\InvokeTickCallable;
88
use Mockery;
99

10-
class InvokeTickCallableTest extends TestCase
10+
class SwooleInvokeTickCallableTest extends TestCase
1111
{
1212
public function test_callable_is_invoked_when_due()
1313
{

0 commit comments

Comments
 (0)