Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9cf6f6d

Browse files
committedDec 19, 2024··
feat(octane): add support for ticks and intervals with FrankenPHP
1 parent ee88fe3 commit 9cf6f6d

5 files changed

+221
-12
lines changed
 

‎src/Concerns/RegistersTickHandlers.php

+46-10
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,43 @@
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\InvokeTickCallable as FrankenPhpInvokeTickCallable;
10+
use Laravel\Octane\FrankenPhp\ServerProcessInspector as FrankenPhpServerProcessInspector;
11+
use Laravel\Octane\Swoole\InvokeTickCallable as SwooleInvokeTickCallable;
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.
1519
*
16-
* @return \Laravel\Octane\Swoole\InvokeTickCallable
20+
* @return \Laravel\Octane\Swoole\InvokeTickCallable|\Laravel\Octane\FrankenPhp\InvokeTickCallable
1721
*/
1822
public function tick(string $key, callable $callback, int $seconds = 1, bool $immediate = true)
1923
{
20-
$listener = new InvokeTickCallable(
21-
$key,
22-
$callback,
23-
$seconds,
24-
$immediate,
25-
Cache::store('octane'),
26-
app(ExceptionHandler::class)
27-
);
24+
$store = Cache::store('octane');
25+
$exceptionHandler = app(ExceptionHandler::class);
26+
27+
$listener = match (true) {
28+
$this->isSwooleServerRunning() => new SwooleInvokeTickCallable(
29+
$key,
30+
$callback,
31+
$seconds,
32+
$immediate,
33+
$store,
34+
$exceptionHandler
35+
),
36+
$this->isFrankenPhpServerRunning() => new FrankenPhpInvokeTickCallable(
37+
$key,
38+
$callback,
39+
$seconds,
40+
$immediate,
41+
$store,
42+
$exceptionHandler
43+
),
44+
default => throw new RuntimeException('Tick functionality is not supported in this environment.'),
45+
};
2846

2947
app(Dispatcher::class)->listen(
3048
TickReceived::class,
@@ -33,4 +51,22 @@ public function tick(string $key, callable $callback, int $seconds = 1, bool $im
3351

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

‎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

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
*
24+
* @throws Throwable
25+
*/
26+
public function __invoke(): void
27+
{
28+
$lastInvokedAt = $this->cache->get('tick-'.$this->key);
29+
30+
if (! is_null($lastInvokedAt) &&
31+
(Carbon::now()->getTimestamp() - $lastInvokedAt) < $this->seconds) {
32+
return;
33+
}
34+
35+
$this->cache->forever('tick-'.$this->key, Carbon::now()->getTimestamp());
36+
37+
if (is_null($lastInvokedAt) && ! $this->immediate) {
38+
return;
39+
}
40+
41+
try {
42+
call_user_func($this->callback);
43+
} catch (Throwable $e) {
44+
$this->exceptionHandler->report($e);
45+
}
46+
}
47+
48+
/**
49+
* Indicate how often the listener should be invoked.
50+
*/
51+
public function seconds(int $seconds): static
52+
{
53+
$this->seconds = $seconds;
54+
55+
return $this;
56+
}
57+
58+
/**
59+
* Indicate that the listener should be invoked on the first tick after the server starts.
60+
*/
61+
public function immediate(): static
62+
{
63+
$this->immediate = true;
64+
65+
return $this;
66+
}
67+
}
+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)
Please sign in to comment.