Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"amphp/postgres": "v2.0.0",
"amphp/redis": "^2.0",
"amphp/socket": "^2.1.0",
"dragonmantank/cron-expression": "^3.6",
"egulias/email-validator": "^4.0",
"fakerphp/faker": "^1.23",
"kelunik/rate-limit": "^3.0",
Expand Down
3 changes: 3 additions & 0 deletions src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
use Phenix\Http\Constants\Protocol;
use Phenix\Logging\LoggerFactory;
use Phenix\Runtime\Log;
use Phenix\Scheduling\TimerRegistry;
use Phenix\Session\SessionMiddlewareFactory;

use function Amp\async;
Expand Down Expand Up @@ -116,6 +117,8 @@ public function run(): void

$this->isRunning = true;

TimerRegistry::run();

if ($this->serverMode === ServerMode::CLUSTER && $this->signalTrapping) {
async(function (): void {
Cluster::awaitTermination();
Expand Down
8 changes: 3 additions & 5 deletions src/Events/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,10 @@ public function boot(): void

private function loadEvents(): void
{
$eventPath = base_path('events');
$eventsPath = base_path('listen' . DIRECTORY_SEPARATOR . 'events.php');

if (File::exists($eventPath)) {
foreach (File::listFilesRecursively($eventPath, '.php') as $file) {
require $file;
}
if (File::exists($eventsPath)) {
require $eventsPath;
}
}
}
24 changes: 24 additions & 0 deletions src/Facades/Schedule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Phenix\Facades;

use Phenix\Runtime\Facade;
use Phenix\Scheduling\Scheduler;
use Phenix\Scheduling\Timer;

/**
* @method static Timer timer(Closure $closure)
* @method static Scheduler call(Closure $closure)
* @method static void run()
*
* @see \Phenix\Scheduling\Schedule
*/
class Schedule extends Facade
{
protected static function getKeyName(): string
{
return \Phenix\Scheduling\Schedule::class;
}
}
4 changes: 4 additions & 0 deletions src/Filesystem/FilesystemServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ public function provides(string $id): bool
public function register(): void
{
$this->bind(Storage::class);
}

public function boot(): void
{
$this->bind(FileContract::class, File::class);
}
}
7 changes: 5 additions & 2 deletions src/Routing/RouteServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Phenix\Routing;

use Phenix\Facades\File;
use Phenix\Providers\ServiceProvider;
use Phenix\Routing\Console\RouteList;
use Phenix\Util\Directory;
Expand Down Expand Up @@ -41,8 +42,10 @@ private function getControllersPath(): string

private function loadRoutes(): void
{
foreach (Directory::all(base_path('routes')) as $file) {
require $file;
$routesPath = base_path('routes' . DIRECTORY_SEPARATOR . 'api.php');

if (File::exists($routesPath)) {
require $routesPath;
}
}
}
36 changes: 36 additions & 0 deletions src/Scheduling/Console/ScheduleRunCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Phenix\Scheduling\Console;

use Phenix\App;
use Phenix\Scheduling\Schedule;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ScheduleRunCommand extends Command
{
/**
* @var string
* @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint
*/
protected static $defaultName = 'schedule:run';

/**
* @var string
* @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint
*/
protected static $defaultDescription = 'Run the scheduled tasks once and exit';

protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var Schedule $schedule */
$schedule = App::make(Schedule::class);

$schedule->run();

return Command::SUCCESS;
}
}
36 changes: 36 additions & 0 deletions src/Scheduling/Console/ScheduleWorkCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Phenix\Scheduling\Console;

use Phenix\App;
use Phenix\Scheduling\ScheduleWorker;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ScheduleWorkCommand extends Command
{
/**
* @var string
* @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint
*/
protected static $defaultName = 'schedule:work';

/**
* @var string
* @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint
*/
protected static $defaultDescription = 'Run the scheduled tasks in a long-running process';

protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var ScheduleWorker $worker */
$worker = App::make(ScheduleWorker::class);

$worker->daemon($output);

return Command::SUCCESS;
}
}
44 changes: 44 additions & 0 deletions src/Scheduling/Schedule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Phenix\Scheduling;

use Closure;
use Phenix\Util\Date;

class Schedule
{
/**
* @var array<Scheduler>
*/
protected array $schedules = [];

public function timer(Closure $closure): Timer
{
$timer = new Timer($closure);

TimerRegistry::add($timer);

return $timer;
}

public function call(Closure $closure): Scheduler
{
$scheduler = new Scheduler($closure);

$this->schedules[] = $scheduler;

return $scheduler;
}

public function run(): void
{
$now = null;
foreach ($this->schedules as $scheduler) {
$now ??= Date::now('UTC');

$scheduler->tick($now);
}
}
}
77 changes: 77 additions & 0 deletions src/Scheduling/ScheduleWorker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace Phenix\Scheduling;

use Phenix\Facades\Schedule;
use Phenix\Util\Date;
use Symfony\Component\Console\Output\OutputInterface;

class ScheduleWorker
{
protected bool $quit = false;

public function daemon(OutputInterface|null $output = null): void
{
$output?->writeln('<info>Starting schedule worker...</info>');

$this->listenSignals();

$lastRunKey = null;

while (true) {
if ($this->shouldQuit()) {
break;
}

$this->sleepMicroseconds(100_000); // 100ms

$now = $this->now();

if ($now->second !== 0) {
continue;
}

$currentKey = $now->format('Y-m-d H:i');

if ($currentKey === $lastRunKey) {
continue;
}

Schedule::run();

$lastRunKey = $currentKey;
}

$output?->writeln('<info>Schedule worker stopped.</info>');
}

public function shouldQuit(): bool
{
return $this->quit;
}

protected function sleepMicroseconds(int $microseconds): void
{
usleep($microseconds);
}

protected function now(): Date
{
return Date::now('UTC');
}

protected function listenSignals(): void
{
pcntl_async_signals(true);

pcntl_signal(SIGINT, function (): void {
$this->quit = true;
});

pcntl_signal(SIGTERM, function (): void {
$this->quit = true;
});
}
}
Loading