Skip to content

Commit 39db9ec

Browse files
authored
Merge pull request #365 from bowphp/refactor/code-base
Add scheduler features
2 parents ef6cade + 19b60e1 commit 39db9ec

18 files changed

Lines changed: 3823 additions & 7 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ composer.lock
99
.phpunit.result.cache
1010
bob
1111
.phpunit.cache
12+
.vscode/

.vscode/settings.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/Configuration/Configuration.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ public function getName(): string
4949
* @param Loader $config
5050
* @return void
5151
*/
52-
abstract public function create(Loader $config): void;
52+
public function create(Loader $config): void
53+
{
54+
// By default, we do nothing here, but you can override this method in your configuration class
55+
// to set up your server or package as needed.
56+
}
5357

5458
/**
5559
* Start the configured package

src/Configuration/Loader.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Bow\Configuration\EnvConfiguration;
1212
use Bow\Application\Exception\ApplicationException;
1313
use Bow\Container\CompassConfiguration;
14+
use Bow\Scheduler\Scheduler;
1415

1516
class Loader implements ArrayAccess
1617
{
@@ -369,6 +370,19 @@ public function events(): array
369370
];
370371
}
371372

373+
/**
374+
* Define scheduled tasks
375+
*
376+
* Override this method in your Kernel to define scheduled tasks.
377+
*
378+
* @param Scheduler $schedule
379+
* @return void
380+
*/
381+
public function schedules(Scheduler $schedule): void
382+
{
383+
//
384+
}
385+
372386
/**
373387
* @inheritDoc
374388
*/

src/Console/Command.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Bow\Console\Command\SeederCommand;
1111
use Bow\Console\Command\ServerCommand;
1212
use Bow\Console\Command\WorkerCommand;
13+
use Bow\Console\Command\SchedulerCommand;
1314
use Bow\Console\Command\MigrationCommand;
1415
use Bow\Console\Command\Generator\GenerateKeyCommand;
1516
use Bow\Console\Command\Generator\GenerateCacheCommand;
@@ -64,6 +65,11 @@ class Command extends AbstractCommand
6465
"run:server" => ServerCommand::class,
6566
"run:worker" => WorkerCommand::class,
6667
"flush:worker" => WorkerCommand::class,
68+
"schedule:run" => SchedulerCommand::class,
69+
"schedule:work" => SchedulerCommand::class,
70+
"schedule:list" => SchedulerCommand::class,
71+
"schedule:next" => SchedulerCommand::class,
72+
"schedule:test" => SchedulerCommand::class,
6773
"generate:key" => GenerateKeyCommand::class,
6874
"generate:resource" => GenerateRouterResourceCommand::class,
6975
"generate:session-table" => GenerateSessionCommand::class,
@@ -99,7 +105,7 @@ public function call(string $command, string $action, ...$rest): mixed
99105
$this->throwFailsCommand("The command $command not found !");
100106
}
101107

102-
if (!preg_match('/^(migration|seed)/', $command)) {
108+
if (!preg_match('/^(migration|seed|schedule)/', $command)) {
103109
$method = "run";
104110
} else {
105111
$method = $action;
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bow\Console\Command;
6+
7+
use DateTime;
8+
use Bow\Console\AbstractCommand;
9+
use Bow\Console\Color;
10+
use Bow\Scheduler\Scheduler;
11+
use Bow\Configuration\Loader;
12+
13+
class SchedulerCommand extends AbstractCommand
14+
{
15+
/**
16+
* Run the scheduler once (execute all due events)
17+
*
18+
* @return void
19+
*/
20+
public function run(): void
21+
{
22+
$scheduler = $this->getScheduler();
23+
24+
echo Color::green("Running scheduler...\n");
25+
26+
$results = $scheduler->run();
27+
28+
if (empty($results)) {
29+
echo Color::yellow("No scheduled events are due.\n");
30+
return;
31+
}
32+
33+
foreach ($results as $result) {
34+
$this->displayResult($result);
35+
}
36+
37+
echo Color::green("\nScheduler run completed.\n");
38+
}
39+
40+
/**
41+
* Start the scheduler daemon (continuous loop)
42+
*
43+
* @return void
44+
*/
45+
public function work(): void
46+
{
47+
$scheduler = $this->getScheduler();
48+
49+
echo Color::green("Starting scheduler daemon...\n");
50+
echo Color::yellow("Press Ctrl+C to stop.\n\n");
51+
52+
// Set up custom logger for console output
53+
$scheduler->setLogger(function (string $message) {
54+
echo $message . "\n";
55+
});
56+
57+
$scheduler->start();
58+
}
59+
60+
/**
61+
* List all registered scheduled events
62+
*
63+
* @return void
64+
*/
65+
public function list(): void
66+
{
67+
$scheduler = $this->getScheduler();
68+
$events = $scheduler->getEvents();
69+
70+
if (empty($events)) {
71+
echo Color::yellow("No scheduled events registered.\n");
72+
return;
73+
}
74+
75+
echo Color::green("Registered Scheduled Events:\n");
76+
echo str_repeat('-', 100) . "\n";
77+
78+
printf("%-45s | %-10s | %-15s | %s\n", "Description", "Type", "Expression", "Next Due");
79+
echo str_repeat('-', 100) . "\n";
80+
81+
$now = new DateTime();
82+
83+
foreach ($events as $event) {
84+
$description = $event->getDescription();
85+
$type = $event->getType();
86+
$expression = $event->getCronExpression();
87+
$isDue = $event->isDue($now);
88+
89+
// Truncate long descriptions
90+
if (strlen($description) > 43) {
91+
$description = substr($description, 0, 40) . '...';
92+
}
93+
94+
$dueStatus = $isDue ? Color::green("DUE NOW") : Color::yellow("waiting");
95+
96+
printf(
97+
"%-45s | %-10s | %-15s | %s\n",
98+
$description,
99+
$type,
100+
$expression,
101+
$dueStatus
102+
);
103+
}
104+
105+
echo str_repeat('-', 100) . "\n";
106+
echo Color::green("Total: " . count($events) . " event(s)\n");
107+
}
108+
109+
/**
110+
* Show the next run time for all events
111+
*
112+
* @return void
113+
*/
114+
public function next(): void
115+
{
116+
$scheduler = $this->getScheduler();
117+
$events = $scheduler->getEvents();
118+
119+
if (empty($events)) {
120+
echo Color::yellow("No scheduled events registered.\n");
121+
return;
122+
}
123+
124+
echo Color::green("Next Run Times:\n");
125+
echo str_repeat('-', 80) . "\n";
126+
127+
$now = new DateTime();
128+
129+
foreach ($events as $event) {
130+
$description = $event->getDescription();
131+
$isDue = $event->isDue($now);
132+
133+
$status = $isDue
134+
? Color::green("DUE NOW")
135+
: Color::yellow("waiting");
136+
137+
echo sprintf(
138+
"[%-8s] %-50s %s (%s)\n",
139+
$event->getType(),
140+
$description,
141+
$status,
142+
$event->getCronExpression()
143+
);
144+
}
145+
146+
echo str_repeat('-', 80) . "\n";
147+
}
148+
149+
/**
150+
* Test run a specific event by its index
151+
*
152+
* @param int $index The 0-based index of the event to run
153+
* @return void
154+
*/
155+
public function test(int $index = 0): void
156+
{
157+
$scheduler = $this->getScheduler();
158+
$events = $scheduler->getEvents();
159+
160+
if (empty($events)) {
161+
echo Color::yellow("No scheduled events registered.\n");
162+
return;
163+
}
164+
165+
if ($index < 0 || $index >= count($events)) {
166+
echo Color::red("Invalid event index: {$index}\n");
167+
echo Color::yellow("Use 'php bow schedule:list' to see available events (0-indexed).\n");
168+
return;
169+
}
170+
171+
$event = $events[$index];
172+
$description = $event->getDescription();
173+
174+
echo Color::green("Running event: {$description}\n");
175+
176+
try {
177+
$startTime = microtime(true);
178+
$event->run();
179+
$endTime = microtime(true);
180+
181+
$duration = round(($endTime - $startTime) * 1000, 2);
182+
echo Color::green("Event completed successfully in {$duration}ms\n");
183+
184+
$output = $event->getOutput();
185+
if ($output) {
186+
echo Color::yellow("Output:\n{$output}\n");
187+
}
188+
} catch (\Throwable $e) {
189+
echo Color::red("Event failed: " . $e->getMessage() . "\n");
190+
echo Color::yellow("Stack trace:\n" . $e->getTraceAsString() . "\n");
191+
}
192+
}
193+
194+
/**
195+
* Get the scheduler instance
196+
*
197+
* @return Scheduler
198+
*/
199+
private function getScheduler(): Scheduler
200+
{
201+
$scheduler = Scheduler::getInstance();
202+
203+
$this->loadSchedulerFile($scheduler);
204+
205+
return $scheduler;
206+
}
207+
208+
/**
209+
* Load the scheduler from kernel
210+
*
211+
* @param Scheduler $scheduler
212+
* @return void
213+
*/
214+
private function loadSchedulerFile(Scheduler $scheduler): void
215+
{
216+
$kernel = Loader::getInstance();
217+
218+
$kernel->schedules($scheduler);
219+
}
220+
221+
/**
222+
* Display an event result
223+
*
224+
* @param array $result
225+
* @return void
226+
*/
227+
private function displayResult(array $result): void
228+
{
229+
$status = match ($result['status']) {
230+
'success' => Color::green('[SUCCESS]'),
231+
'failed' => Color::red('[FAILED]'),
232+
'skipped' => Color::yellow('[SKIPPED]'),
233+
default => Color::yellow('[UNKNOWN]'),
234+
};
235+
236+
echo sprintf(
237+
"%s [%s] %s\n",
238+
$status,
239+
$result['type'],
240+
$result['description']
241+
);
242+
243+
if ($result['error']) {
244+
echo Color::red(" Error: {$result['error']}\n");
245+
}
246+
247+
if ($result['started_at'] && $result['finished_at']) {
248+
$duration = $result['finished_at']->getTimestamp() - $result['started_at']->getTimestamp();
249+
echo Color::yellow(" Duration: {$duration}s\n");
250+
}
251+
}
252+
}

0 commit comments

Comments
 (0)